xref: /openbsd-src/usr.bin/diff/diff.c (revision b4bca33fcd90e52689100cb9afe3534ba75f7b3f)
1 /*	$OpenBSD: diff.c,v 1.27 2003/07/09 00:07:44 millert Exp $	*/
2 
3 /*
4  * Copyright (c) 2003 Todd C. Miller <Todd.Miller@courtesan.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  *
18  * Sponsored in part by the Defense Advanced Research Projects
19  * Agency (DARPA) and Air Force Research Laboratory, Air Force
20  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
21  */
22 
23 #ifndef lint
24 static const char rcsid[] = "$OpenBSD: diff.c,v 1.27 2003/07/09 00:07:44 millert Exp $";
25 #endif /* not lint */
26 
27 #include <sys/param.h>
28 #include <sys/stat.h>
29 
30 #include <err.h>
31 #include <errno.h>
32 #include <getopt.h>
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <stdarg.h>
36 #include <string.h>
37 #include <unistd.h>
38 
39 #include "diff.h"
40 
41 int	 aflag, bflag, iflag, lflag, Nflag, Pflag, rflag, sflag, tflag, wflag;
42 int	 format, context, status;
43 char	*start, *ifdefname, *diffargs;
44 struct stat stb1, stb2;
45 struct excludes *excludes_list;
46 
47 #define	OPTIONS	"abC:cD:efhilnNPqrS:stU:uwX:x:"
48 static struct option longopts[] = {
49 	{ "text",			no_argument,		0,	'a' },
50 	{ "ignore-space-change",	no_argument,		0,	'b' },
51 	{ "context",			optional_argument,	0,	'C' },
52 	{ "ifdef",			required_argument,	0,	'D' },
53 	{ "ed",				no_argument,		0,	'e' },
54 	{ "forward-ed",			no_argument,		0,	'f' },
55 	{ "ignore-case",		no_argument,		0,	'i' },
56 	{ "paginate",			no_argument,		0,	'l' },
57 	{ "new-file",			no_argument,		0,	'N' },
58 	{ "rcs",			no_argument,		0,	'n' },
59 	{ "unidirectional-new-file",	no_argument,		0,	'P' },
60 	{ "brief",			no_argument,		0,	'q' },
61 	{ "recursive",			no_argument,		0,	'r' },
62 	{ "report-identical-files",	no_argument,		0,	's' },
63 	{ "starting-file",		required_argument,	0,	'S' },
64 	{ "expand-tabs",		no_argument,		0,	't' },
65 	{ "unified",			optional_argument,	0,	'U' },
66 	{ "ignore-all-space",		no_argument,		0,	'w' },
67 	{ "exclude",			required_argument,	0,	'x' },
68 	{ "exclude-from",		required_argument,	0,	'X' },
69 };
70 
71 __dead void usage(void);
72 void push_excludes(char *);
73 void read_excludes_file(char *file);
74 void set_argstr(char **, char **);
75 
76 int
77 main(int argc, char **argv)
78 {
79 	char *ep, **oargv;
80 	long  l;
81 	int   ch, gotstdin;
82 
83 	oargv = argv;
84 	gotstdin = 0;
85 
86 	while ((ch = getopt_long(argc, argv, OPTIONS, longopts, NULL)) != -1) {
87 		switch (ch) {
88 		case 'a':
89 			aflag = 1;
90 			break;
91 		case 'b':
92 			bflag = 1;
93 			break;
94 		case 'C':
95 		case 'c':
96 			format = D_CONTEXT;
97 			if (optarg != NULL) {
98 				l = strtol(optarg, &ep, 10);
99 				if (*ep != '\0' || l < 0 || l >= INT_MAX)
100 					usage();
101 				context = (int)l;
102 			} else
103 				context = 3;
104 			break;
105 		case 'D':
106 			format = D_IFDEF;
107 			ifdefname = optarg;
108 			break;
109 		case 'e':
110 			format = D_EDIT;
111 			break;
112 		case 'f':
113 			format = D_REVERSE;
114 			break;
115 		case 'h':
116 			/* silently ignore for backwards compatibility */
117 			break;
118 		case 'i':
119 			iflag = 1;
120 			break;
121 		case 'l':
122 			lflag = 1;
123 			break;
124 		case 'N':
125 			Nflag = 1;
126 			break;
127 		case 'n':
128 			format = D_NREVERSE;
129 			break;
130 		case 'P':
131 			Pflag = 1;
132 			break;
133 		case 'r':
134 			rflag = 1;
135 			break;
136 		case 'q':
137 			format = D_BRIEF;
138 			break;
139 		case 'S':
140 			start = optarg;
141 			break;
142 		case 's':
143 			sflag = 1;
144 			break;
145 		case 't':
146 			tflag = 1;
147 			break;
148 		case 'U':
149 		case 'u':
150 			format = D_UNIFIED;
151 			if (optarg != NULL) {
152 				l = strtol(optarg, &ep, 10);
153 				if (*ep != '\0' || l < 0 || l >= INT_MAX)
154 					usage();
155 				context = (int)l;
156 			} else
157 				context = 3;
158 			break;
159 		case 'w':
160 			wflag = 1;
161 			break;
162 		case 'X':
163 			read_excludes_file(optarg);
164 			break;
165 		case 'x':
166 			push_excludes(optarg);
167 			break;
168 		default:
169 			usage();
170 			break;
171 		}
172 	}
173 	argc -= optind;
174 	argv += optind;
175 
176 	/*
177 	 * Do sanity checks, fill in stb1 and stb2 and call the appropriate
178 	 * driver routine.  Both drivers use the contents of stb1 and stb2.
179 	 */
180 	if (argc != 2)
181 		usage();
182 	if (strcmp(argv[0], "-") == 0) {
183 		fstat(STDIN_FILENO, &stb1);
184 		gotstdin = 1;
185 	} else if (stat(argv[0], &stb1) != 0)
186 		error("%s", argv[0]);
187 	if (strcmp(argv[1], "-") == 0) {
188 		fstat(STDIN_FILENO, &stb2);
189 		gotstdin = 1;
190 	} else if (stat(argv[1], &stb2) != 0)
191 		error("%s", argv[1]);
192 	if (gotstdin && (S_ISDIR(stb1.st_mode) || S_ISDIR(stb2.st_mode)))
193 		errorx("can't compare - to a directory");
194 	set_argstr(oargv, argv);
195 	if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) {
196 		if (format == D_IFDEF)
197 			errorx("-D option not supported with directories");
198 		diffdir(argv[0], argv[1]);
199 	} else {
200 		print_status(diffreg(argv[0], argv[1], 0), argv[0], argv[1],
201 		    NULL);
202 	}
203 	exit(status);
204 }
205 
206 void
207 quit(int signo)
208 {
209 	if (tempfiles[0] != NULL)
210 		unlink(tempfiles[0]);
211 	if (tempfiles[1] != NULL)
212 		unlink(tempfiles[1]);
213 	_exit(status);
214 }
215 
216 void *
217 emalloc(size_t n)
218 {
219 	void *p;
220 
221 	if ((p = malloc(n)) == NULL)
222 		error(NULL);
223 	return (p);
224 }
225 
226 void *
227 erealloc(void *p, size_t n)
228 {
229 	void *q;
230 
231 	if ((q = realloc(p, n)) == NULL)
232 		error(NULL);
233 	return (q);
234 }
235 
236 int
237 easprintf(char **ret, const char *fmt, ...)
238 {
239 	int len;
240 	va_list ap;
241 
242 	va_start(ap, fmt);
243 	len = vasprintf(ret, fmt, ap);
244 	va_end(ap);
245 
246 	if (len == -1)
247 		error(NULL);
248 	return(len);
249 }
250 
251 __dead void
252 error(const char *fmt, ...)
253 {
254 	va_list ap;
255 	int sverrno = errno;
256 
257 	if (tempfiles[0] != NULL)
258 		unlink(tempfiles[0]);
259 	if (tempfiles[1] != NULL)
260 		unlink(tempfiles[1]);
261 	errno = sverrno;
262 	va_start(ap, fmt);
263 	verr(2, fmt, ap);
264 	va_end(ap);
265 }
266 
267 __dead void
268 errorx(const char *fmt, ...)
269 {
270 	va_list ap;
271 
272 	if (tempfiles[0] != NULL)
273 		unlink(tempfiles[0]);
274 	if (tempfiles[1] != NULL)
275 		unlink(tempfiles[1]);
276 	va_start(ap, fmt);
277 	verrx(2, fmt, ap);
278 	va_end(ap);
279 }
280 
281 void
282 set_argstr(char **av, char **ave)
283 {
284 	size_t argsize;
285 	char **ap;
286 
287 	argsize = 4 + (char *)ave - (char *)av + 1;
288 	diffargs = emalloc(argsize);
289 	strlcpy(diffargs, "diff", argsize);
290 	for (ap = av + 1; ap < ave; ap++) {
291 		if (strcmp(*ap, "--") != 0) {
292 			strlcat(diffargs, " ", argsize);
293 			strlcat(diffargs, *ap, argsize);
294 		}
295 	}
296 }
297 
298 /*
299  * Read in an excludes file and push each line.
300  */
301 void
302 read_excludes_file(char *file)
303 {
304 	FILE *fp;
305 	char *buf, *pattern;
306 	size_t len;
307 
308 	if (strcmp(file, "-") == 0)
309 		fp = stdin;
310 	else if ((fp = fopen(file, "r")) == NULL)
311 		error("%s", file);
312 	while ((buf = fgetln(fp, &len)) != NULL) {
313 		if (buf[len - 1] == '\n')
314 			len--;
315 		pattern = emalloc(len + 1);
316 		memcpy(pattern, buf, len);
317 		pattern[len] = '\0';
318 		push_excludes(pattern);
319 	}
320 	if (strcmp(file, "-") != 0)
321 		fclose(fp);
322 }
323 
324 /*
325  * Push a pattern onto the excludes list.
326  */
327 void
328 push_excludes(char *pattern)
329 {
330 	struct excludes *entry;
331 
332 	entry = emalloc(sizeof(*entry));
333 	entry->pattern = pattern;
334 	entry->next = excludes_list;
335 	excludes_list = entry;
336 }
337 
338 void
339 print_status(int val, char *path1, char *path2, char *entry)
340 {
341 	switch (val) {
342 	case D_ONLY:
343 		printf("Only in %s: %s\n", path1, entry);
344 		break;
345 	case D_COMMON:
346 		printf("Common subdirectories: %s%s and %s%s\n",
347 		    path1, entry ? entry : "", path2, entry ? entry : "");
348 		break;
349 	case D_BINARY:
350 		printf("Binary files %s%s and %s%s differ\n",
351 		    path1, entry ? entry : "", path2, entry ? entry : "");
352 		break;
353 	case D_DIFFER:
354 		if (format == D_BRIEF)
355 			printf("Files %s%s and %s%s differ\n",
356 			    path1, entry ? entry : "",
357 			    path2, entry ? entry : "");
358 		break;
359 	case D_SAME:
360 		if (sflag)
361 			printf("Files %s%s and %s%s are identical\n",
362 			    path1, entry ? entry : "",
363 			    path2, entry ? entry : "");
364 		break;
365 	}
366 }
367 
368 __dead void
369 usage(void)
370 {
371 	(void)fprintf(stderr,
372 	    "usage: diff [-biqtw] [-c | -e | -f | -n | -u ] file1 file2\n"
373 	    "       diff [-biqtw] -C number file1 file2\n"
374 	    "       diff [-biqtw] -D string file1 file2\n"
375 	    "       diff [-biqtw] -U number file1 file2\n"
376 	    "       diff [-biNPqwt] [-c | -e | -f | -n | -u ] [-r] [-s] [-S name]"
377 	    " [-X file]\n            [-x pattern] dir1 dir2\n");
378 
379 	exit(2);
380 }
381