xref: /dflybsd-src/usr.bin/diff/diffdir.c (revision c9733229451fac5faa53b1a016b01866eae75a1c)
1  /*	$OpenBSD: diffdir.c,v 1.47 2019/01/25 00:19:26 millert Exp $	*/
2  
3  /*
4   * Copyright (c) 2003, 2010 Todd C. Miller <millert@openbsd.org>
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  #include <sys/stat.h>
24  
25  #include <dirent.h>
26  #include <err.h>
27  #include <errno.h>
28  #include <fcntl.h>
29  #include <fnmatch.h>
30  #include <paths.h>
31  #include <stdio.h>
32  #include <stdlib.h>
33  #include <string.h>
34  #include <unistd.h>
35  #include <limits.h>
36  
37  #include "diff.h"
38  #include "xmalloc.h"
39  
40  static int selectfile(const struct dirent *);
41  static void diffit(struct dirent *, char *, size_t, char *, size_t, int);
42  
43  #define d_status	d_type		/* we need to store status for -l */
44  
45  /*
46   * Diff directory traversal. Will be called recursively if -r was specified.
47   */
48  void
diffdir(char * p1,char * p2,int flags)49  diffdir(char *p1, char *p2, int flags)
50  {
51  	struct dirent *dent1, **dp1, **edp1 = NULL, **dirp1 = NULL;
52  	struct dirent *dent2, **dp2, **edp2 = NULL, **dirp2 = NULL;
53  	size_t dirlen1, dirlen2;
54  	char path1[PATH_MAX], path2[PATH_MAX];
55  	int pos;
56  
57  	dirlen1 = strlcpy(path1, *p1 ? p1 : ".", sizeof(path1));
58  	if (dirlen1 >= sizeof(path1) - 1) {
59  		warnc(ENAMETOOLONG, "%s", p1);
60  		status |= 2;
61  		return;
62  	}
63  	if (path1[dirlen1 - 1] != '/') {
64  		path1[dirlen1++] = '/';
65  		path1[dirlen1] = '\0';
66  	}
67  	dirlen2 = strlcpy(path2, *p2 ? p2 : ".", sizeof(path2));
68  	if (dirlen2 >= sizeof(path2) - 1) {
69  		warnc(ENAMETOOLONG, "%s", p2);
70  		status |= 2;
71  		return;
72  	}
73  	if (path2[dirlen2 - 1] != '/') {
74  		path2[dirlen2++] = '/';
75  		path2[dirlen2] = '\0';
76  	}
77  
78  	/*
79  	 * Get a list of entries in each directory, skipping "excluded" files
80  	 * and sorting alphabetically.
81  	 */
82  	pos = scandir(path1, &dirp1, selectfile, alphasort);
83  	if (pos == -1) {
84  		if (errno == ENOENT && (Nflag || Pflag)) {
85  			pos = 0;
86  		} else {
87  			warn("%s", path1);
88  			goto closem;
89  		}
90  	}
91  	dp1 = dirp1;
92  	edp1 = dirp1 + pos;
93  
94  	pos = scandir(path2, &dirp2, selectfile, alphasort);
95  	if (pos == -1) {
96  		if (errno == ENOENT && Nflag) {
97  			pos = 0;
98  		} else {
99  			warn("%s", path2);
100  			goto closem;
101  		}
102  	}
103  	dp2 = dirp2;
104  	edp2 = dirp2 + pos;
105  
106  	/*
107  	 * If we were given a starting point, find it.
108  	 */
109  	if (start != NULL) {
110  		while (dp1 != edp1 && strcmp((*dp1)->d_name, start) < 0)
111  			dp1++;
112  		while (dp2 != edp2 && strcmp((*dp2)->d_name, start) < 0)
113  			dp2++;
114  	}
115  
116  	/*
117  	 * Iterate through the two directory lists, diffing as we go.
118  	 */
119  	while (dp1 != edp1 || dp2 != edp2) {
120  		dent1 = dp1 != edp1 ? *dp1 : NULL;
121  		dent2 = dp2 != edp2 ? *dp2 : NULL;
122  
123  		pos = dent1 == NULL ? 1 : dent2 == NULL ? -1 :
124  		    strcmp(dent1->d_name, dent2->d_name);
125  		if (pos == 0) {
126  			/* file exists in both dirs, diff it */
127  			diffit(dent1, path1, dirlen1, path2, dirlen2, flags);
128  			dp1++;
129  			dp2++;
130  		} else if (pos < 0) {
131  			/* file only in first dir, only diff if -N */
132  			if (Nflag) {
133  				diffit(dent1, path1, dirlen1, path2, dirlen2,
134  				    flags);
135  			} else {
136  				print_only(path1, dirlen1, dent1->d_name);
137  				status |= 1;
138  			}
139  			dp1++;
140  		} else {
141  			/* file only in second dir, only diff if -N or -P */
142  			if (Nflag || Pflag) {
143  				diffit(dent2, path1, dirlen1, path2, dirlen2,
144  				    flags);
145  			} else {
146  				print_only(path2, dirlen2, dent2->d_name);
147  				status |= 1;
148  			}
149  			dp2++;
150  		}
151  	}
152  
153  closem:
154  	if (dirp1 != NULL) {
155  		for (dp1 = dirp1; dp1 < edp1; dp1++)
156  			free(*dp1);
157  		free(dirp1);
158  	}
159  	if (dirp2 != NULL) {
160  		for (dp2 = dirp2; dp2 < edp2; dp2++)
161  			free(*dp2);
162  		free(dirp2);
163  	}
164  }
165  
166  /*
167   * Do the actual diff by calling either diffreg() or diffdir().
168   */
169  static void
diffit(struct dirent * dp,char * path1,size_t plen1,char * path2,size_t plen2,int flags)170  diffit(struct dirent *dp, char *path1, size_t plen1, char *path2, size_t plen2,
171      int flags)
172  {
173  	flags |= D_HEADER;
174  	strlcpy(path1 + plen1, dp->d_name, PATH_MAX - plen1);
175  	if (stat(path1, &stb1) != 0) {
176  		if (!(Nflag || Pflag) || errno != ENOENT) {
177  			warn("%s", path1);
178  			return;
179  		}
180  		flags |= D_EMPTY1;
181  		memset(&stb1, 0, sizeof(stb1));
182  	}
183  
184  	strlcpy(path2 + plen2, dp->d_name, PATH_MAX - plen2);
185  	if (stat(path2, &stb2) != 0) {
186  		if (!Nflag || errno != ENOENT) {
187  			warn("%s", path2);
188  			return;
189  		}
190  		flags |= D_EMPTY2;
191  		memset(&stb2, 0, sizeof(stb2));
192  		stb2.st_mode = stb1.st_mode;
193  	}
194  	if (stb1.st_mode == 0)
195  		stb1.st_mode = stb2.st_mode;
196  
197  	if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) {
198  		if (rflag)
199  			diffdir(path1, path2, flags);
200  		else
201  			printf("Common subdirectories: %s and %s\n",
202  			    path1, path2);
203  		return;
204  	}
205  	if (!S_ISREG(stb1.st_mode) && !S_ISDIR(stb1.st_mode))
206  		dp->d_status = D_SKIPPED1;
207  	else if (!S_ISREG(stb2.st_mode) && !S_ISDIR(stb2.st_mode))
208  		dp->d_status = D_SKIPPED2;
209  	else
210  		dp->d_status = diffreg(path1, path2, flags);
211  	print_status(dp->d_status, path1, path2, "");
212  }
213  
214  /*
215   * Returns 1 if the directory entry should be included in the
216   * diff, else 0.  Checks the excludes list.
217   */
218  static int
selectfile(const struct dirent * dp)219  selectfile(const struct dirent *dp)
220  {
221  	struct excludes *excl;
222  
223  	if (dp->d_fileno == 0)
224  		return (0);
225  
226  	/* always skip "." and ".." */
227  	if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
228  	    (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
229  		return (0);
230  
231  	/* check excludes list */
232  	for (excl = excludes_list; excl != NULL; excl = excl->next)
233  		if (fnmatch(excl->pattern, dp->d_name, FNM_PATHNAME) == 0)
234  			return (0);
235  
236  	return (1);
237  }
238