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