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