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