GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/sdiff/sdiff.c Lines: 299 402 74.4 %
Date: 2017-11-07 Branches: 204 316 64.6 %

Line Branch Exec Source
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
924
	struct stat sb;
99
	ssize_t rcount;
100
	int ifd, ofd;
101
462
	u_char buf[BUFSIZ];
102
462
	char *target_file;
103
104
	/* Open input and output. */
105
462
	ifd = open(source_file, O_RDONLY, 0);
106
	/* File was opened successfully. */
107
462
	if (ifd != -1) {
108
462
		if (fstat(ifd, &sb) == -1)
109
			err(2, "error getting file status from %s", source_file);
110
111
		/* Regular file. */
112
462
		if (S_ISREG(sb.st_mode)) {
113
385
			close(ifd);
114
385
			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
77
	if (asprintf(&target_file, "%s/sdiff.XXXXXXXXXX", tmpdir) == -1)
126
		err(2, "asprintf");
127
77
	if ((ofd = mkstemp(target_file)) == -1) {
128
		warn("error opening %s", target_file);
129
		goto FAIL;
130
	}
131
182
	while ((rcount = read(ifd, buf, sizeof(buf))) != -1 &&
132
91
	    rcount != 0) {
133
		ssize_t wcount;
134
135
14
		wcount = write(ofd, buf, (size_t)rcount);
136

28
		if (-1 == wcount || rcount != wcount) {
137
			warn("error writing to %s", target_file);
138
			goto FAIL;
139
		}
140
14
	}
141
77
	if (rcount == -1) {
142
		warn("error reading from %s", source_file);
143
		goto FAIL;
144
	}
145
146
77
	close(ifd);
147
77
	close(ofd);
148
149
77
	return (target_file);
150
151
FAIL:
152
	unlink(target_file);
153
	exit(2);
154
462
}
155
156
int
157
main(int argc, char **argv)
158
{
159
	FILE *diffpipe, *file1, *file2;
160
	size_t diffargc = 0, wflag = WIDTH;
161
476
	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
238
	if (pledge("stdio rpath wpath cpath proc exec flock", 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
238
	if (!(diffargv = calloc(argc, sizeof(char **) * 2)))
182
		err(2, "main");
183
184
	/* Add first argument, the program name. */
185
238
	diffargv[diffargc++] = diffprog;
186
187
1722
	while ((ch = getopt_long(argc, argv, "aBbdEHI:ilo:stWw:",
188
574
	    longopts, NULL)) != -1) {
189
336
		const char *errstr;
190
191




336
		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
56
			Iflag = 1;
215
56
			diffargv[diffargc++] = "-I";
216
56
			diffargv[diffargc++] = optarg;
217
56
			break;
218
		case 'i':
219
			diffargv[diffargc++] = "-i";
220
			break;
221
		case 'l':
222
49
			lflag = 1;
223
49
			break;
224
		case 'o':
225
84
			outfile = optarg;
226
84
			break;
227
		case 'S':
228
			diffargv[diffargc++] = "--strip-trailing-cr";
229
			break;
230
		case 's':
231
63
			sflag = 1;
232
63
			break;
233
		case 't':
234
			diffargv[diffargc++] = "-t";
235
			break;
236
		case 'W':
237
			diffargv[diffargc++] = "-w";
238
			break;
239
		case 'w':
240
84
			wflag = strtonum(optarg, WIDTH_MIN,
241
			    INT_MAX, &errstr);
242
84
			if (errstr)
243
				errx(2, "width is %s: %s", errstr, optarg);
244
			break;
245
		default:
246
			usage();
247
		}
248
249
336
	}
250
238
	argc -= optind;
251
238
	argv += optind;
252
253
238
	if (argc != 2)
254
		usage();
255
256

322
	if (outfile && (outfp = fopen(outfile, "w")) == NULL)
257
		err(2, "could not open: %s", optarg);
258
259

238
	if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0')
260
238
		tmpdir = _PATH_TMP;
261
262
238
	filename1 = argv[0];
263
238
	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
476
	if (strcmp(filename1, filename2) == 0) {
276
252
		if ((tmp1 = mktmpcpy(filename1)))
277
			filename1 = filename2 = tmp1;
278
	/* Copy file1 and file2 into separate temp files. */
279
	} else {
280
224
		if ((tmp1 = mktmpcpy(filename1)))
281
35
			filename1 = tmp1;
282
224
		if ((tmp2 = mktmpcpy(filename2)))
283
42
			filename2 = tmp2;
284
	}
285
286
238
	diffargv[diffargc++] = filename1;
287
238
	diffargv[diffargc++] = filename2;
288
	/* Add NULL to end of array to indicate end of array. */
289
238
	diffargv[diffargc++] = NULL;
290
291
	/* Subtract column divider and divide by two. */
292
238
	width = (wflag - 3) / 2;
293
	/* Make sure line_width can fit in size_t. */
294
238
	if (width > (SIZE_MAX - 3) / 2)
295
		errx(2, "width is too large: %zu", width);
296
238
	line_width = width * 2 + 3;
297
298
238
	if (pipe(fd))
299
		err(2, "pipe");
300
301
238
	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
238
	close(fd[1]);
320
321
	/* Open pipe to diff command. */
322
238
	if ((diffpipe = fdopen(fd[0], "r")) == NULL)
323
		err(2, "could not open diff pipe");
324
238
	if ((file1 = fopen(filename1, "r")) == NULL)
325
		err(2, "could not open %s", filename1);
326
238
	if ((file2 = fopen(filename2, "r")) == NULL)
327
		err(2, "could not open %s", filename2);
328
329
	/* Line numbers start at one. */
330
238
	file1ln = file2ln = 1;
331
332
	/* Read and parse diff output. */
333
1022
	while (parsecmd(diffpipe, file1, file2) != EOF)
334
		;
335
238
	fclose(diffpipe);
336
337
	/* Wait for diff to exit. */
338

714
	if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status) ||
339
238
	    WEXITSTATUS(status) >= 2)
340
		err(2, "diff exited abnormally");
341
342
	/* Delete and free unneeded temporary files. */
343
238
	if (tmp1)
344
35
		if (unlink(tmp1))
345
			warn("error deleting %s", tmp1);
346
238
	if (tmp2)
347
42
		if (unlink(tmp2))
348
			warn("error deleting %s", tmp2);
349
238
	free(tmp1);
350
238
	free(tmp2);
351
	filename1 = filename2 = tmp1 = tmp2 = NULL;
352
353
	/* No more diffs, so print common lines. */
354
238
	if (lflag)
355
245
		while ((s1 = xfgets(file1)))
356
98
			enqueue(s1, ' ', NULL);
357
	else
358
350
		for (;;) {
359
539
			s1 = xfgets(file1);
360
539
			s2 = xfgets(file2);
361
539
			if (s1 || s2)
362
350
				enqueue(s1, ' ', s2);
363
			else
364
				break;
365
		}
366
238
	fclose(file1);
367
238
	fclose(file2);
368
	/* Process unmodified lines. */
369
238
	processq();
370
371
	/* Return diff exit status. */
372
476
	return (WEXITSTATUS(status));
373
238
}
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

67879
	for (; *s && *col < col_max; ++s) {
385
		size_t new_col;
386
387
20622
		switch (*s) {
388
		case '\t':
389
			/*
390
			 * If rounding to next multiple of eight causes
391
			 * an integer overflow, just return.
392
			 */
393
490
			if (*col > SIZE_MAX - 8)
394
				return;
395
396
			/* Round to next multiple of eight. */
397
490
			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
490
			if (new_col > col_max)
404
182
				return;
405
308
			*col = new_col;
406
308
			break;
407
408
		default:
409
20132
			++(*col);
410
20132
		}
411
412
40880
		putchar(*s);
413
20440
	}
414
2016
}
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
756
	putchar('%');
427
428
	/* Get user input. */
429
336
	for (; (cmd = xfgets(stdin)); free(cmd)) {
430
		const char *p;
431
432
		/* Skip leading whitespace. */
433
588
		for (p = cmd; isspace((unsigned char)*p); ++p)
434
			;
435
436


308
		switch (*p) {
437
		case 'e':
438
			/* Skip `e'. */
439
42
			++p;
440
441
42
			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
105
			if (s1 != NULL)
449
70
				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
105
			if (s2 != NULL)
461
70
				fprintf(outfp, "%s\n", s2);
462
463
			/* End of command parsing. */
464
			break;
465
466
		case 's':
467
28
			sflag = 1;
468
28
			goto PROMPT;
469
470
		case 'v':
471
14
			sflag = 0;
472
			/* FALLTHROUGH */
473
474
		default:
475
			/* Interactive usage help. */
476
USAGE:
477
14
			int_usage();
478
PROMPT:
479
84
			putchar('%');
480
481
			/* Prompt user again. */
482
42
			continue;
483
		}
484
485
252
		free(cmd);
486
252
		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
252
}
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
2912
	size_t col;
511
512
	/* Print first column.  Skips if s1 == NULL. */
513
1456
	col = 0;
514
1456
	if (s1) {
515
		/* Skip angle bracket and space. */
516
1155
		printcol(s1, &col, width);
517
518
1155
	}
519
520
	/* Only print left column. */
521
1456
	if (div == ' ' && !s2) {
522
266
		putchar('\n');
523
133
		return;
524
	}
525
526
	/* Otherwise, we pad this column up to width. */
527
102921
	for (; col < width; ++col)
528
101598
		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
1323
	if (!s2) {
535
462
		printf(" %c\n", div);
536
462
		return;
537
	}
538
861
	printf(" %c ", div);
539
861
	col += 3;
540
541
	/* Skip angle bracket and space. */
542
861
	printcol(s2, &col, line_width);
543
544
1722
	putchar('\n');
545
2317
}
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
16548
	clearerr(file);
559
560

6181
	if (!(s = fparseln(file, NULL, NULL, delim, 0)) &&
561
1330
	    ferror(file))
562
		err(2, "error reading file");
563
564
5516
	if (!s) {
565
665
		return (NULL);
566
	}
567
568
4851
	return (s);
569
5516
}
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
1568
	const char *errstr;
583
	char c, cmd;
584
585
	/* Read ed command. */
586
784
	if (!(line = xfgets(diffpipe)))
587
238
		return (EOF);
588
589
	p = line;
590
	/* Go to character after line number. */
591
2184
	while (isdigit((unsigned char)*p))
592
546
		++p;
593
	c = *p;
594
546
	*p++ = 0;
595
546
	file1start = strtonum(line, 0, INT_MAX, &errstr);
596
546
	if (errstr)
597
		errx(2, "file1 start is %s: %s", errstr, line);
598
599
	/* A range is specified for file1. */
600
546
	if (c == ',') {
601
602
		q = p;
603
		/* Go to character after file2end. */
604
84
		while (isdigit((unsigned char)*p))
605
28
			++p;
606
		c = *p;
607
14
		*p++ = 0;
608
14
		file1end = strtonum(q, 0, INT_MAX, &errstr);
609
14
		if (errstr)
610
			errx(2, "file1 end is %s: %s", errstr, line);
611
14
		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

1099
	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
2184
	while (isdigit((unsigned char)*p))
625
546
		++p;
626
	c = *p;
627
546
	*p++ = 0;
628
546
	file2start = strtonum(q, 0, INT_MAX, &errstr);
629
546
	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

1085
	if (c != ',' && c != '\0')
637
		errx(2, "invalid line range in file2: %c: %s", c, line);
638
639
546
	if (c == ',') {
640
641
7
		file2end = strtonum(p, 0, INT_MAX, &errstr);
642
7
		if (errstr)
643
			errx(2, "file2 end is %s: %s", errstr, line);
644
7
		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
546
	if (cmd == 'a') {
651
189
		if (file1start != file1end)
652
			errx(2, "append cannot have a file1 range: %s",
653
			    line);
654
189
		if (file1start == SIZE_MAX)
655
			errx(2, "file1 line range too high: %s", line);
656
189
		file1start = ++file1end;
657
189
	}
658
	/*
659
	 * I'm not sure what the deal is with the line numbers for
660
	 * deletes, though.
661
	 */
662
357
	else if (cmd == 'd') {
663
196
		if (file2start != file2end)
664
			errx(2, "delete cannot have a file2 range: %s",
665
			    line);
666
196
		if (file2start == SIZE_MAX)
667
			errx(2, "file2 line range too high: %s", line);
668
196
		file2start = ++file2end;
669
196
	}
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

1995
	for (; file1ln < file1start && file2ln < file2start;
676
483
	    ++file1ln, ++file2ln) {
677
		char *s1, *s2;
678
679
483
		if (!(s1 = xfgets(file1)))
680
			errx(2, "file1 shorter than expected");
681
483
		if (!(s2 = xfgets(file2)))
682
			errx(2, "file2 shorter than expected");
683
684
		/* If the -l flag was specified, print only left column. */
685
483
		if (lflag) {
686
147
			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
147
			if (Iflag)
695
42
				enqueue(s1, '(', NULL);
696
			else
697
105
				enqueue(s1, ' ', NULL);
698
		} else
699
336
			enqueue(s1, ' ', s2);
700
	}
701
	/* Ignore deleted lines. */
702
546
	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
546
	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
546
	processq();
726
727

546
	switch (cmd) {
728
	case 'a':
729
189
		printa(file2, file2end);
730
189
		n = file2end - file2start + 1;
731
189
		break;
732
733
	case 'c':
734
161
		printc(file1, file1end, file2, file2end);
735
161
		n = file1end - file1start + 1 + 1 + file2end - file2start + 1;
736
161
		break;
737
738
	case 'd':
739
196
		printd(file1, file1end);
740
196
		n = file1end - file1start + 1;
741
196
		break;
742
743
	default:
744
		errx(2, "invalid diff command: %c: %s", cmd, line);
745
	}
746
546
	free(line);
747
748
	/* Skip to next ed line. */
749
3500
	while (n--) {
750
1204
		if (!(line = xfgets(diffpipe)))
751
			errx(2, "diff ended early");
752
1204
		free(line);
753
	}
754
755
546
	return (0);
756
784
}
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
3626
	if (!(diffp = malloc(sizeof(struct diffline))))
767
		err(2, "enqueue");
768
1813
	diffp->left = left;
769
1813
	diffp->div = div;
770
1813
	diffp->right = right;
771
1813
	SIMPLEQ_INSERT_TAIL(&diffhead, diffp, diffentries);
772
1813
}
773
774
/*
775
 * Free a diffline structure and its elements.
776
 */
777
static void
778
freediff(struct diffline *diffp)
779
{
780
3626
	free(diffp->left);
781
1813
	free(diffp->right);
782
1813
	free(diffp);
783
1813
}
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
5320
	if (!*s) {
807
1827
		if (!(*s = strdup(append)))
808
			err(2, "astrcat");
809
810
		/* Keep track of string. */
811
1827
		offset = strlen(*s);
812
1827
		oldstr = *s;
813
814
1827
		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
833
	if (oldstr != *s) {
827
448
		offset = strlen(*s);
828
448
		oldstr = *s;
829
448
	}
830
831
	/* Size = strlen(*s) + \n + strlen(append) + '\0'. */
832
833
	newsiz = offset + 1 + strlen(append) + 1;
833
834
	/* Resize *s to fit new string. */
835
833
	newstr = realloc(*s, newsiz);
836
833
	if (newstr == NULL)
837
		err(2, "astrcat");
838
833
	*s = newstr;
839
840
	/* *s + offset should be end of string. */
841
	/* Concatenate. */
842
833
	strlcpy(*s + offset, "\n", newsiz - offset);
843
833
	strlcat(*s + offset, append, newsiz - offset);
844
845
	/* New string length should be exactly newsiz - 1 characters. */
846
	/* Store generated string's values. */
847
833
	offset = newsiz - 1;
848
833
	oldstr = *s;
849
3493
}
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
3304
	char divc, *left, *right;
860
861
	/* Don't process empty queue. */
862
1652
	if (SIMPLEQ_EMPTY(&diffhead))
863
448
		return;
864
865
	/* Remember the divider. */
866
1204
	divc = SIMPLEQ_FIRST(&diffhead)->div;
867
868
1204
	left = NULL;
869
1204
	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
6034
	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


3290
		if (!sflag || diffp->div == '|' || diffp->div == '<' ||
880
420
		    diffp->div == '>')
881
1456
			println(diffp->left, diffp->div, diffp->right);
882
883
		/* Append new lines to diff set. */
884
1813
		if (diffp->left)
885
1512
			astrcat(&left, diffp->left);
886
1813
		if (diffp->right)
887
1148
			astrcat(&right, diffp->right);
888
	}
889
890
	/* Empty queue and free each diff line and its elements. */
891
4830
	while (!SIMPLEQ_EMPTY(&diffhead)) {
892
		diffp = SIMPLEQ_FIRST(&diffhead);
893
3017
		SIMPLEQ_REMOVE_HEAD(&diffhead, diffentries);
894
1813
		freediff(diffp);
895
	}
896
897
	/* Write to outfp, prompting user if lines are different. */
898
1204
	if (outfp)
899

588
		switch (divc) {
900
		case ' ': case '(': case ')':
901
336
			fprintf(outfp, "%s\n", left);
902
336
			break;
903
		case '|': case '<': case '>':
904
252
			prompt(left, right);
905
252
			break;
906
		default:
907
			errx(2, "invalid divider: %c", divc);
908
		}
909
910
	/* Free left and right. */
911
1204
	free(left);
912
1204
	free(right);
913
2856
}
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
1169
	for (; file2ln <= line2; ++file2ln) {
924
301
		if (!(line = xfgets(file)))
925
			errx(2, "append ended early");
926
301
		enqueue(NULL, '>', line);
927
	}
928
929
189
	processq();
930
189
}
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
322
	SIMPLEQ_HEAD(, fileline) delqhead = SIMPLEQ_HEAD_INITIALIZER(delqhead);
944
945
	/* Read lines to be deleted. */
946
644
	for (; file1ln <= file1end; ++file1ln) {
947
		struct fileline *linep;
948
		char *line1;
949
950
		/* Read lines from both. */
951
161
		if (!(line1 = xfgets(file1)))
952
			errx(2, "error reading file1 in delete in change");
953
954
		/* Add to delete queue. */
955
161
		if (!(linep = malloc(sizeof(struct fileline))))
956
			err(2, "printc");
957
161
		linep->line = line1;
958
161
		SIMPLEQ_INSERT_TAIL(&delqhead, linep, fileentries);
959
	}
960
961
	/* Process changed lines.. */
962

644
	for (; !SIMPLEQ_EMPTY(&delqhead) && file2ln <= file2end;
963
161
	    ++file2ln) {
964
		struct fileline *del;
965
		char *add;
966
967
		/* Get add line. */
968
161
		if (!(add = xfgets(file2)))
969
			errx(2, "error reading add in change");
970
971
161
		del = SIMPLEQ_FIRST(&delqhead);
972
161
		enqueue(del->line, '|', add);
973
322
		SIMPLEQ_REMOVE_HEAD(&delqhead, fileentries);
974
		/*
975
		 * Free fileline structure but not its elements since
976
		 * they are queued up.
977
		 */
978
161
		free(del);
979
	}
980
161
	processq();
981
982
	/* Process remaining lines to add. */
983
322
	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
161
	processq();
993
994
	/* Process remaining lines to delete. */
995
322
	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
161
	processq();
1004
161
}
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
1428
	for (; file1ln <= file1end; ++file1ln) {
1016
420
		if (!(line1 = xfgets(file1)))
1017
			errx(2, "file1 ended early in delete");
1018
420
		enqueue(line1, '<', NULL);
1019
	}
1020
196
	processq();
1021
196
}
1022
1023
/*
1024
 * Interactive mode usage.
1025
 */
1026
static void
1027
int_usage(void)
1028
{
1029
28
	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
14
}
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
}