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