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