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