Bug Summary

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 **'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.0 -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name sdiff.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 1 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -fno-rounding-math -mconstructor-aliases -munwind-tables -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/usr/src/usr.bin/sdiff/obj -resource-dir /usr/local/lib/clang/13.0.0 -internal-isystem /usr/local/lib/clang/13.0.0/include -internal-externc-isystem /usr/include -O2 -Wno-sign-compare -fdebug-compilation-dir=/usr/src/usr.bin/sdiff/obj -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/ben/Projects/vmm/scan-build/2022-01-12-194120-40624-1 -x c /usr/src/usr.bin/sdiff/sdiff.c
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. */
38struct diffline {
39 SIMPLEQ_ENTRY(diffline)struct { struct diffline *sqe_next; } diffentries;
40 char *left;
41 char div;
42 char *right;
43};
44
45static void astrcat(char **, const char *);
46static void enqueue(char *, char, char *);
47static char *mktmpcpy(const char *);
48static void freediff(struct diffline *);
49static void int_usage(void);
50static int parsecmd(FILE *, FILE *, FILE *);
51static void printa(FILE *, size_t);
52static void printc(FILE *, size_t, FILE *, size_t);
53static void printcol(const char *, size_t *, const size_t);
54static void printd(FILE *, size_t);
55static void println(const char *, const char, const char *);
56static void processq(void);
57static void prompt(const char *, const char *);
58__dead__attribute__((__noreturn__)) static void usage(void);
59static char *xfgets(FILE *);
60
61SIMPLEQ_HEAD(, diffline)struct { struct diffline *sqh_first; struct diffline **sqh_last
; }
diffhead = SIMPLEQ_HEAD_INITIALIZER(diffhead){ ((void *)0), &(diffhead).sqh_first };
62size_t line_width; /* width of a line (two columns and divider) */
63size_t width; /* width of each column */
64size_t file1ln, file2ln; /* line number of file1 and file2 */
65int Iflag = 0; /* ignore sets matching regexp */
66int lflag; /* print only left column for identical lines */
67int sflag; /* skip identical lines */
68FILE *outfp; /* file to save changes to */
69const char *tmpdir; /* TMPDIR or /tmp */
70
71static 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 */
95static char *
96mktmpcpy(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
151FAIL:
152 unlink(target_file);
153 exit(2);
154}
155
156int
157main(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 */
395static void
396printcol(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 */
434static void
435prompt(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. */
490USAGE:
491 int_usage();
492PROMPT:
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 */
507QUIT:
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 */
521static void
522println(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 */
565static char *
566xfgets(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 */
590static int
591parsecmd(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 */
775static void
776enqueue(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 */
791static void
792freediff(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 */
803static void
804astrcat(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 */
869static void
870processq(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 */
932static void
933printa(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 */
950static void
951printc(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 */
1023static void
1024printd(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 */
1040static void
1041int_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
1054static void
1055usage(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}