1 /* $OpenBSD: diffdir.c,v 1.30 2005/06/15 18:44:01 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 #include <sys/param.h>
24 #include <sys/stat.h>
25
26 #include <dirent.h>
27 #include <err.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <fnmatch.h>
31 #include <paths.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36
37 #include "diff.h"
38
39 __RCSID("$MirOS: src/usr.bin/diff/diffdir.c,v 1.2 2007/03/22 03:56:48 tg Exp $");
40
41 static int dircompare(const void *, const void *);
42 static int excluded(const char *);
43 static struct dirent **slurpdir(char *, char **, int);
44 static void diffit(struct dirent *, char *, size_t, char *, size_t);
45
46 #define d_status d_type /* we need to store status for -l */
47
48 /*
49 * Diff directory traversal. Will be called recursively if -r was specified.
50 */
51 void
diffdir(char * p1,char * p2)52 diffdir(char *p1, char *p2)
53 {
54 struct dirent **dirp1, **dirp2, **dp1, **dp2;
55 struct dirent *dent1, *dent2;
56 size_t dirlen1, dirlen2;
57 char path1[MAXPATHLEN], path2[MAXPATHLEN];
58 char *dirbuf1, *dirbuf2;
59 int pos;
60
61 dirlen1 = strlcpy(path1, *p1 ? p1 : ".", sizeof(path1));
62 if (dirlen1 >= sizeof(path1) - 1) {
63 warnx("%s: %s", p1, strerror(ENAMETOOLONG));
64 status = 2;
65 return;
66 }
67 if (path1[dirlen1 - 1] != '/') {
68 path1[dirlen1++] = '/';
69 path1[dirlen1] = '\0';
70 }
71 dirlen2 = strlcpy(path2, *p2 ? p2 : ".", sizeof(path2));
72 if (dirlen2 >= sizeof(path2) - 1) {
73 warnx("%s: %s", p2, strerror(ENAMETOOLONG));
74 status = 2;
75 return;
76 }
77 if (path2[dirlen2 - 1] != '/') {
78 path2[dirlen2++] = '/';
79 path2[dirlen2] = '\0';
80 }
81
82 /* get a list of the entries in each directory */
83 dp1 = dirp1 = slurpdir(path1, &dirbuf1, Nflag + Pflag);
84 dp2 = dirp2 = slurpdir(path2, &dirbuf2, Nflag);
85 if (dirp1 == NULL || dirp2 == NULL)
86 return;
87
88 /*
89 * If we were given a starting point, find it.
90 */
91 if (start != NULL) {
92 while (*dp1 != NULL && strcmp((*dp1)->d_name, start) < 0)
93 dp1++;
94 while (*dp2 != NULL && strcmp((*dp2)->d_name, start) < 0)
95 dp2++;
96 }
97
98 /*
99 * Iterate through the two directory lists, diffing as we go.
100 */
101 while (*dp1 != NULL || *dp2 != NULL) {
102 dent1 = *dp1;
103 dent2 = *dp2;
104
105 pos = dent1 == NULL ? 1 : dent2 == NULL ? -1 :
106 strcmp(dent1->d_name, dent2->d_name);
107 if (pos == 0) {
108 /* file exists in both dirs, diff it */
109 diffit(dent1, path1, dirlen1, path2, dirlen2);
110 dp1++;
111 dp2++;
112 } else if (pos < 0) {
113 /* file only in first dir, only diff if -N */
114 if (Nflag)
115 diffit(dent1, path1, dirlen1, path2, dirlen2);
116 else if (lflag)
117 dent1->d_status |= D_ONLY;
118 else
119 print_only(path1, dirlen1, dent1->d_name);
120 dp1++;
121 } else {
122 /* file only in second dir, only diff if -N or -P */
123 if (Nflag || Pflag)
124 diffit(dent2, path1, dirlen1, path2, dirlen2);
125 else if (lflag)
126 dent2->d_status |= D_ONLY;
127 else
128 print_only(path2, dirlen2, dent2->d_name);
129 dp2++;
130 }
131 }
132 if (lflag) {
133 path1[dirlen1] = '\0';
134 path2[dirlen2] = '\0';
135 for (dp1 = dirp1; (dent1 = *dp1) != NULL; dp1++) {
136 print_status(dent1->d_status, path1, path2,
137 dent1->d_name);
138 }
139 for (dp2 = dirp2; (dent2 = *dp2) != NULL; dp2++) {
140 if (dent2->d_status == D_ONLY)
141 print_status(dent2->d_status, path2, NULL,
142 dent2->d_name);
143 }
144 }
145
146 if (dirbuf1 != NULL) {
147 free(dirp1);
148 free(dirbuf1);
149 }
150 if (dirbuf2 != NULL) {
151 free(dirp2);
152 free(dirbuf2);
153 }
154 }
155
156 /*
157 * Read in a whole directory's worth of struct dirents, culling
158 * out the "excluded" ones.
159 * Returns an array of struct dirent *'s that point into the buffer
160 * returned via bufp. Caller is responsible for free()ing both of these.
161 */
162 static struct dirent **
slurpdir(char * path,char ** bufp,int enoentok)163 slurpdir(char *path, char **bufp, int enoentok)
164 {
165 char *buf, *ebuf, *cp;
166 size_t bufsize, have, need;
167 long base;
168 int fd, nbytes, entries;
169 struct stat sb;
170 struct dirent **dirlist, *dp;
171
172 *bufp = NULL;
173 if ((fd = open(path, O_RDONLY, 0644)) == -1) {
174 static struct dirent *dummy;
175
176 if (!enoentok || errno != ENOENT) {
177 warn("%s", path);
178 return (NULL);
179 }
180 return (&dummy);
181 }
182 if (fstat(fd, &sb) == -1) {
183 warn("%s", path);
184 close(fd);
185 return (NULL);
186 }
187
188 need = roundup(sb.st_blksize, sizeof(struct dirent));
189 have = bufsize = roundup(MAX(sb.st_size, sb.st_blksize),
190 sizeof(struct dirent)) + need;
191 ebuf = buf = emalloc(bufsize);
192
193 do {
194 if (have < need) {
195 bufsize += need;
196 have += need;
197 cp = erealloc(buf, bufsize);
198 ebuf = cp + (ebuf - buf);
199 buf = cp;
200 }
201 nbytes = getdirentries(fd, ebuf, have, &base);
202 if (nbytes == -1) {
203 warn("%s", path);
204 free(buf);
205 close(fd);
206 return (NULL);
207 }
208 ebuf += nbytes;
209 have -= nbytes;
210 } while (nbytes != 0);
211 close(fd);
212
213 /*
214 * We now have all the directory entries in our buffer.
215 * However, in order to easily sort them we need to convert
216 * the buffer into an array.
217 */
218 for (entries = 0, cp = buf; cp < ebuf; ) {
219 dp = (struct dirent *)cp;
220 if (dp->d_fileno != 0)
221 entries++;
222 if (dp->d_reclen <= 0)
223 break;
224 cp += dp->d_reclen;
225 }
226 dirlist = emalloc(sizeof(struct dirent *) * (entries + 1));
227 for (entries = 0, cp = buf; cp < ebuf; ) {
228 dp = (struct dirent *)cp;
229 if (dp->d_fileno != 0 && !excluded(dp->d_name)) {
230 dp->d_status = 0;
231 dirlist[entries++] = dp;
232 }
233 if (dp->d_reclen <= 0)
234 break;
235 cp += dp->d_reclen;
236 }
237 dirlist[entries] = NULL;
238
239 qsort(dirlist, entries, sizeof(struct dirent *), dircompare);
240
241 *bufp = buf;
242 return (dirlist);
243 }
244
245 /*
246 * Compare d_name in two dirent structures; for qsort(3).
247 */
248 static int
dircompare(const void * vp1,const void * vp2)249 dircompare(const void *vp1, const void *vp2)
250 {
251 const struct dirent *dp1 = *((struct dirent * const *) vp1);
252 const struct dirent *dp2 = *((struct dirent * const *) vp2);
253
254 return (strcmp(dp1->d_name, dp2->d_name));
255 }
256
257 /*
258 * Do the actual diff by calling either diffreg() or diffdir().
259 */
260 static void
diffit(struct dirent * dp,char * path1,size_t plen1,char * path2,size_t plen2)261 diffit(struct dirent *dp, char *path1, size_t plen1, char *path2, size_t plen2)
262 {
263 int flags = D_HEADER;
264
265 strlcpy(path1 + plen1, dp->d_name, MAXPATHLEN - plen1);
266 if (stat(path1, &stb1) != 0) {
267 if (!(Nflag || Pflag) || errno != ENOENT) {
268 warn("%s", path1);
269 return;
270 }
271 flags |= D_EMPTY1;
272 memset(&stb1, 0, sizeof(stb1));
273 }
274
275 strlcpy(path2 + plen2, dp->d_name, MAXPATHLEN - plen2);
276 if (stat(path2, &stb2) != 0) {
277 if (!Nflag || errno != ENOENT) {
278 warn("%s", path2);
279 return;
280 }
281 flags |= D_EMPTY2;
282 memset(&stb2, 0, sizeof(stb2));
283 stb2.st_mode = stb1.st_mode;
284 }
285 if (stb1.st_mode == 0)
286 stb1.st_mode = stb2.st_mode;
287
288 if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) {
289 if (rflag)
290 diffdir(path1, path2);
291 else if (lflag)
292 dp->d_status |= D_COMMON;
293 else
294 printf("Common subdirectories: %s and %s\n",
295 path1, path2);
296 return;
297 }
298 if (!S_ISREG(stb1.st_mode) && !S_ISDIR(stb1.st_mode))
299 dp->d_status = D_SKIPPED1;
300 else if (!S_ISREG(stb2.st_mode) && !S_ISDIR(stb2.st_mode))
301 dp->d_status = D_SKIPPED2;
302 else
303 dp->d_status = diffreg(path1, path2, flags);
304 if (!lflag)
305 print_status(dp->d_status, path1, path2, NULL);
306 }
307
308 /*
309 * Exclude the given directory entry?
310 */
311 static int
excluded(const char * entry)312 excluded(const char *entry)
313 {
314 struct excludes *excl;
315
316 /* always skip "." and ".." */
317 if (entry[0] == '.' &&
318 (entry[1] == '\0' || (entry[1] == '.' && entry[2] == '\0')))
319 return (1);
320
321 /* check excludes list */
322 for (excl = excludes_list; excl != NULL; excl = excl->next)
323 if (fnmatch(excl->pattern, entry, FNM_PATHNAME) == 0)
324 return (1);
325
326 return (0);
327 }
328