xref: /openbsd-src/usr.bin/diff/diffdir.c (revision 2023c35e157026ced6d3ffe8d22c577784bdc582)
1*2023c35eSmillert /*	$OpenBSD: diffdir.c,v 1.39 2010/11/08 15:49:13 millert Exp $	*/
2d0c3f575Sderaadt 
3d0c3f575Sderaadt /*
4*2023c35eSmillert  * Copyright (c) 2003, 2010 Todd C. Miller <Todd.Miller@courtesan.com>
5d0c3f575Sderaadt  *
64ec4b3d5Smillert  * Permission to use, copy, modify, and distribute this software for any
74ec4b3d5Smillert  * purpose with or without fee is hereby granted, provided that the above
84ec4b3d5Smillert  * copyright notice and this permission notice appear in all copies.
9d0c3f575Sderaadt  *
104ec4b3d5Smillert  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
114ec4b3d5Smillert  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
124ec4b3d5Smillert  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
134ec4b3d5Smillert  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
144ec4b3d5Smillert  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
154ec4b3d5Smillert  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
164ec4b3d5Smillert  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
174ec4b3d5Smillert  *
184ec4b3d5Smillert  * Sponsored in part by the Defense Advanced Research Projects
194ec4b3d5Smillert  * Agency (DARPA) and Air Force Research Laboratory, Air Force
204ec4b3d5Smillert  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
21d0c3f575Sderaadt  */
22d0c3f575Sderaadt 
234ec4b3d5Smillert #include <sys/param.h>
244ec4b3d5Smillert #include <sys/stat.h>
2526da422aStedu 
2649dffe13Smillert #include <dirent.h>
274ec4b3d5Smillert #include <err.h>
2849dffe13Smillert #include <errno.h>
2949dffe13Smillert #include <fcntl.h>
304ec4b3d5Smillert #include <fnmatch.h>
314ec4b3d5Smillert #include <paths.h>
325004e76eStedu #include <stdio.h>
3326da422aStedu #include <stdlib.h>
3426da422aStedu #include <string.h>
3549dffe13Smillert #include <unistd.h>
36ae8d569bSderaadt 
37ae8d569bSderaadt #include "diff.h"
384a034c3aSray #include "xmalloc.h"
3926da422aStedu 
40*2023c35eSmillert static int selectfile(struct dirent *);
41*2023c35eSmillert static struct dirent **slurpdir(char *, int);
423f8e756bSray static void diffit(struct dirent *, char *, size_t, char *, size_t, int);
4326da422aStedu 
44b4bca33fSmillert #define d_status	d_type		/* we need to store status for -l */
45b4bca33fSmillert 
46ae8d569bSderaadt /*
478fa21293Sotto  * Diff directory traversal. Will be called recursively if -r was specified.
48ae8d569bSderaadt  */
4926da422aStedu void
503f8e756bSray diffdir(char *p1, char *p2, int flags)
51ae8d569bSderaadt {
52*2023c35eSmillert 	struct dirent *dent1, **dp1, **dirp1 = NULL;
53*2023c35eSmillert 	struct dirent *dent2, **dp2, **dirp2 = NULL;
544ec4b3d5Smillert 	size_t dirlen1, dirlen2;
554ec4b3d5Smillert 	char path1[MAXPATHLEN], path2[MAXPATHLEN];
564ec4b3d5Smillert 	int pos;
57ae8d569bSderaadt 
584ec4b3d5Smillert 	dirlen1 = strlcpy(path1, *p1 ? p1 : ".", sizeof(path1));
594ec4b3d5Smillert 	if (dirlen1 >= sizeof(path1) - 1) {
604ec4b3d5Smillert 		warnx("%s: %s", p1, strerror(ENAMETOOLONG));
614ec4b3d5Smillert 		status = 2;
624ec4b3d5Smillert 		return;
63ae8d569bSderaadt 	}
644ec4b3d5Smillert 	if (path1[dirlen1 - 1] != '/') {
654ec4b3d5Smillert 		path1[dirlen1++] = '/';
664ec4b3d5Smillert 		path1[dirlen1] = '\0';
67ae8d569bSderaadt 	}
684ec4b3d5Smillert 	dirlen2 = strlcpy(path2, *p2 ? p2 : ".", sizeof(path2));
694ec4b3d5Smillert 	if (dirlen2 >= sizeof(path2) - 1) {
704ec4b3d5Smillert 		warnx("%s: %s", p2, strerror(ENAMETOOLONG));
714ec4b3d5Smillert 		status = 2;
724ec4b3d5Smillert 		return;
73ae8d569bSderaadt 	}
744ec4b3d5Smillert 	if (path2[dirlen2 - 1] != '/') {
754ec4b3d5Smillert 		path2[dirlen2++] = '/';
764ec4b3d5Smillert 		path2[dirlen2] = '\0';
774ec4b3d5Smillert 	}
784ec4b3d5Smillert 
794ec4b3d5Smillert 	/* get a list of the entries in each directory */
80*2023c35eSmillert 	dp1 = dirp1 = slurpdir(path1, Nflag + Pflag);
81*2023c35eSmillert 	dp2 = dirp2 = slurpdir(path2, Nflag);
82*2023c35eSmillert 	if (dirp1 == NULL || dirp2 == NULL)
8389a45341Sray 		goto closem;
844ec4b3d5Smillert 
854ec4b3d5Smillert 	/*
864ec4b3d5Smillert 	 * If we were given a starting point, find it.
874ec4b3d5Smillert 	 */
884ec4b3d5Smillert 	if (start != NULL) {
894ec4b3d5Smillert 		while (*dp1 != NULL && strcmp((*dp1)->d_name, start) < 0)
904ec4b3d5Smillert 			dp1++;
914ec4b3d5Smillert 		while (*dp2 != NULL && strcmp((*dp2)->d_name, start) < 0)
924ec4b3d5Smillert 			dp2++;
934ec4b3d5Smillert 	}
944ec4b3d5Smillert 
954ec4b3d5Smillert 	/*
964ec4b3d5Smillert 	 * Iterate through the two directory lists, diffing as we go.
974ec4b3d5Smillert 	 */
984ec4b3d5Smillert 	while (*dp1 != NULL || *dp2 != NULL) {
994ec4b3d5Smillert 		dent1 = *dp1;
1004ec4b3d5Smillert 		dent2 = *dp2;
1014ec4b3d5Smillert 
1024ec4b3d5Smillert 		pos = dent1 == NULL ? 1 : dent2 == NULL ? -1 :
1034ec4b3d5Smillert 		    strcmp(dent1->d_name, dent2->d_name);
1044ec4b3d5Smillert 		if (pos == 0) {
1054ec4b3d5Smillert 			/* file exists in both dirs, diff it */
1063f8e756bSray 			diffit(dent1, path1, dirlen1, path2, dirlen2, flags);
1074ec4b3d5Smillert 			dp1++;
1084ec4b3d5Smillert 			dp2++;
1094ec4b3d5Smillert 		} else if (pos < 0) {
1104ec4b3d5Smillert 			/* file only in first dir, only diff if -N */
1114ec4b3d5Smillert 			if (Nflag)
1123f8e756bSray 				diffit(dent1, path1, dirlen1, path2, dirlen2,
1133f8e756bSray 				    flags);
114b4bca33fSmillert 			else if (lflag)
115b4bca33fSmillert 				dent1->d_status |= D_ONLY;
116016bcb7bSmillert 			else
1174893e147Smillert 				print_only(path1, dirlen1, dent1->d_name);
1184ec4b3d5Smillert 			dp1++;
119ae8d569bSderaadt 		} else {
120aeb82612Smillert 			/* file only in second dir, only diff if -N or -P */
121aeb82612Smillert 			if (Nflag || Pflag)
1223f8e756bSray 				diffit(dent2, path1, dirlen1, path2, dirlen2,
1233f8e756bSray 				    flags);
124b4bca33fSmillert 			else if (lflag)
125b4bca33fSmillert 				dent2->d_status |= D_ONLY;
126016bcb7bSmillert 			else
1274893e147Smillert 				print_only(path2, dirlen2, dent2->d_name);
1284ec4b3d5Smillert 			dp2++;
129ae8d569bSderaadt 		}
130ae8d569bSderaadt 	}
131b4bca33fSmillert 	if (lflag) {
132de414158Smillert 		path1[dirlen1] = '\0';
133de414158Smillert 		path2[dirlen2] = '\0';
134b4bca33fSmillert 		for (dp1 = dirp1; (dent1 = *dp1) != NULL; dp1++) {
135b4bca33fSmillert 			print_status(dent1->d_status, path1, path2,
136b4bca33fSmillert 			    dent1->d_name);
137b4bca33fSmillert 		}
138b4bca33fSmillert 		for (dp2 = dirp2; (dent2 = *dp2) != NULL; dp2++) {
139b4bca33fSmillert 			if (dent2->d_status == D_ONLY)
140b4bca33fSmillert 				print_status(dent2->d_status, path2, NULL,
141b4bca33fSmillert 				    dent2->d_name);
142b4bca33fSmillert 		}
143b4bca33fSmillert 	}
144ae8d569bSderaadt 
14589a45341Sray closem:
146*2023c35eSmillert 	if (dirp1 != NULL) {
147*2023c35eSmillert 		for (dp1 = dirp1; (dent1 = *dp1) != NULL; dp1++)
148*2023c35eSmillert 			xfree(dent1);
1494a034c3aSray 		xfree(dirp1);
1504ec4b3d5Smillert 	}
151*2023c35eSmillert 	if (dirp2 != NULL) {
152*2023c35eSmillert 		for (dp2 = dirp2; (dent2 = *dp2) != NULL; dp2++)
153*2023c35eSmillert 			xfree(dent2);
1544a034c3aSray 		xfree(dirp2);
1554ec4b3d5Smillert 	}
1564ec4b3d5Smillert }
1574ec4b3d5Smillert 
1584ec4b3d5Smillert /*
159*2023c35eSmillert  * Read in a whole directory, culling out the "excluded" files.
160*2023c35eSmillert  * Returns an array of struct dirent *'s in alphabetic order.
161*2023c35eSmillert  * Caller is responsible for free()ing each array element and the array itself.
1624ec4b3d5Smillert  */
1634ec4b3d5Smillert static struct dirent **
164*2023c35eSmillert slurpdir(char *dirname, int enoentok)
165ae8d569bSderaadt {
166*2023c35eSmillert 	struct dirent **namelist = NULL;
167*2023c35eSmillert 	int rval;
168ae8d569bSderaadt 
169*2023c35eSmillert 	rval = scandir(dirname, &namelist, selectfile, alphasort);
170*2023c35eSmillert 	if (rval == -1) {
171*2023c35eSmillert 		if (enoentok && errno == ENOENT) {
172*2023c35eSmillert 			namelist = xmalloc(sizeof(struct dirent *));
173*2023c35eSmillert 			namelist[0] = NULL;
174*2023c35eSmillert 		} else {
175*2023c35eSmillert 			warn("%s", dirname);
17649dffe13Smillert 		}
1773cbc20bbSotto 	}
1784ec4b3d5Smillert 
179*2023c35eSmillert 	return (namelist);
180ae8d569bSderaadt }
181ae8d569bSderaadt 
1824ec4b3d5Smillert /*
1834ec4b3d5Smillert  * Do the actual diff by calling either diffreg() or diffdir().
1844ec4b3d5Smillert  */
18526da422aStedu static void
1863f8e756bSray diffit(struct dirent *dp, char *path1, size_t plen1, char *path2, size_t plen2,
1873f8e756bSray     int flags)
188ae8d569bSderaadt {
1893f8e756bSray 	flags |= D_HEADER;
1904ec4b3d5Smillert 	strlcpy(path1 + plen1, dp->d_name, MAXPATHLEN - plen1);
1914ec4b3d5Smillert 	if (stat(path1, &stb1) != 0) {
192aeb82612Smillert 		if (!(Nflag || Pflag) || errno != ENOENT) {
1934ec4b3d5Smillert 			warn("%s", path1);
194ae8d569bSderaadt 			return;
195ae8d569bSderaadt 		}
1964ec4b3d5Smillert 		flags |= D_EMPTY1;
1974ec4b3d5Smillert 		memset(&stb1, 0, sizeof(stb1));
1984ec4b3d5Smillert 	}
1994ec4b3d5Smillert 
2004ec4b3d5Smillert 	strlcpy(path2 + plen2, dp->d_name, MAXPATHLEN - plen2);
2014ec4b3d5Smillert 	if (stat(path2, &stb2) != 0) {
2024ec4b3d5Smillert 		if (!Nflag || errno != ENOENT) {
2034ec4b3d5Smillert 			warn("%s", path2);
204ae8d569bSderaadt 			return;
205ae8d569bSderaadt 		}
2064ec4b3d5Smillert 		flags |= D_EMPTY2;
2074ec4b3d5Smillert 		memset(&stb2, 0, sizeof(stb2));
2084ec4b3d5Smillert 		stb2.st_mode = stb1.st_mode;
2094ec4b3d5Smillert 	}
2104ec4b3d5Smillert 	if (stb1.st_mode == 0)
2114ec4b3d5Smillert 		stb1.st_mode = stb2.st_mode;
2124ec4b3d5Smillert 
2134ec4b3d5Smillert 	if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) {
2144ec4b3d5Smillert 		if (rflag)
2153f8e756bSray 			diffdir(path1, path2, flags);
216b4bca33fSmillert 		else if (lflag)
217b4bca33fSmillert 			dp->d_status |= D_COMMON;
218016bcb7bSmillert 		else
219aeb82612Smillert 			printf("Common subdirectories: %s and %s\n",
220aeb82612Smillert 			    path1, path2);
221ae8d569bSderaadt 		return;
222ae8d569bSderaadt 	}
2235f4c3fa8Smillert 	if (!S_ISREG(stb1.st_mode) && !S_ISDIR(stb1.st_mode))
2245f4c3fa8Smillert 		dp->d_status = D_SKIPPED1;
2255f4c3fa8Smillert 	else if (!S_ISREG(stb2.st_mode) && !S_ISDIR(stb2.st_mode))
2265f4c3fa8Smillert 		dp->d_status = D_SKIPPED2;
2275f4c3fa8Smillert 	else
228b4bca33fSmillert 		dp->d_status = diffreg(path1, path2, flags);
229de414158Smillert 	if (!lflag)
230d2ea36f5Sray 		print_status(dp->d_status, path1, path2, "");
231ae8d569bSderaadt }
232ae8d569bSderaadt 
233ae8d569bSderaadt /*
234*2023c35eSmillert  * Returns 1 if the directory entry should be included in the
235*2023c35eSmillert  * diff, else 0.  Checks the excludes list.
236ae8d569bSderaadt  */
2374ec4b3d5Smillert static int
238*2023c35eSmillert selectfile(struct dirent *dp)
239ae8d569bSderaadt {
2404ec4b3d5Smillert 	struct excludes *excl;
2414ec4b3d5Smillert 
242*2023c35eSmillert 	if (dp->d_fileno == 0)
243*2023c35eSmillert 		return (0);
244*2023c35eSmillert 
2454ec4b3d5Smillert 	/* always skip "." and ".." */
246*2023c35eSmillert 	if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
247*2023c35eSmillert 	    (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
248*2023c35eSmillert 		return (0);
2494ec4b3d5Smillert 
2504ec4b3d5Smillert 	/* check excludes list */
2514ec4b3d5Smillert 	for (excl = excludes_list; excl != NULL; excl = excl->next)
252*2023c35eSmillert 		if (fnmatch(excl->pattern, dp->d_name, FNM_PATHNAME) == 0)
253ae8d569bSderaadt 			return (0);
254*2023c35eSmillert 
255*2023c35eSmillert 	return (1);
256ae8d569bSderaadt }
257