1 /* $OpenBSD: diffdir.c,v 1.35 2009/10/27 23:59:37 deraadt 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 <sys/statfs.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <unistd.h> 37 #include <assert.h> 38 39 #include "diff.h" 40 #include "xmalloc.h" 41 42 struct diffdirent { 43 int d_status; 44 int d_fileno; 45 unsigned short d_reclen; 46 char d_name[1]; 47 }; 48 49 #define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) 50 51 static int dircompare(const void *, const void *); 52 static int excluded(const char *); 53 static struct diffdirent **slurpdir(char *, char **, int); 54 static void diffit(struct diffdirent *, char *, size_t, char *, size_t, int); 55 56 /* 57 * Diff directory traversal. Will be called recursively if -r was specified. 58 */ 59 void 60 diffdir(char *p1, char *p2, int flags) 61 { 62 struct diffdirent **dirp1, **dirp2, **dp1, **dp2; 63 struct diffdirent *dent1, *dent2; 64 size_t dirlen1, dirlen2; 65 char path1[MAXPATHLEN], path2[MAXPATHLEN]; 66 char *dirbuf1, *dirbuf2; 67 int pos; 68 69 dirlen1 = strlcpy(path1, *p1 ? p1 : ".", sizeof(path1)); 70 if (dirlen1 >= sizeof(path1) - 1) { 71 warnx("%s: %s", p1, strerror(ENAMETOOLONG)); 72 status = 2; 73 return; 74 } 75 if (path1[dirlen1 - 1] != '/') { 76 path1[dirlen1++] = '/'; 77 path1[dirlen1] = '\0'; 78 } 79 dirlen2 = strlcpy(path2, *p2 ? p2 : ".", sizeof(path2)); 80 if (dirlen2 >= sizeof(path2) - 1) { 81 warnx("%s: %s", p2, strerror(ENAMETOOLONG)); 82 status = 2; 83 return; 84 } 85 if (path2[dirlen2 - 1] != '/') { 86 path2[dirlen2++] = '/'; 87 path2[dirlen2] = '\0'; 88 } 89 90 /* get a list of the entries in each directory */ 91 dp1 = dirp1 = slurpdir(path1, &dirbuf1, Nflag + Pflag); 92 dp2 = dirp2 = slurpdir(path2, &dirbuf2, Nflag); 93 if (dirp1 == NULL || dirp2 == NULL) 94 return; 95 96 /* 97 * If we were given a starting point, find it. 98 */ 99 if (start != NULL) { 100 while (*dp1 != NULL && strcmp((*dp1)->d_name, start) < 0) 101 dp1++; 102 while (*dp2 != NULL && strcmp((*dp2)->d_name, start) < 0) 103 dp2++; 104 } 105 106 /* 107 * Iterate through the two directory lists, diffing as we go. 108 */ 109 while (*dp1 != NULL || *dp2 != NULL) { 110 dent1 = *dp1; 111 dent2 = *dp2; 112 113 pos = dent1 == NULL ? 1 : dent2 == NULL ? -1 : 114 strcmp(dent1->d_name, dent2->d_name); 115 if (pos == 0) { 116 /* file exists in both dirs, diff it */ 117 diffit(dent1, path1, dirlen1, path2, dirlen2, flags); 118 dp1++; 119 dp2++; 120 } else if (pos < 0) { 121 /* file only in first dir, only diff if -N */ 122 if (Nflag) 123 diffit(dent1, path1, dirlen1, path2, dirlen2, 124 flags); 125 else if (lflag) 126 dent1->d_status |= D_ONLY; 127 else 128 print_only(path1, dirlen1, dent1->d_name); 129 dp1++; 130 } else { 131 /* file only in second dir, only diff if -N or -P */ 132 if (Nflag || Pflag) 133 diffit(dent2, path1, dirlen1, path2, dirlen2, 134 flags); 135 else if (lflag) 136 dent2->d_status |= D_ONLY; 137 else 138 print_only(path2, dirlen2, dent2->d_name); 139 dp2++; 140 } 141 } 142 if (lflag) { 143 path1[dirlen1] = '\0'; 144 path2[dirlen2] = '\0'; 145 for (dp1 = dirp1; (dent1 = *dp1) != NULL; dp1++) { 146 print_status(dent1->d_status, path1, path2, 147 dent1->d_name); 148 } 149 for (dp2 = dirp2; (dent2 = *dp2) != NULL; dp2++) { 150 if (dent2->d_status == D_ONLY) 151 print_status(dent2->d_status, path2, NULL, 152 dent2->d_name); 153 } 154 } 155 156 if (dirbuf1 != NULL) { 157 xfree(dirp1); 158 xfree(dirbuf1); 159 } 160 if (dirbuf2 != NULL) { 161 xfree(dirp2); 162 xfree(dirbuf2); 163 } 164 } 165 166 static int 167 getdiffdirentries(int fd, char *buf, int nbytes) 168 { 169 char *read_de; 170 int dentsbytes_actual; 171 int i, readlen = nbytes * 11 / 16; /* worst growth */ 172 int written = 0; 173 174 lseek(fd, (off_t)0, SEEK_CUR); 175 176 if(!(read_de = malloc(readlen))) 177 errx(1, "getdiffdirentries: can't malloc"); 178 179 #ifndef __minix 180 dentsbytes_actual = getdents(fd, (struct dirent *) read_de, readlen); 181 #else /* __minix */ 182 dentsbytes_actual = getdents(fd, read_de, readlen); 183 #endif /* __minix */ 184 185 if(dentsbytes_actual <= 0) 186 return dentsbytes_actual; 187 188 while(dentsbytes_actual > 0) { 189 int namelen; 190 struct diffdirent *dde = (struct diffdirent *) buf; 191 struct dirent *de = (struct dirent *) read_de; 192 dde->d_status = 0; 193 dde->d_fileno = de->d_ino; 194 namelen = strlen(de->d_name); 195 dde->d_reclen = namelen + 1 + sizeof(*dde) - sizeof(dde->d_name); 196 strcpy(dde->d_name, de->d_name); 197 dde->d_status = 0; 198 assert(dentsbytes_actual >= de->d_reclen); 199 dentsbytes_actual -= de->d_reclen; 200 buf += dde->d_reclen; 201 read_de += de->d_reclen; 202 written += dde->d_reclen; 203 assert(written <= nbytes); 204 #if 0 205 fprintf(stderr, "orig: inode %d len %d; made: inode %d len %d\n", 206 de->d_ino, de->d_reclen, dde->d_fileno, dde->d_reclen); 207 #endif 208 } 209 210 return written; 211 } 212 213 /* 214 * Read in a whole directory's worth of struct dirents, culling 215 * out the "excluded" ones. 216 * Returns an array of struct diffdirent *'s that point into the buffer 217 * returned via bufp. Caller is responsible for free()ing both of these. 218 */ 219 static struct diffdirent ** 220 slurpdir(char *path, char **bufp, int enoentok) 221 { 222 char *buf, *ebuf, *cp; 223 size_t bufsize, have, need; 224 int fd, nbytes, entries; 225 #ifdef __minix 226 struct statfs statfs; 227 #endif 228 struct stat sb; 229 int blocksize; 230 struct diffdirent **dirlist, *dp; 231 232 *bufp = NULL; 233 if ((fd = open(path, O_RDONLY, 0644)) == -1) { 234 static struct diffdirent *dummy; 235 236 if (!enoentok || errno != ENOENT) { 237 warn("%s", path); 238 return (NULL); 239 } 240 return (&dummy); 241 } 242 if ( 243 #ifdef __minix 244 fstatfs(fd, &statfs) < 0 || 245 #endif 246 fstat(fd, &sb) < 0) { 247 warn("%s", path); 248 close(fd); 249 return (NULL); 250 } 251 #ifdef __minix 252 blocksize = statfs.f_bsize; 253 #else 254 blocksize = sb.st_blksize; 255 #endif 256 257 need = roundup(blocksize, sizeof(struct dirent)); 258 have = bufsize = roundup(MAX(sb.st_size, blocksize), 259 sizeof(struct dirent)) + need; 260 ebuf = buf = xmalloc(bufsize); 261 262 do { 263 if (have < need) { 264 bufsize += need; 265 have += need; 266 cp = xrealloc(buf, 1, bufsize); 267 ebuf = cp + (ebuf - buf); 268 buf = cp; 269 } 270 nbytes = getdiffdirentries(fd, ebuf, have); 271 if (nbytes == -1) { 272 warn("%s", path); 273 xfree(buf); 274 close(fd); 275 return (NULL); 276 } 277 ebuf += nbytes; 278 have -= nbytes; 279 } while (nbytes != 0); 280 close(fd); 281 282 /* 283 * We now have all the directory entries in our buffer. 284 * However, in order to easily sort them we need to convert 285 * the buffer into an array. 286 */ 287 for (entries = 0, cp = buf; cp < ebuf; ) { 288 dp = (struct diffdirent *)cp; 289 if (dp->d_fileno != 0) 290 entries++; 291 if (dp->d_reclen <= 0) 292 break; 293 cp += dp->d_reclen; 294 } 295 dirlist = xcalloc(sizeof(*dirlist), entries + 1); 296 for (entries = 0, cp = buf; cp < ebuf; ) { 297 dp = (struct diffdirent *)cp; 298 if (dp->d_fileno != 0 && !excluded(dp->d_name)) { 299 dp->d_status = 0; 300 dirlist[entries++] = dp; 301 } 302 if (dp->d_reclen <= 0) 303 break; 304 cp += dp->d_reclen; 305 } 306 dirlist[entries] = NULL; 307 308 qsort(dirlist, entries, sizeof(struct diffdirent *), dircompare); 309 310 *bufp = buf; 311 return (dirlist); 312 } 313 314 /* 315 * Compare d_name in two dirent structures; for qsort(3). 316 */ 317 static int 318 dircompare(const void *vp1, const void *vp2) 319 { 320 struct diffdirent *dp1 = *((struct diffdirent **) vp1); 321 struct diffdirent *dp2 = *((struct diffdirent **) vp2); 322 323 return (strcmp(dp1->d_name, dp2->d_name)); 324 } 325 326 /* 327 * Do the actual diff by calling either diffreg() or diffdir(). 328 */ 329 static void 330 diffit(struct diffdirent *dp, char *path1, size_t plen1, char *path2, size_t plen2, 331 int flags) 332 { 333 flags |= D_HEADER; 334 strlcpy(path1 + plen1, dp->d_name, MAXPATHLEN - plen1); 335 if (stat(path1, &stb1) != 0) { 336 if (!(Nflag || Pflag) || errno != ENOENT) { 337 warn("%s", path1); 338 return; 339 } 340 flags |= D_EMPTY1; 341 memset(&stb1, 0, sizeof(stb1)); 342 } 343 344 strlcpy(path2 + plen2, dp->d_name, MAXPATHLEN - plen2); 345 if (stat(path2, &stb2) != 0) { 346 if (!Nflag || errno != ENOENT) { 347 warn("%s", path2); 348 return; 349 } 350 flags |= D_EMPTY2; 351 memset(&stb2, 0, sizeof(stb2)); 352 stb2.st_mode = stb1.st_mode; 353 } 354 if (stb1.st_mode == 0) 355 stb1.st_mode = stb2.st_mode; 356 357 if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) { 358 if (rflag) 359 diffdir(path1, path2, flags); 360 else if (lflag) 361 dp->d_status |= D_COMMON; 362 else 363 printf("Common subdirectories: %s and %s\n", 364 path1, path2); 365 return; 366 } 367 if (!S_ISREG(stb1.st_mode) && !S_ISDIR(stb1.st_mode)) 368 dp->d_status = D_SKIPPED1; 369 else if (!S_ISREG(stb2.st_mode) && !S_ISDIR(stb2.st_mode)) 370 dp->d_status = D_SKIPPED2; 371 else 372 dp->d_status = diffreg(path1, path2, flags); 373 if (!lflag) 374 print_status(dp->d_status, path1, path2, NULL); 375 } 376 377 /* 378 * Exclude the given directory entry? 379 */ 380 static int 381 excluded(const char *entry) 382 { 383 struct excludes *excl; 384 385 /* always skip "." and ".." */ 386 if (entry[0] == '.' && 387 (entry[1] == '\0' || (entry[1] == '.' && entry[2] == '\0'))) 388 return (1); 389 390 /* check excludes list */ 391 for (excl = excludes_list; excl != NULL; excl = excl->next) 392 if (fnmatch(excl->pattern, entry, FNM_PATHNAME) == 0) 393 return (1); 394 395 return (0); 396 } 397