1 |
|
|
/* $OpenBSD: diffdir.c,v 1.46 2017/08/28 15:33:27 millert Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2003, 2010 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/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 |
49 |
|
|
diffdir(char *p1, char *p2, int flags) |
50 |
|
|
{ |
51 |
|
|
struct dirent *dent1, **dp1, **edp1, **dirp1 = NULL; |
52 |
|
|
struct dirent *dent2, **dp2, **edp2, **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 |
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 |
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 |
|
|
} |