xref: /openbsd-src/usr.bin/diff/diffdir.c (revision a6a14831a52dc2f752e78bcf9046befbc1121990)
1 /*	$OpenBSD: diffdir.c,v 1.7 2003/06/25 03:37:32 deraadt Exp $	*/
2 
3 /*
4  * Copyright (C) Caldera International Inc.  2001-2002.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code and documentation must retain the above
11  *    copyright notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *	This product includes software developed or owned by Caldera
18  *	International, Inc.
19  * 4. Neither the name of Caldera International, Inc. nor the names of other
20  *    contributors may be used to endorse or promote products derived from
21  *    this software without specific prior written permission.
22  *
23  * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
24  * INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
25  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
27  * IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE LIABLE FOR ANY DIRECT,
28  * INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
33  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34  * POSSIBILITY OF SUCH DAMAGE.
35  */
36 
37 #include <sys/types.h>
38 #include <sys/wait.h>
39 
40 #include <stdlib.h>
41 #include <fcntl.h>
42 #include <unistd.h>
43 #include <string.h>
44 
45 #include "diff.h"
46 
47 #if 0
48 static const char sccsid[] = "@(#)diffdir.c	4.12 (Berkeley) 4/30/89";
49 #endif
50 
51 /*
52  * diff - directory comparison
53  */
54 #define	d_flags	d_ino
55 
56 #define	ONLY	1		/* Only in this directory */
57 #define	SAME	2		/* Both places and same */
58 #define	DIFFER	4		/* Both places and different */
59 #define	DIRECT	8		/* Directory */
60 
61 struct dir {
62 	u_long d_ino;
63 	short d_reclen;
64 	short d_namlen;
65 	char *d_entry;
66 };
67 
68 int header;
69 static int dirstatus;		/* exit status from diffdir */
70 extern int status;
71 char title[2 * BUFSIZ];
72 char *etitle;
73 char *prargs[] = {"pr", "-h", 0, "-f", 0, 0};
74 
75 
76 static struct dir *setupdir(char *);
77 static int ascii(int);
78 static void compare(struct dir *);
79 static void calldiff(char *);
80 static void setfile(char **fpp, char **epp, char *file);
81 static int useless(char *);
82 static void only(struct dir *dp, int which);
83 static void scanpr(struct dir *, int, char *, char *, char *, char *, char *);
84 
85 void
86 diffdir(char **argv)
87 {
88 	struct dir *d1, *d2;
89 	struct dir *dir1, *dir2;
90 	int i;
91 	int cmp;
92 
93 	if (opt == D_IFDEF) {
94 		fprintf(stderr, "diff: can't specify -I with directories\n");
95 		done(0);
96 	}
97 	if (opt == D_EDIT && (sflag || lflag))
98 		fprintf(stderr,
99 		    "diff: warning: shouldn't give -s or -l with -e\n");
100 	strlcpy(title, "diff ", sizeof title);
101 	for (i = 1; diffargv[i + 2]; i++) {
102 		if (!strcmp(diffargv[i], "-"))
103 			continue;	/* was -S, dont look silly */
104 		strlcat(title, diffargv[i], sizeof title);
105 		strlcat(title, " ", sizeof title);
106 	}
107 	for (etitle = title; *etitle; etitle++);
108 	setfile(&file1, &efile1, file1);
109 	setfile(&file2, &efile2, file2);
110 	argv[0] = file1;
111 	argv[1] = file2;
112 	dir1 = setupdir(file1);
113 	dir2 = setupdir(file2);
114 	d1 = dir1;
115 	d2 = dir2;
116 	while (d1->d_entry != 0 || d2->d_entry != 0) {
117 		if (d1->d_entry && useless(d1->d_entry)) {
118 			d1++;
119 			continue;
120 		}
121 		if (d2->d_entry && useless(d2->d_entry)) {
122 			d2++;
123 			continue;
124 		}
125 		if (d1->d_entry == 0)
126 			cmp = 1;
127 		else if (d2->d_entry == 0)
128 			cmp = -1;
129 		else
130 			cmp = strcmp(d1->d_entry, d2->d_entry);
131 		if (cmp < 0) {
132 			if (lflag)
133 				d1->d_flags |= ONLY;
134 			else if (opt == 0 || opt == 2)
135 				only(d1, 1);
136 			d1++;
137 			dirstatus |= 1;
138 		} else if (cmp == 0) {
139 			compare(d1);
140 			d1++;
141 			d2++;
142 		} else {
143 			if (lflag)
144 				d2->d_flags |= ONLY;
145 			else if (opt == 0 || opt == 2)
146 				only(d2, 2);
147 			d2++;
148 			dirstatus |= 1;
149 		}
150 	}
151 	if (lflag) {
152 		scanpr(dir1, ONLY, "Only in %.*s", file1, efile1, 0, 0);
153 		scanpr(dir2, ONLY, "Only in %.*s", file2, efile2, 0, 0);
154 		scanpr(dir1, SAME, "Common identical files in %.*s and %.*s",
155 		    file1, efile1, file2, efile2);
156 		scanpr(dir1, DIFFER, "Binary files which differ in %.*s and %.*s",
157 		    file1, efile1, file2, efile2);
158 		scanpr(dir1, DIRECT, "Common subdirectories of %.*s and %.*s",
159 		    file1, efile1, file2, efile2);
160 	}
161 	if (rflag) {
162 		if (header && lflag)
163 			printf("\f");
164 		for (d1 = dir1; d1->d_entry; d1++) {
165 			if ((d1->d_flags & DIRECT) == 0)
166 				continue;
167 			strcpy(efile1, d1->d_entry);
168 			strcpy(efile2, d1->d_entry);
169 			calldiff(0);
170 		}
171 	}
172 	status = dirstatus;
173 }
174 
175 void
176 setfile(char **fpp, char **epp, char *file)
177 {
178 	char *cp;
179 
180 	*fpp = talloc(BUFSIZ);
181 	strcpy(*fpp, file);
182 	for (cp = *fpp; *cp; cp++)
183 		continue;
184 	*cp++ = '/';
185 	*epp = cp;
186 }
187 
188 static void
189 scanpr(struct dir *dp, int test, char *title, char *file1, char *efile1,
190     char *file2, char *efile2)
191 {
192 	int titled = 0;
193 
194 	for (; dp->d_entry; dp++) {
195 		if ((dp->d_flags & test) == 0)
196 			continue;
197 		if (titled == 0) {
198 			if (header == 0)
199 				header = 1;
200 			else
201 				printf("\n");
202 			printf(title,
203 			    efile1 - file1 - 1, file1,
204 			    efile2 - file2 - 1, file2);
205 			printf(":\n");
206 			titled = 1;
207 		}
208 		printf("\t%s\n", dp->d_entry);
209 	}
210 }
211 
212 void
213 only(struct dir *dp, int which)
214 {
215 	char *file = which == 1 ? file1 : file2;
216 	char *efile = which == 1 ? efile1 : efile2;
217 
218 	printf("Only in %.*s: %s\n", (int)(efile - file - 1), file, dp->d_entry);
219 }
220 
221 int entcmp();
222 
223 struct dir *
224 setupdir(char *cp)
225 {
226 	struct dir *dp, *ep;
227 	struct direct *rp;
228 	int nitems;
229 	DIR *dirp;
230 
231 	dirp = opendir(cp);
232 	if (dirp == NULL) {
233 		fprintf(stderr, "diff: ");
234 		perror(cp);
235 		done(0);
236 	}
237 	nitems = 0;
238 	dp = talloc(sizeof(struct dir));
239 	while ((rp = readdir(dirp))) {
240 		ep = &dp[nitems++];
241 		ep->d_reclen = rp->d_reclen;
242 		ep->d_namlen = rp->d_namlen;
243 		ep->d_entry = 0;
244 		ep->d_flags = 0;
245 		if (ep->d_namlen > 0) {
246 			ep->d_entry = talloc(ep->d_namlen + 1);
247 			strcpy(ep->d_entry, rp->d_name);
248 		}
249 		dp = ralloc(dp, (nitems + 1) * sizeof(struct dir));
250 	}
251 	dp[nitems].d_entry = 0;	/* delimiter */
252 	closedir(dirp);
253 	qsort(dp, nitems, sizeof(struct dir), entcmp);
254 	return (dp);
255 }
256 
257 int
258 entcmp(struct dir *d1, struct dir *d2)
259 {
260 	return (strcmp(d1->d_entry, d2->d_entry));
261 }
262 
263 static void
264 compare(struct dir *dp)
265 {
266 	int i, j;
267 	int f1, f2, fmt1, fmt2;
268 	struct stat stb1, stb2;
269 	char buf1[BUFSIZ], buf2[BUFSIZ];
270 
271 	strcpy(efile1, dp->d_entry);
272 	strcpy(efile2, dp->d_entry);
273 	f1 = open(file1, 0);
274 	if (f1 < 0) {
275 		perror(file1);
276 		return;
277 	}
278 	f2 = open(file2, 0);
279 	if (f2 < 0) {
280 		perror(file2);
281 		close(f1);
282 		return;
283 	}
284 	fstat(f1, &stb1);
285 	fstat(f2, &stb2);
286 	fmt1 = stb1.st_mode & S_IFMT;
287 	fmt2 = stb2.st_mode & S_IFMT;
288 	if (fmt1 != S_IFREG || fmt2 != S_IFREG) {
289 		if (fmt1 == fmt2) {
290 			if (fmt1 != S_IFDIR && stb1.st_rdev == stb2.st_rdev)
291 				goto same;
292 			if (fmt1 == S_IFDIR) {
293 				dp->d_flags = DIRECT;
294 				if (lflag || opt == D_EDIT)
295 					goto closem;
296 				printf("Common subdirectories: %s and %s\n",
297 				    file1, file2);
298 				goto closem;
299 			}
300 		}
301 		goto notsame;
302 	}
303 	if (stb1.st_size != stb2.st_size)
304 		goto notsame;
305 	for (;;) {
306 		i = read(f1, buf1, BUFSIZ);
307 		j = read(f2, buf2, BUFSIZ);
308 		if (i < 0 || j < 0 || i != j)
309 			goto notsame;
310 		if (i == 0 && j == 0)
311 			goto same;
312 		for (j = 0; j < i; j++)
313 			if (buf1[j] != buf2[j])
314 				goto notsame;
315 	}
316 same:
317 	if (sflag == 0)
318 		goto closem;
319 	if (lflag)
320 		dp->d_flags = SAME;
321 	else
322 		printf("Files %s and %s are identical\n", file1, file2);
323 	goto closem;
324 notsame:
325 	dirstatus |= 1;
326 	if (!ascii(f1) || !ascii(f2)) {
327 		if (lflag)
328 			dp->d_flags |= DIFFER;
329 		else if (opt == D_NORMAL || opt == D_CONTEXT)
330 			printf("Binary files %s and %s differ\n",
331 			    file1, file2);
332 		goto closem;
333 	}
334 	close(f1);
335 	close(f2);
336 	anychange = 1;
337 	if (lflag)
338 		calldiff(title);
339 	else {
340 		if (opt == D_EDIT) {
341 			printf("ed - %s << '-*-END-*-'\n", dp->d_entry);
342 			calldiff(0);
343 		} else {
344 			printf("%s%s %s\n", title, file1, file2);
345 			calldiff(0);
346 		}
347 		if (opt == D_EDIT)
348 			printf("w\nq\n-*-END-*-\n");
349 	}
350 	return;
351 closem:
352 	close(f1);
353 	close(f2);
354 }
355 
356 static void
357 calldiff(char *wantpr)
358 {
359 	int pid, lstatus, lstatus2, pv[2];
360 
361 	prargs[2] = wantpr;
362 	fflush(stdout);
363 	if (wantpr) {
364 		snprintf(etitle, title + sizeof title - etitle,
365 		    "%s %s", file1, file2);
366 		pipe(pv);
367 		pid = fork();
368 		if (pid == -1) {
369 			fprintf(stderr, "No more processes");
370 			done(0);
371 		}
372 		if (pid == 0) {
373 			close(0);
374 			dup(pv[0]);
375 			close(pv[0]);
376 			close(pv[1]);
377 			execv(pr + 4, prargs);
378 			execv(pr, prargs);
379 			perror(pr);
380 			done(0);
381 		}
382 	}
383 	pid = fork();
384 	if (pid == -1) {
385 		fprintf(stderr, "diff: No more processes\n");
386 		done(0);
387 	}
388 	if (pid == 0) {
389 		if (wantpr) {
390 			close(1);
391 			dup(pv[1]);
392 			close(pv[0]);
393 			close(pv[1]);
394 		}
395 		execv(diff + 4, diffargv);
396 		execv(diff, diffargv);
397 		perror(diff);
398 		done(0);
399 	}
400 	if (wantpr) {
401 		close(pv[0]);
402 		close(pv[1]);
403 	}
404 	while (wait(&lstatus) != pid)
405 		continue;
406 	while (wait(&lstatus2) != -1)
407 		continue;
408 	/*
409 		if ((lstatus >> 8) >= 2)
410 			done(0);
411 	*/
412 	dirstatus |= lstatus >> 8;
413 }
414 
415 int
416 ascii(int f)
417 {
418 	char buf[BUFSIZ];
419 	int cnt;
420 	char *cp;
421 
422 	lseek(f, (off_t)0, SEEK_SET);
423 	cnt = read(f, buf, BUFSIZ);
424 	cp = buf;
425 	while (--cnt >= 0)
426 		if (*cp++ & 0200)
427 			return (0);
428 	return (1);
429 }
430 
431 /*
432  * THIS IS CRUDE.
433  */
434 int
435 useless(char *cp)
436 {
437 	if (cp[0] == '.') {
438 		if (cp[1] == '\0')
439 			return (1);	/* directory "." */
440 		if (cp[1] == '.' && cp[2] == '\0')
441 			return (1);	/* directory ".." */
442 	}
443 	if (start && strcmp(start, cp) > 0)
444 		return (1);
445 	return (0);
446 }
447