GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
Line | Branch | Exec | Source |
1 |
/* $OpenBSD: sdiff.c,v 1.36 2015/12/29 19:04:46 gsoares Exp $ */ |
||
2 |
|||
3 |
/* |
||
4 |
* Written by Raymond Lai <ray@cyth.net>. |
||
5 |
* Public domain. |
||
6 |
*/ |
||
7 |
|||
8 |
#include <sys/queue.h> |
||
9 |
#include <sys/stat.h> |
||
10 |
#include <sys/types.h> |
||
11 |
#include <sys/wait.h> |
||
12 |
|||
13 |
#include <ctype.h> |
||
14 |
#include <err.h> |
||
15 |
#include <errno.h> |
||
16 |
#include <fcntl.h> |
||
17 |
#include <getopt.h> |
||
18 |
#include <limits.h> |
||
19 |
#include <paths.h> |
||
20 |
#include <stdint.h> |
||
21 |
#include <stdio.h> |
||
22 |
#include <stdlib.h> |
||
23 |
#include <string.h> |
||
24 |
#include <unistd.h> |
||
25 |
#include <util.h> |
||
26 |
|||
27 |
#include "common.h" |
||
28 |
#include "extern.h" |
||
29 |
|||
30 |
#define WIDTH 130 |
||
31 |
/* |
||
32 |
* Each column must be at least one character wide, plus three |
||
33 |
* characters between the columns (space, [<|>], space). |
||
34 |
*/ |
||
35 |
#define WIDTH_MIN 5 |
||
36 |
|||
37 |
/* A single diff line. */ |
||
38 |
struct diffline { |
||
39 |
SIMPLEQ_ENTRY(diffline) diffentries; |
||
40 |
char *left; |
||
41 |
char div; |
||
42 |
char *right; |
||
43 |
}; |
||
44 |
|||
45 |
static void astrcat(char **, const char *); |
||
46 |
static void enqueue(char *, char, char *); |
||
47 |
static char *mktmpcpy(const char *); |
||
48 |
static void freediff(struct diffline *); |
||
49 |
static void int_usage(void); |
||
50 |
static int parsecmd(FILE *, FILE *, FILE *); |
||
51 |
static void printa(FILE *, size_t); |
||
52 |
static void printc(FILE *, size_t, FILE *, size_t); |
||
53 |
static void printcol(const char *, size_t *, const size_t); |
||
54 |
static void printd(FILE *, size_t); |
||
55 |
static void println(const char *, const char, const char *); |
||
56 |
static void processq(void); |
||
57 |
static void prompt(const char *, const char *); |
||
58 |
__dead static void usage(void); |
||
59 |
static char *xfgets(FILE *); |
||
60 |
|||
61 |
SIMPLEQ_HEAD(, diffline) diffhead = SIMPLEQ_HEAD_INITIALIZER(diffhead); |
||
62 |
size_t line_width; /* width of a line (two columns and divider) */ |
||
63 |
size_t width; /* width of each column */ |
||
64 |
size_t file1ln, file2ln; /* line number of file1 and file2 */ |
||
65 |
int Iflag = 0; /* ignore sets matching regexp */ |
||
66 |
int lflag; /* print only left column for identical lines */ |
||
67 |
int sflag; /* skip identical lines */ |
||
68 |
FILE *outfp; /* file to save changes to */ |
||
69 |
const char *tmpdir; /* TMPDIR or /tmp */ |
||
70 |
|||
71 |
static struct option longopts[] = { |
||
72 |
{ "text", no_argument, NULL, 'a' }, |
||
73 |
{ "ignore-blank-lines", no_argument, NULL, 'B' }, |
||
74 |
{ "ignore-space-change", no_argument, NULL, 'b' }, |
||
75 |
{ "minimal", no_argument, NULL, 'd' }, |
||
76 |
{ "ignore-tab-expansion", no_argument, NULL, 'E' }, |
||
77 |
{ "diff-program", required_argument, NULL, 'F' }, |
||
78 |
{ "speed-large-files", no_argument, NULL, 'H' }, |
||
79 |
{ "ignore-matching-lines", required_argument, NULL, 'I' }, |
||
80 |
{ "ignore-case", no_argument, NULL, 'i' }, |
||
81 |
{ "left-column", no_argument, NULL, 'l' }, |
||
82 |
{ "output", required_argument, NULL, 'o' }, |
||
83 |
{ "strip-trailing-cr", no_argument, NULL, 'S' }, |
||
84 |
{ "suppress-common-lines", no_argument, NULL, 's' }, |
||
85 |
{ "expand-tabs", no_argument, NULL, 't' }, |
||
86 |
{ "ignore-all-space", no_argument, NULL, 'W' }, |
||
87 |
{ "width", required_argument, NULL, 'w' }, |
||
88 |
{ NULL, 0, NULL, 0 } |
||
89 |
}; |
||
90 |
|||
91 |
/* |
||
92 |
* Create temporary file if source_file is not a regular file. |
||
93 |
* Returns temporary file name if one was malloced, NULL if unnecessary. |
||
94 |
*/ |
||
95 |
static char * |
||
96 |
mktmpcpy(const char *source_file) |
||
97 |
{ |
||
98 |
528 |
struct stat sb; |
|
99 |
ssize_t rcount; |
||
100 |
int ifd, ofd; |
||
101 |
264 |
u_char buf[BUFSIZ]; |
|
102 |
264 |
char *target_file; |
|
103 |
|||
104 |
/* Open input and output. */ |
||
105 |
264 |
ifd = open(source_file, O_RDONLY, 0); |
|
106 |
/* File was opened successfully. */ |
||
107 |
✓✗ | 264 |
if (ifd != -1) { |
108 |
✗✓ | 264 |
if (fstat(ifd, &sb) == -1) |
109 |
err(2, "error getting file status from %s", source_file); |
||
110 |
|||
111 |
/* Regular file. */ |
||
112 |
✓✓ | 264 |
if (S_ISREG(sb.st_mode)) { |
113 |
220 |
close(ifd); |
|
114 |
220 |
return (NULL); |
|
115 |
} |
||
116 |
} else { |
||
117 |
/* If ``-'' does not exist the user meant stdin. */ |
||
118 |
if (errno == ENOENT && strcmp(source_file, "-") == 0) |
||
119 |
ifd = STDIN_FILENO; |
||
120 |
else |
||
121 |
err(2, "error opening %s", source_file); |
||
122 |
} |
||
123 |
|||
124 |
/* Not a regular file, so copy input into temporary file. */ |
||
125 |
✗✓ | 44 |
if (asprintf(&target_file, "%s/sdiff.XXXXXXXXXX", tmpdir) == -1) |
126 |
err(2, "asprintf"); |
||
127 |
✗✓ | 44 |
if ((ofd = mkstemp(target_file)) == -1) { |
128 |
warn("error opening %s", target_file); |
||
129 |
goto FAIL; |
||
130 |
} |
||
131 |
✓✓ | 156 |
while ((rcount = read(ifd, buf, sizeof(buf))) != -1 && |
132 |
52 |
rcount != 0) { |
|
133 |
ssize_t wcount; |
||
134 |
|||
135 |
8 |
wcount = write(ofd, buf, (size_t)rcount); |
|
136 |
✓✗✗✓ |
16 |
if (-1 == wcount || rcount != wcount) { |
137 |
warn("error writing to %s", target_file); |
||
138 |
goto FAIL; |
||
139 |
} |
||
140 |
✓✗✓ | 8 |
} |
141 |
✗✓ | 44 |
if (rcount == -1) { |
142 |
warn("error reading from %s", source_file); |
||
143 |
goto FAIL; |
||
144 |
} |
||
145 |
|||
146 |
44 |
close(ifd); |
|
147 |
44 |
close(ofd); |
|
148 |
|||
149 |
44 |
return (target_file); |
|
150 |
|||
151 |
FAIL: |
||
152 |
unlink(target_file); |
||
153 |
exit(2); |
||
154 |
264 |
} |
|
155 |
|||
156 |
int |
||
157 |
main(int argc, char **argv) |
||
158 |
{ |
||
159 |
FILE *diffpipe, *file1, *file2; |
||
160 |
size_t diffargc = 0, wflag = WIDTH; |
||
161 |
272 |
int ch, fd[2], status; |
|
162 |
pid_t pid; |
||
163 |
const char *outfile = NULL; |
||
164 |
char **diffargv, *diffprog = "diff", *filename1, *filename2, |
||
165 |
*tmp1, *tmp2, *s1, *s2; |
||
166 |
|||
167 |
✗✓ | 136 |
if (pledge("stdio rpath wpath cpath proc exec flock", NULL) == -1) |
168 |
err(2, "pledge"); |
||
169 |
|||
170 |
/* |
||
171 |
* Process diff flags. |
||
172 |
*/ |
||
173 |
/* |
||
174 |
* Allocate memory for diff arguments and NULL. |
||
175 |
* Each flag has at most one argument, so doubling argc gives an |
||
176 |
* upper limit of how many diff args can be passed. argv[0], |
||
177 |
* file1, and file2 won't have arguments so doubling them will |
||
178 |
* waste some memory; however we need an extra space for the |
||
179 |
* NULL at the end, so it sort of works out. |
||
180 |
*/ |
||
181 |
✗✓ | 136 |
if (!(diffargv = calloc(argc, sizeof(char **) * 2))) |
182 |
err(2, "main"); |
||
183 |
|||
184 |
/* Add first argument, the program name. */ |
||
185 |
136 |
diffargv[diffargc++] = diffprog; |
|
186 |
|||
187 |
✓✓ | 984 |
while ((ch = getopt_long(argc, argv, "aBbdEHI:ilo:stWw:", |
188 |
328 |
longopts, NULL)) != -1) { |
|
189 |
192 |
const char *errstr; |
|
190 |
|||
191 |
✗✗✗✗ ✗✗✗✓ ✗✓✓✗ ✓✗✗✓ ✗ |
192 |
switch (ch) { |
192 |
case 'a': |
||
193 |
diffargv[diffargc++] = "-a"; |
||
194 |
break; |
||
195 |
case 'B': |
||
196 |
diffargv[diffargc++] = "-B"; |
||
197 |
break; |
||
198 |
case 'b': |
||
199 |
diffargv[diffargc++] = "-b"; |
||
200 |
break; |
||
201 |
case 'd': |
||
202 |
diffargv[diffargc++] = "-d"; |
||
203 |
break; |
||
204 |
case 'E': |
||
205 |
diffargv[diffargc++] = "-E"; |
||
206 |
break; |
||
207 |
case 'F': |
||
208 |
diffargv[0] = diffprog = optarg; |
||
209 |
break; |
||
210 |
case 'H': |
||
211 |
diffargv[diffargc++] = "-H"; |
||
212 |
break; |
||
213 |
case 'I': |
||
214 |
32 |
Iflag = 1; |
|
215 |
32 |
diffargv[diffargc++] = "-I"; |
|
216 |
32 |
diffargv[diffargc++] = optarg; |
|
217 |
32 |
break; |
|
218 |
case 'i': |
||
219 |
diffargv[diffargc++] = "-i"; |
||
220 |
break; |
||
221 |
case 'l': |
||
222 |
28 |
lflag = 1; |
|
223 |
28 |
break; |
|
224 |
case 'o': |
||
225 |
48 |
outfile = optarg; |
|
226 |
48 |
break; |
|
227 |
case 'S': |
||
228 |
diffargv[diffargc++] = "--strip-trailing-cr"; |
||
229 |
break; |
||
230 |
case 's': |
||
231 |
36 |
sflag = 1; |
|
232 |
36 |
break; |
|
233 |
case 't': |
||
234 |
diffargv[diffargc++] = "-t"; |
||
235 |
break; |
||
236 |
case 'W': |
||
237 |
diffargv[diffargc++] = "-w"; |
||
238 |
break; |
||
239 |
case 'w': |
||
240 |
48 |
wflag = strtonum(optarg, WIDTH_MIN, |
|
241 |
INT_MAX, &errstr); |
||
242 |
✗✓ | 48 |
if (errstr) |
243 |
errx(2, "width is %s: %s", errstr, optarg); |
||
244 |
break; |
||
245 |
default: |
||
246 |
usage(); |
||
247 |
} |
||
248 |
|||
249 |
192 |
} |
|
250 |
136 |
argc -= optind; |
|
251 |
136 |
argv += optind; |
|
252 |
|||
253 |
✗✓ | 136 |
if (argc != 2) |
254 |
usage(); |
||
255 |
|||
256 |
✓✓✗✓ |
184 |
if (outfile && (outfp = fopen(outfile, "w")) == NULL) |
257 |
err(2, "could not open: %s", optarg); |
||
258 |
|||
259 |
✗✓✗✗ |
136 |
if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0') |
260 |
136 |
tmpdir = _PATH_TMP; |
|
261 |
|||
262 |
136 |
filename1 = argv[0]; |
|
263 |
136 |
filename2 = argv[1]; |
|
264 |
|||
265 |
/* |
||
266 |
* Create temporary files for diff and sdiff to share if file1 |
||
267 |
* or file2 are not regular files. This allows sdiff and diff |
||
268 |
* to read the same inputs if one or both inputs are stdin. |
||
269 |
* |
||
270 |
* If any temporary files were created, their names would be |
||
271 |
* saved in tmp1 or tmp2. tmp1 should never equal tmp2. |
||
272 |
*/ |
||
273 |
tmp1 = tmp2 = NULL; |
||
274 |
/* file1 and file2 are the same, so copy to same temp file. */ |
||
275 |
✓✓ | 272 |
if (strcmp(filename1, filename2) == 0) { |
276 |
✗✓ | 144 |
if ((tmp1 = mktmpcpy(filename1))) |
277 |
filename1 = filename2 = tmp1; |
||
278 |
/* Copy file1 and file2 into separate temp files. */ |
||
279 |
} else { |
||
280 |
✓✓ | 128 |
if ((tmp1 = mktmpcpy(filename1))) |
281 |
20 |
filename1 = tmp1; |
|
282 |
✓✓ | 128 |
if ((tmp2 = mktmpcpy(filename2))) |
283 |
24 |
filename2 = tmp2; |
|
284 |
} |
||
285 |
|||
286 |
136 |
diffargv[diffargc++] = filename1; |
|
287 |
136 |
diffargv[diffargc++] = filename2; |
|
288 |
/* Add NULL to end of array to indicate end of array. */ |
||
289 |
136 |
diffargv[diffargc++] = NULL; |
|
290 |
|||
291 |
/* Subtract column divider and divide by two. */ |
||
292 |
136 |
width = (wflag - 3) / 2; |
|
293 |
/* Make sure line_width can fit in size_t. */ |
||
294 |
✗✓ | 136 |
if (width > (SIZE_MAX - 3) / 2) |
295 |
errx(2, "width is too large: %zu", width); |
||
296 |
136 |
line_width = width * 2 + 3; |
|
297 |
|||
298 |
✗✓ | 136 |
if (pipe(fd)) |
299 |
err(2, "pipe"); |
||
300 |
|||
301 |
✗✗✓ | 136 |
switch(pid = fork()) { |
302 |
case 0: |
||
303 |
/* child */ |
||
304 |
/* We don't read from the pipe. */ |
||
305 |
close(fd[0]); |
||
306 |
if (dup2(fd[1], STDOUT_FILENO) == -1) |
||
307 |
err(2, "child could not duplicate descriptor"); |
||
308 |
/* Free unused descriptor. */ |
||
309 |
close(fd[1]); |
||
310 |
|||
311 |
execvp(diffprog, diffargv); |
||
312 |
err(2, "could not execute diff: %s", diffprog); |
||
313 |
case -1: |
||
314 |
err(2, "could not fork"); |
||
315 |
} |
||
316 |
|||
317 |
/* parent */ |
||
318 |
/* We don't write to the pipe. */ |
||
319 |
136 |
close(fd[1]); |
|
320 |
|||
321 |
/* Open pipe to diff command. */ |
||
322 |
✗✓ | 136 |
if ((diffpipe = fdopen(fd[0], "r")) == NULL) |
323 |
err(2, "could not open diff pipe"); |
||
324 |
✗✓ | 136 |
if ((file1 = fopen(filename1, "r")) == NULL) |
325 |
err(2, "could not open %s", filename1); |
||
326 |
✗✓ | 136 |
if ((file2 = fopen(filename2, "r")) == NULL) |
327 |
err(2, "could not open %s", filename2); |
||
328 |
|||
329 |
/* Line numbers start at one. */ |
||
330 |
136 |
file1ln = file2ln = 1; |
|
331 |
|||
332 |
/* Read and parse diff output. */ |
||
333 |
✓✓ | 896 |
while (parsecmd(diffpipe, file1, file2) != EOF) |
334 |
; |
||
335 |
136 |
fclose(diffpipe); |
|
336 |
|||
337 |
/* Wait for diff to exit. */ |
||
338 |
✓✗✓✗ ✗✓ |
408 |
if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status) || |
339 |
136 |
WEXITSTATUS(status) >= 2) |
|
340 |
err(2, "diff exited abnormally"); |
||
341 |
|||
342 |
/* Delete and free unneeded temporary files. */ |
||
343 |
✓✓ | 136 |
if (tmp1) |
344 |
✗✓ | 20 |
if (unlink(tmp1)) |
345 |
warn("error deleting %s", tmp1); |
||
346 |
✓✓ | 136 |
if (tmp2) |
347 |
✗✓ | 24 |
if (unlink(tmp2)) |
348 |
warn("error deleting %s", tmp2); |
||
349 |
136 |
free(tmp1); |
|
350 |
136 |
free(tmp2); |
|
351 |
filename1 = filename2 = tmp1 = tmp2 = NULL; |
||
352 |
|||
353 |
/* No more diffs, so print common lines. */ |
||
354 |
✓✓ | 136 |
if (lflag) |
355 |
✓✓ | 168 |
while ((s1 = xfgets(file1))) |
356 |
56 |
enqueue(s1, ' ', NULL); |
|
357 |
else |
||
358 |
308 |
for (;;) { |
|
359 |
308 |
s1 = xfgets(file1); |
|
360 |
308 |
s2 = xfgets(file2); |
|
361 |
✓✓ | 308 |
if (s1 || s2) |
362 |
200 |
enqueue(s1, ' ', s2); |
|
363 |
else |
||
364 |
break; |
||
365 |
} |
||
366 |
136 |
fclose(file1); |
|
367 |
136 |
fclose(file2); |
|
368 |
/* Process unmodified lines. */ |
||
369 |
136 |
processq(); |
|
370 |
|||
371 |
/* Return diff exit status. */ |
||
372 |
272 |
return (WEXITSTATUS(status)); |
|
373 |
136 |
} |
|
374 |
|||
375 |
/* |
||
376 |
* Prints an individual column (left or right), taking into account |
||
377 |
* that tabs are variable-width. Takes a string, the current column |
||
378 |
* the cursor is on the screen, and the maximum value of the column. |
||
379 |
* The column value is updated as we go along. |
||
380 |
*/ |
||
381 |
static void |
||
382 |
printcol(const char *s, size_t *col, const size_t col_max) |
||
383 |
{ |
||
384 |
✓✓✓✓ |
38788 |
for (; *s && *col < col_max; ++s) { |
385 |
size_t new_col; |
||
386 |
|||
387 |
✓✓ | 11784 |
switch (*s) { |
388 |
case '\t': |
||
389 |
/* |
||
390 |
* If rounding to next multiple of eight causes |
||
391 |
* an integer overflow, just return. |
||
392 |
*/ |
||
393 |
✗✓ | 280 |
if (*col > SIZE_MAX - 8) |
394 |
return; |
||
395 |
|||
396 |
/* Round to next multiple of eight. */ |
||
397 |
280 |
new_col = (*col / 8 + 1) * 8; |
|
398 |
|||
399 |
/* |
||
400 |
* If printing the tab goes past the column |
||
401 |
* width, don't print it and just quit. |
||
402 |
*/ |
||
403 |
✓✓ | 280 |
if (new_col > col_max) |
404 |
104 |
return; |
|
405 |
176 |
*col = new_col; |
|
406 |
176 |
break; |
|
407 |
|||
408 |
default: |
||
409 |
11504 |
++(*col); |
|
410 |
11504 |
} |
|
411 |
|||
412 |
✓✗ | 23360 |
putchar(*s); |
413 |
✓✓ | 11680 |
} |
414 |
1152 |
} |
|
415 |
|||
416 |
/* |
||
417 |
* Prompts user to either choose between two strings or edit one, both, |
||
418 |
* or neither. |
||
419 |
*/ |
||
420 |
static void |
||
421 |
prompt(const char *s1, const char *s2) |
||
422 |
{ |
||
423 |
char *cmd; |
||
424 |
|||
425 |
/* Print command prompt. */ |
||
426 |
✓✗ | 432 |
putchar('%'); |
427 |
|||
428 |
/* Get user input. */ |
||
429 |
✓✗ | 336 |
for (; (cmd = xfgets(stdin)); free(cmd)) { |
430 |
const char *p; |
||
431 |
|||
432 |
/* Skip leading whitespace. */ |
||
433 |
✗✓ | 336 |
for (p = cmd; isspace((unsigned char)*p); ++p) |
434 |
; |
||
435 |
|||
436 |
✓✗✓✗ ✗✓✓✓ ✓ |
176 |
switch (*p) { |
437 |
case 'e': |
||
438 |
/* Skip `e'. */ |
||
439 |
24 |
++p; |
|
440 |
|||
441 |
✗✓ | 24 |
if (eparse(p, s1, s2) == -1) |
442 |
goto USAGE; |
||
443 |
break; |
||
444 |
|||
445 |
case 'l': |
||
446 |
case '1': |
||
447 |
/* Choose left column as-is. */ |
||
448 |
✓✓ | 60 |
if (s1 != NULL) |
449 |
40 |
fprintf(outfp, "%s\n", s1); |
|
450 |
|||
451 |
/* End of command parsing. */ |
||
452 |
break; |
||
453 |
|||
454 |
case 'q': |
||
455 |
goto QUIT; |
||
456 |
|||
457 |
case 'r': |
||
458 |
case '2': |
||
459 |
/* Choose right column as-is. */ |
||
460 |
✓✓ | 60 |
if (s2 != NULL) |
461 |
40 |
fprintf(outfp, "%s\n", s2); |
|
462 |
|||
463 |
/* End of command parsing. */ |
||
464 |
break; |
||
465 |
|||
466 |
case 's': |
||
467 |
16 |
sflag = 1; |
|
468 |
16 |
goto PROMPT; |
|
469 |
|||
470 |
case 'v': |
||
471 |
8 |
sflag = 0; |
|
472 |
/* FALLTHROUGH */ |
||
473 |
|||
474 |
default: |
||
475 |
/* Interactive usage help. */ |
||
476 |
USAGE: |
||
477 |
8 |
int_usage(); |
|
478 |
PROMPT: |
||
479 |
✓✗ | 48 |
putchar('%'); |
480 |
|||
481 |
/* Prompt user again. */ |
||
482 |
24 |
continue; |
|
483 |
} |
||
484 |
|||
485 |
144 |
free(cmd); |
|
486 |
✓✗✓ | 144 |
return; |
487 |
} |
||
488 |
|||
489 |
/* |
||
490 |
* If there was no error, we received an EOF from stdin, so we |
||
491 |
* should quit. |
||
492 |
*/ |
||
493 |
QUIT: |
||
494 |
fclose(outfp); |
||
495 |
exit(0); |
||
496 |
144 |
} |
|
497 |
|||
498 |
/* |
||
499 |
* Takes two strings, separated by a column divider. NULL strings are |
||
500 |
* treated as empty columns. If the divider is the ` ' character, the |
||
501 |
* second column is not printed (-l flag). In this case, the second |
||
502 |
* string must be NULL. When the second column is NULL, the divider |
||
503 |
* does not print the trailing space following the divider character. |
||
504 |
* |
||
505 |
* Takes into account that tabs can take multiple columns. |
||
506 |
*/ |
||
507 |
static void |
||
508 |
println(const char *s1, const char div, const char *s2) |
||
509 |
{ |
||
510 |
1664 |
size_t col; |
|
511 |
|||
512 |
/* Print first column. Skips if s1 == NULL. */ |
||
513 |
832 |
col = 0; |
|
514 |
✓✓ | 832 |
if (s1) { |
515 |
/* Skip angle bracket and space. */ |
||
516 |
660 |
printcol(s1, &col, width); |
|
517 |
|||
518 |
660 |
} |
|
519 |
|||
520 |
/* Only print left column. */ |
||
521 |
✓✓ | 832 |
if (div == ' ' && !s2) { |
522 |
✓✗ | 152 |
putchar('\n'); |
523 |
76 |
return; |
|
524 |
} |
||
525 |
|||
526 |
/* Otherwise, we pad this column up to width. */ |
||
527 |
✓✓ | 59568 |
for (; col < width; ++col) |
528 |
✓✗ | 58056 |
putchar(' '); |
529 |
|||
530 |
/* |
||
531 |
* Print column divider. If there is no second column, we don't |
||
532 |
* need to add the space for padding. |
||
533 |
*/ |
||
534 |
✓✓ | 756 |
if (!s2) { |
535 |
264 |
printf(" %c\n", div); |
|
536 |
264 |
return; |
|
537 |
} |
||
538 |
492 |
printf(" %c ", div); |
|
539 |
492 |
col += 3; |
|
540 |
|||
541 |
/* Skip angle bracket and space. */ |
||
542 |
492 |
printcol(s2, &col, line_width); |
|
543 |
|||
544 |
✓✗ | 984 |
putchar('\n'); |
545 |
1324 |
} |
|
546 |
|||
547 |
/* |
||
548 |
* Reads a line from file and returns as a string. If EOF is reached, |
||
549 |
* NULL is returned. The returned string must be freed afterwards. |
||
550 |
*/ |
||
551 |
static char * |
||
552 |
xfgets(FILE *file) |
||
553 |
{ |
||
554 |
const char delim[3] = {'\0', '\0', '\0'}; |
||
555 |
char *s; |
||
556 |
|||
557 |
/* XXX - Is this necessary? */ |
||
558 |
✓✗ | 9456 |
clearerr(file); |
559 |
|||
560 |
✓✓✓✗ ✗✗ |
3532 |
if (!(s = fparseln(file, NULL, NULL, delim, 0)) && |
561 |
✗✓ | 760 |
ferror(file)) |
562 |
err(2, "error reading file"); |
||
563 |
|||
564 |
✓✓ | 3152 |
if (!s) { |
565 |
380 |
return (NULL); |
|
566 |
} |
||
567 |
|||
568 |
2772 |
return (s); |
|
569 |
3152 |
} |
|
570 |
|||
571 |
/* |
||
572 |
* Parse ed commands from diffpipe and print lines from file1 (lines |
||
573 |
* to change or delete) or file2 (lines to add or change). |
||
574 |
* Returns EOF or 0. |
||
575 |
*/ |
||
576 |
static int |
||
577 |
parsecmd(FILE *diffpipe, FILE *file1, FILE *file2) |
||
578 |
{ |
||
579 |
size_t file1start, file1end, file2start, file2end, n; |
||
580 |
/* ed command line and pointer to characters in line */ |
||
581 |
char *line, *p, *q; |
||
582 |
896 |
const char *errstr; |
|
583 |
char c, cmd; |
||
584 |
|||
585 |
/* Read ed command. */ |
||
586 |
✓✓ | 448 |
if (!(line = xfgets(diffpipe))) |
587 |
136 |
return (EOF); |
|
588 |
|||
589 |
p = line; |
||
590 |
/* Go to character after line number. */ |
||
591 |
✓✓ | 1248 |
while (isdigit((unsigned char)*p)) |
592 |
312 |
++p; |
|
593 |
c = *p; |
||
594 |
312 |
*p++ = 0; |
|
595 |
312 |
file1start = strtonum(line, 0, INT_MAX, &errstr); |
|
596 |
✗✓ | 312 |
if (errstr) |
597 |
errx(2, "file1 start is %s: %s", errstr, line); |
||
598 |
|||
599 |
/* A range is specified for file1. */ |
||
600 |
✓✓ | 312 |
if (c == ',') { |
601 |
|||
602 |
q = p; |
||
603 |
/* Go to character after file2end. */ |
||
604 |
✓✓ | 48 |
while (isdigit((unsigned char)*p)) |
605 |
16 |
++p; |
|
606 |
c = *p; |
||
607 |
8 |
*p++ = 0; |
|
608 |
8 |
file1end = strtonum(q, 0, INT_MAX, &errstr); |
|
609 |
✗✓ | 8 |
if (errstr) |
610 |
errx(2, "file1 end is %s: %s", errstr, line); |
||
611 |
✗✓ | 8 |
if (file1start > file1end) |
612 |
errx(2, "invalid line range in file1: %s", line); |
||
613 |
|||
614 |
} else |
||
615 |
file1end = file1start; |
||
616 |
|||
617 |
cmd = c; |
||
618 |
/* Check that cmd is valid. */ |
||
619 |
✓✓✓✓ ✗✓ |
628 |
if (!(cmd == 'a' || cmd == 'c' || cmd == 'd')) |
620 |
errx(2, "ed command not recognized: %c: %s", cmd, line); |
||
621 |
|||
622 |
q = p; |
||
623 |
/* Go to character after line number. */ |
||
624 |
✓✓ | 1248 |
while (isdigit((unsigned char)*p)) |
625 |
312 |
++p; |
|
626 |
c = *p; |
||
627 |
312 |
*p++ = 0; |
|
628 |
312 |
file2start = strtonum(q, 0, INT_MAX, &errstr); |
|
629 |
✗✓ | 312 |
if (errstr) |
630 |
errx(2, "file2 start is %s: %s", errstr, line); |
||
631 |
|||
632 |
/* |
||
633 |
* There should either be a comma signifying a second line |
||
634 |
* number or the line should just end here. |
||
635 |
*/ |
||
636 |
✓✓✗✓ |
620 |
if (c != ',' && c != '\0') |
637 |
errx(2, "invalid line range in file2: %c: %s", c, line); |
||
638 |
|||
639 |
✓✓ | 312 |
if (c == ',') { |
640 |
|||
641 |
4 |
file2end = strtonum(p, 0, INT_MAX, &errstr); |
|
642 |
✗✓ | 4 |
if (errstr) |
643 |
errx(2, "file2 end is %s: %s", errstr, line); |
||
644 |
✗✓ | 4 |
if (file2start >= file2end) |
645 |
errx(2, "invalid line range in file2: %s", line); |
||
646 |
} else |
||
647 |
file2end = file2start; |
||
648 |
|||
649 |
/* Appends happen _after_ stated line. */ |
||
650 |
✓✓ | 312 |
if (cmd == 'a') { |
651 |
✗✓ | 108 |
if (file1start != file1end) |
652 |
errx(2, "append cannot have a file1 range: %s", |
||
653 |
line); |
||
654 |
✗✓ | 108 |
if (file1start == SIZE_MAX) |
655 |
errx(2, "file1 line range too high: %s", line); |
||
656 |
108 |
file1start = ++file1end; |
|
657 |
108 |
} |
|
658 |
/* |
||
659 |
* I'm not sure what the deal is with the line numbers for |
||
660 |
* deletes, though. |
||
661 |
*/ |
||
662 |
✓✓ | 204 |
else if (cmd == 'd') { |
663 |
✗✓ | 112 |
if (file2start != file2end) |
664 |
errx(2, "delete cannot have a file2 range: %s", |
||
665 |
line); |
||
666 |
✗✓ | 112 |
if (file2start == SIZE_MAX) |
667 |
errx(2, "file2 line range too high: %s", line); |
||
668 |
112 |
file2start = ++file2end; |
|
669 |
112 |
} |
|
670 |
|||
671 |
/* |
||
672 |
* Continue reading file1 and file2 until we reach line numbers |
||
673 |
* specified by diff. Should only happen with -I flag. |
||
674 |
*/ |
||
675 |
✓✓✓✗ |
1452 |
for (; file1ln < file1start && file2ln < file2start; |
676 |
276 |
++file1ln, ++file2ln) { |
|
677 |
char *s1, *s2; |
||
678 |
|||
679 |
✗✓ | 276 |
if (!(s1 = xfgets(file1))) |
680 |
errx(2, "file1 shorter than expected"); |
||
681 |
✗✓ | 276 |
if (!(s2 = xfgets(file2))) |
682 |
errx(2, "file2 shorter than expected"); |
||
683 |
|||
684 |
/* If the -l flag was specified, print only left column. */ |
||
685 |
✓✓ | 276 |
if (lflag) { |
686 |
84 |
free(s2); |
|
687 |
/* |
||
688 |
* XXX - If -l and -I are both specified, all |
||
689 |
* unchanged or ignored lines are shown with a |
||
690 |
* `(' divider. This matches GNU sdiff, but I |
||
691 |
* believe it is a bug. Just check out: |
||
692 |
* gsdiff -l -I '^$' samefile samefile. |
||
693 |
*/ |
||
694 |
✓✓ | 84 |
if (Iflag) |
695 |
24 |
enqueue(s1, '(', NULL); |
|
696 |
else |
||
697 |
60 |
enqueue(s1, ' ', NULL); |
|
698 |
} else |
||
699 |
192 |
enqueue(s1, ' ', s2); |
|
700 |
} |
||
701 |
/* Ignore deleted lines. */ |
||
702 |
✗✓ | 624 |
for (; file1ln < file1start; ++file1ln) { |
703 |
char *s; |
||
704 |
|||
705 |
if (!(s = xfgets(file1))) |
||
706 |
errx(2, "file1 shorter than expected"); |
||
707 |
|||
708 |
enqueue(s, '(', NULL); |
||
709 |
} |
||
710 |
/* Ignore added lines. */ |
||
711 |
✗✓ | 624 |
for (; file2ln < file2start; ++file2ln) { |
712 |
char *s; |
||
713 |
|||
714 |
if (!(s = xfgets(file2))) |
||
715 |
errx(2, "file2 shorter than expected"); |
||
716 |
|||
717 |
/* If -l flag was given, don't print right column. */ |
||
718 |
if (lflag) |
||
719 |
free(s); |
||
720 |
else |
||
721 |
enqueue(NULL, ')', s); |
||
722 |
} |
||
723 |
|||
724 |
/* Process unmodified or skipped lines. */ |
||
725 |
312 |
processq(); |
|
726 |
|||
727 |
✓✓✓✗ |
312 |
switch (cmd) { |
728 |
case 'a': |
||
729 |
108 |
printa(file2, file2end); |
|
730 |
108 |
n = file2end - file2start + 1; |
|
731 |
108 |
break; |
|
732 |
|||
733 |
case 'c': |
||
734 |
92 |
printc(file1, file1end, file2, file2end); |
|
735 |
92 |
n = file1end - file1start + 1 + 1 + file2end - file2start + 1; |
|
736 |
92 |
break; |
|
737 |
|||
738 |
case 'd': |
||
739 |
112 |
printd(file1, file1end); |
|
740 |
112 |
n = file1end - file1start + 1; |
|
741 |
112 |
break; |
|
742 |
|||
743 |
default: |
||
744 |
errx(2, "invalid diff command: %c: %s", cmd, line); |
||
745 |
} |
||
746 |
312 |
free(line); |
|
747 |
|||
748 |
/* Skip to next ed line. */ |
||
749 |
✓✓ | 2000 |
while (n--) { |
750 |
✗✓ | 688 |
if (!(line = xfgets(diffpipe))) |
751 |
errx(2, "diff ended early"); |
||
752 |
688 |
free(line); |
|
753 |
} |
||
754 |
|||
755 |
312 |
return (0); |
|
756 |
448 |
} |
|
757 |
|||
758 |
/* |
||
759 |
* Queues up a diff line. |
||
760 |
*/ |
||
761 |
static void |
||
762 |
enqueue(char *left, char div, char *right) |
||
763 |
{ |
||
764 |
struct diffline *diffp; |
||
765 |
|||
766 |
✗✓ | 2072 |
if (!(diffp = malloc(sizeof(struct diffline)))) |
767 |
err(2, "enqueue"); |
||
768 |
1036 |
diffp->left = left; |
|
769 |
1036 |
diffp->div = div; |
|
770 |
1036 |
diffp->right = right; |
|
771 |
1036 |
SIMPLEQ_INSERT_TAIL(&diffhead, diffp, diffentries); |
|
772 |
1036 |
} |
|
773 |
|||
774 |
/* |
||
775 |
* Free a diffline structure and its elements. |
||
776 |
*/ |
||
777 |
static void |
||
778 |
freediff(struct diffline *diffp) |
||
779 |
{ |
||
780 |
2072 |
free(diffp->left); |
|
781 |
1036 |
free(diffp->right); |
|
782 |
1036 |
free(diffp); |
|
783 |
1036 |
} |
|
784 |
|||
785 |
/* |
||
786 |
* Append second string into first. Repeated appends to the same string |
||
787 |
* are cached, making this an O(n) function, where n = strlen(append). |
||
788 |
*/ |
||
789 |
static void |
||
790 |
astrcat(char **s, const char *append) |
||
791 |
{ |
||
792 |
/* Length of string in previous run. */ |
||
793 |
static size_t offset = 0; |
||
794 |
size_t newsiz; |
||
795 |
/* |
||
796 |
* String from previous run. Compared to *s to see if we are |
||
797 |
* dealing with the same string. If so, we can use offset. |
||
798 |
*/ |
||
799 |
static const char *oldstr = NULL; |
||
800 |
char *newstr; |
||
801 |
|||
802 |
|||
803 |
/* |
||
804 |
* First string is NULL, so just copy append. |
||
805 |
*/ |
||
806 |
✓✓ | 3040 |
if (!*s) { |
807 |
✗✓ | 1044 |
if (!(*s = strdup(append))) |
808 |
err(2, "astrcat"); |
||
809 |
|||
810 |
/* Keep track of string. */ |
||
811 |
1044 |
offset = strlen(*s); |
|
812 |
1044 |
oldstr = *s; |
|
813 |
|||
814 |
1044 |
return; |
|
815 |
} |
||
816 |
|||
817 |
/* |
||
818 |
* *s is a string so concatenate. |
||
819 |
*/ |
||
820 |
|||
821 |
/* Did we process the same string in the last run? */ |
||
822 |
/* |
||
823 |
* If this is a different string from the one we just processed |
||
824 |
* cache new string. |
||
825 |
*/ |
||
826 |
✓✓ | 476 |
if (oldstr != *s) { |
827 |
256 |
offset = strlen(*s); |
|
828 |
256 |
oldstr = *s; |
|
829 |
256 |
} |
|
830 |
|||
831 |
/* Size = strlen(*s) + \n + strlen(append) + '\0'. */ |
||
832 |
476 |
newsiz = offset + 1 + strlen(append) + 1; |
|
833 |
|||
834 |
/* Resize *s to fit new string. */ |
||
835 |
476 |
newstr = realloc(*s, newsiz); |
|
836 |
✗✓ | 476 |
if (newstr == NULL) |
837 |
err(2, "astrcat"); |
||
838 |
476 |
*s = newstr; |
|
839 |
|||
840 |
/* *s + offset should be end of string. */ |
||
841 |
/* Concatenate. */ |
||
842 |
476 |
strlcpy(*s + offset, "\n", newsiz - offset); |
|
843 |
476 |
strlcat(*s + offset, append, newsiz - offset); |
|
844 |
|||
845 |
/* New string length should be exactly newsiz - 1 characters. */ |
||
846 |
/* Store generated string's values. */ |
||
847 |
476 |
offset = newsiz - 1; |
|
848 |
476 |
oldstr = *s; |
|
849 |
1996 |
} |
|
850 |
|||
851 |
/* |
||
852 |
* Process diff set queue, printing, prompting, and saving each diff |
||
853 |
* line stored in queue. |
||
854 |
*/ |
||
855 |
static void |
||
856 |
processq(void) |
||
857 |
{ |
||
858 |
struct diffline *diffp; |
||
859 |
1888 |
char divc, *left, *right; |
|
860 |
|||
861 |
/* Don't process empty queue. */ |
||
862 |
✓✓ | 944 |
if (SIMPLEQ_EMPTY(&diffhead)) |
863 |
256 |
return; |
|
864 |
|||
865 |
/* Remember the divider. */ |
||
866 |
688 |
divc = SIMPLEQ_FIRST(&diffhead)->div; |
|
867 |
|||
868 |
688 |
left = NULL; |
|
869 |
688 |
right = NULL; |
|
870 |
/* |
||
871 |
* Go through set of diffs, concatenating each line in left or |
||
872 |
* right column into two long strings, `left' and `right'. |
||
873 |
*/ |
||
874 |
✓✓ | 3448 |
SIMPLEQ_FOREACH(diffp, &diffhead, diffentries) { |
875 |
/* |
||
876 |
* Print changed lines if -s was given, |
||
877 |
* print all lines if -s was not given. |
||
878 |
*/ |
||
879 |
✓✓✓✓ ✓✓✓✓ |
1880 |
if (!sflag || diffp->div == '|' || diffp->div == '<' || |
880 |
240 |
diffp->div == '>') |
|
881 |
832 |
println(diffp->left, diffp->div, diffp->right); |
|
882 |
|||
883 |
/* Append new lines to diff set. */ |
||
884 |
✓✓ | 1036 |
if (diffp->left) |
885 |
864 |
astrcat(&left, diffp->left); |
|
886 |
✓✓ | 1036 |
if (diffp->right) |
887 |
656 |
astrcat(&right, diffp->right); |
|
888 |
} |
||
889 |
|||
890 |
/* Empty queue and free each diff line and its elements. */ |
||
891 |
✓✓ | 3448 |
while (!SIMPLEQ_EMPTY(&diffhead)) { |
892 |
diffp = SIMPLEQ_FIRST(&diffhead); |
||
893 |
✓✓ | 1724 |
SIMPLEQ_REMOVE_HEAD(&diffhead, diffentries); |
894 |
1036 |
freediff(diffp); |
|
895 |
} |
||
896 |
|||
897 |
/* Write to outfp, prompting user if lines are different. */ |
||
898 |
✓✓ | 688 |
if (outfp) |
899 |
✗✗✓✗ ✗✓✗ |
336 |
switch (divc) { |
900 |
case ' ': case '(': case ')': |
||
901 |
192 |
fprintf(outfp, "%s\n", left); |
|
902 |
192 |
break; |
|
903 |
case '|': case '<': case '>': |
||
904 |
144 |
prompt(left, right); |
|
905 |
144 |
break; |
|
906 |
default: |
||
907 |
errx(2, "invalid divider: %c", divc); |
||
908 |
} |
||
909 |
|||
910 |
/* Free left and right. */ |
||
911 |
688 |
free(left); |
|
912 |
688 |
free(right); |
|
913 |
1632 |
} |
|
914 |
|||
915 |
/* |
||
916 |
* Print lines following an (a)ppend command. |
||
917 |
*/ |
||
918 |
static void |
||
919 |
printa(FILE *file, size_t line2) |
||
920 |
{ |
||
921 |
char *line; |
||
922 |
|||
923 |
✓✓ | 668 |
for (; file2ln <= line2; ++file2ln) { |
924 |
✗✓ | 172 |
if (!(line = xfgets(file))) |
925 |
errx(2, "append ended early"); |
||
926 |
172 |
enqueue(NULL, '>', line); |
|
927 |
} |
||
928 |
|||
929 |
108 |
processq(); |
|
930 |
108 |
} |
|
931 |
|||
932 |
/* |
||
933 |
* Print lines following a (c)hange command, from file1ln to file1end |
||
934 |
* and from file2ln to file2end. |
||
935 |
*/ |
||
936 |
static void |
||
937 |
printc(FILE *file1, size_t file1end, FILE *file2, size_t file2end) |
||
938 |
{ |
||
939 |
struct fileline { |
||
940 |
SIMPLEQ_ENTRY(fileline) fileentries; |
||
941 |
char *line; |
||
942 |
}; |
||
943 |
184 |
SIMPLEQ_HEAD(, fileline) delqhead = SIMPLEQ_HEAD_INITIALIZER(delqhead); |
|
944 |
|||
945 |
/* Read lines to be deleted. */ |
||
946 |
✓✓ | 368 |
for (; file1ln <= file1end; ++file1ln) { |
947 |
struct fileline *linep; |
||
948 |
char *line1; |
||
949 |
|||
950 |
/* Read lines from both. */ |
||
951 |
✗✓ | 92 |
if (!(line1 = xfgets(file1))) |
952 |
errx(2, "error reading file1 in delete in change"); |
||
953 |
|||
954 |
/* Add to delete queue. */ |
||
955 |
✗✓ | 92 |
if (!(linep = malloc(sizeof(struct fileline)))) |
956 |
err(2, "printc"); |
||
957 |
92 |
linep->line = line1; |
|
958 |
92 |
SIMPLEQ_INSERT_TAIL(&delqhead, linep, fileentries); |
|
959 |
} |
||
960 |
|||
961 |
/* Process changed lines.. */ |
||
962 |
✓✓✓✗ |
460 |
for (; !SIMPLEQ_EMPTY(&delqhead) && file2ln <= file2end; |
963 |
92 |
++file2ln) { |
|
964 |
struct fileline *del; |
||
965 |
char *add; |
||
966 |
|||
967 |
/* Get add line. */ |
||
968 |
✗✓ | 92 |
if (!(add = xfgets(file2))) |
969 |
errx(2, "error reading add in change"); |
||
970 |
|||
971 |
92 |
del = SIMPLEQ_FIRST(&delqhead); |
|
972 |
92 |
enqueue(del->line, '|', add); |
|
973 |
✓✗ | 184 |
SIMPLEQ_REMOVE_HEAD(&delqhead, fileentries); |
974 |
/* |
||
975 |
* Free fileline structure but not its elements since |
||
976 |
* they are queued up. |
||
977 |
*/ |
||
978 |
92 |
free(del); |
|
979 |
} |
||
980 |
92 |
processq(); |
|
981 |
|||
982 |
/* Process remaining lines to add. */ |
||
983 |
✗✓ | 184 |
for (; file2ln <= file2end; ++file2ln) { |
984 |
char *add; |
||
985 |
|||
986 |
/* Get add line. */ |
||
987 |
if (!(add = xfgets(file2))) |
||
988 |
errx(2, "error reading add in change"); |
||
989 |
|||
990 |
enqueue(NULL, '>', add); |
||
991 |
} |
||
992 |
92 |
processq(); |
|
993 |
|||
994 |
/* Process remaining lines to delete. */ |
||
995 |
✗✓ | 184 |
while (!SIMPLEQ_EMPTY(&delqhead)) { |
996 |
struct fileline *filep; |
||
997 |
|||
998 |
filep = SIMPLEQ_FIRST(&delqhead); |
||
999 |
enqueue(filep->line, '<', NULL); |
||
1000 |
SIMPLEQ_REMOVE_HEAD(&delqhead, fileentries); |
||
1001 |
free(filep); |
||
1002 |
} |
||
1003 |
92 |
processq(); |
|
1004 |
92 |
} |
|
1005 |
|||
1006 |
/* |
||
1007 |
* Print deleted lines from file, from file1ln to file1end. |
||
1008 |
*/ |
||
1009 |
static void |
||
1010 |
printd(FILE *file1, size_t file1end) |
||
1011 |
{ |
||
1012 |
char *line1; |
||
1013 |
|||
1014 |
/* Print out lines file1ln to line2. */ |
||
1015 |
✓✓ | 816 |
for (; file1ln <= file1end; ++file1ln) { |
1016 |
✗✓ | 240 |
if (!(line1 = xfgets(file1))) |
1017 |
errx(2, "file1 ended early in delete"); |
||
1018 |
240 |
enqueue(line1, '<', NULL); |
|
1019 |
} |
||
1020 |
112 |
processq(); |
|
1021 |
112 |
} |
|
1022 |
|||
1023 |
/* |
||
1024 |
* Interactive mode usage. |
||
1025 |
*/ |
||
1026 |
static void |
||
1027 |
int_usage(void) |
||
1028 |
{ |
||
1029 |
16 |
puts("e:\tedit blank diff\n" |
|
1030 |
"eb:\tedit both diffs concatenated\n" |
||
1031 |
"el:\tedit left diff\n" |
||
1032 |
"er:\tedit right diff\n" |
||
1033 |
"l | 1:\tchoose left diff\n" |
||
1034 |
"r | 2:\tchoose right diff\n" |
||
1035 |
"s:\tsilent mode--don't print identical lines\n" |
||
1036 |
"v:\tverbose mode--print identical lines\n" |
||
1037 |
"q:\tquit"); |
||
1038 |
8 |
} |
|
1039 |
|||
1040 |
static void |
||
1041 |
usage(void) |
||
1042 |
{ |
||
1043 |
extern char *__progname; |
||
1044 |
|||
1045 |
fprintf(stderr, |
||
1046 |
"usage: %s [-abdilstW] [-I regexp] [-o outfile] [-w width] file1 file2\n", |
||
1047 |
__progname); |
||
1048 |
exit(2); |
||
1049 |
} |
Generated by: GCOVR (Version 3.3) |