=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/diff/diffdir.c,v retrieving revision 1.18 retrieving revision 1.19 diff -u -r1.18 -r1.19 --- src/usr.bin/diff/diffdir.c 2003/07/06 02:11:12 1.18 +++ src/usr.bin/diff/diffdir.c 2003/07/06 20:48:59 1.19 @@ -1,365 +1,292 @@ -/* $OpenBSD: diffdir.c,v 1.18 2003/07/06 02:11:12 millert Exp $ */ +/* $OpenBSD: diffdir.c,v 1.19 2003/07/06 20:48:59 millert Exp $ */ /* - * Copyright (C) Caldera International Inc. 2001-2002. - * All rights reserved. + * Copyright (c) 2003 Todd C. Miller * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code and documentation must retain the above - * copyright notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed or owned by Caldera - * International, Inc. - * 4. Neither the name of Caldera International, Inc. nor the names of other - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. * - * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA - * INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE LIABLE FOR ANY DIRECT, - * INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. */ -#include -#include +#ifndef lint +static const char rcsid[] = "$OpenBSD: diffdir.c,v 1.19 2003/07/06 20:48:59 millert Exp $"; +#endif /* not lint */ +#include +#include + #include +#include #include #include +#include +#include #include #include #include #include #include "diff.h" -#include "pathnames.h" -#if 0 -static const char sccsid[] = "@(#)diffdir.c 4.12 (Berkeley) 4/30/89"; -#endif +static int dircompare(const void *, const void *); +static int excluded(const char *); +static struct dirent **slurpdir(char *, char **); +static void diffit(struct dirent *, char *, size_t, char *, size_t); /* - * diff - directory comparison + * Diff directory traveral. Will be called recursively if -r was specified. */ -#define d_flags d_ino +void +diffdir(char *p1, char *p2) +{ + struct dirent **dirp1, **dirp2, **dp1, **dp2; + struct dirent *dent1, *dent2; + size_t dirlen1, dirlen2; + char path1[MAXPATHLEN], path2[MAXPATHLEN]; + char *dirbuf1, *dirbuf2; + int pos; -#define DIRECT 1 /* Directory */ + dirlen1 = strlcpy(path1, *p1 ? p1 : ".", sizeof(path1)); + if (dirlen1 >= sizeof(path1) - 1) { + warnx("%s: %s", p1, strerror(ENAMETOOLONG)); + status = 2; + return; + } + if (path1[dirlen1 - 1] != '/') { + path1[dirlen1++] = '/'; + path1[dirlen1] = '\0'; + } + dirlen2 = strlcpy(path2, *p2 ? p2 : ".", sizeof(path2)); + if (dirlen2 >= sizeof(path2) - 1) { + warnx("%s: %s", p2, strerror(ENAMETOOLONG)); + status = 2; + return; + } + if (path2[dirlen2 - 1] != '/') { + path2[dirlen2++] = '/'; + path2[dirlen2] = '\0'; + } -struct dir { - u_long d_ino; - short d_reclen; - short d_namlen; - char *d_entry; -}; + /* get a list of the entries in each directory */ + dp1 = dirp1 = slurpdir(path1, &dirbuf1); + dp2 = dirp2 = slurpdir(path2, &dirbuf2); + if (dirp1 == NULL || dirp2 == NULL) + return; -static int dirstatus; /* exit status from diffdir */ -static char title[2 * BUFSIZ]; + /* + * If we were given a starting point, find it. + */ + if (start != NULL) { + while (*dp1 != NULL && strcmp((*dp1)->d_name, start) < 0) + dp1++; + while (*dp2 != NULL && strcmp((*dp2)->d_name, start) < 0) + dp2++; + } + /* + * Iterate through the two directory lists, diffing as we go. + */ + while (*dp1 != NULL || *dp2 != NULL) { + dent1 = *dp1; + dent2 = *dp2; -static struct dir *setupdir(char *); -static int ascii(int); -static void compare(struct dir *); -static void calldiff(void); -static void setfile(char **fpp, char **epp, char *file); -static int useless(char *); -static void only(struct dir *dp, int which); -static int entcmp(const void *, const void *); - -void -diffdir(char **argv) -{ - struct dir *dir1, *dir2; - struct dir *d1, *d2; - int i, cmp; - - if (opt == D_IFDEF) - warnx("can't specify -I with directories"); - if (opt == D_EDIT && sflag) - warnx("warning: shouldn't give -s with -e"); - strlcpy(title, "diff ", sizeof title); - for (i = 1; diffargv[i + 2]; i++) { - if (!strcmp(diffargv[i], "-")) - continue; /* was -S, dont look silly */ - strlcat(title, diffargv[i], sizeof title); - strlcat(title, " ", sizeof title); - } - setfile(&file1, &efile1, file1); - setfile(&file2, &efile2, file2); - argv[0] = file1; - argv[1] = file2; - dir1 = setupdir(file1); - dir2 = setupdir(file2); - d1 = dir1; - d2 = dir2; - while (d1->d_entry != 0 || d2->d_entry != 0) { - if (d1->d_entry && useless(d1->d_entry)) { - d1++; - continue; - } - if (d2->d_entry && useless(d2->d_entry)) { - d2++; - continue; - } - if (d1->d_entry == 0) - cmp = 1; - else if (d2->d_entry == 0) - cmp = -1; - else - cmp = strcmp(d1->d_entry, d2->d_entry); - if (cmp < 0) { - if (opt == D_NORMAL || opt == D_CONTEXT || - opt == D_UNIFIED) - only(d1, 1); - d1++; - dirstatus |= 1; - } else if (cmp == 0) { - compare(d1); - d1++; - d2++; + pos = dent1 == NULL ? 1 : dent2 == NULL ? -1 : + strcmp(dent1->d_name, dent2->d_name); + if (pos == 0) { + /* file exists in both dirs, diff it */ + diffit(dent1, path1, dirlen1, path2, dirlen2); + dp1++; + dp2++; + } else if (pos < 0) { + /* file only in first dir, only diff if -N */ + if (Nflag) + diffit(dent1, path1, dirlen1, path2, dirlen2); + else if (format == D_NORMAL || format == D_CONTEXT || + format == D_UNIFIED) + /* XXX GNU diff always prints this XXX */ + printf("Only in %.*s: %s\n", (int)(dirlen1 - 1), + path1, dent1->d_name); + dp1++; } else { - if (opt == D_NORMAL || opt == D_CONTEXT || - opt == D_UNIFIED) - only(d2, 2); - d2++; - dirstatus |= 1; + /* file only in second dir, only diff if -N */ + if (Nflag) + diffit(dent2, path1, dirlen1, path2, dirlen2); + else if (format == D_NORMAL || format == D_CONTEXT || + format == D_UNIFIED) + /* XXX GNU diff always prints this XXX */ + printf("Only in %.*s: %s\n", (int)(dirlen2 - 1), + path2, dent2->d_name); + dp2++; } } - if (rflag) { - for (d1 = dir1; d1->d_entry; d1++) { - if ((d1->d_flags & DIRECT) == 0) - continue; - strlcpy(efile1, d1->d_entry, - file1 + MAXPATHLEN - efile1); - strlcpy(efile2, d1->d_entry, - file2 + MAXPATHLEN - efile2); - calldiff(); - } + + if (dirbuf1 != NULL) { + free(dirp1); + free(dirbuf1); } - status = dirstatus; + if (dirbuf2 != NULL) { + free(dirp2); + free(dirbuf2); + } } -void -setfile(char **fpp, char **epp, char *file) +/* + * Read in a whole directory's worth of struct dirents, culling + * out the "excluded" ones. + * Returns an array of struct dirent *'s that point into the buffer + * returned via bufp. Caller is responsible for free()ing both of these. + */ +static struct dirent ** +slurpdir(char *path, char **bufp) { - char *cp; - size_t len; + char *buf, *ebuf, *cp; + size_t bufsize; + long base; + int fd, nbytes, entries; + struct stat sb; + struct dirent **dirlist, *dp; - if (*file == '\0') - file = "."; - *fpp = emalloc(MAXPATHLEN); - len = strlcpy(*fpp, file, MAXPATHLEN); - if (len >= MAXPATHLEN - 1) - errorx("%s: %s", file, strerror(ENAMETOOLONG)); - cp = *fpp + len - 1; - if (*cp == '/') - ++cp; - else { - *++cp = '/'; - *++cp = '\0'; + *bufp = NULL; + if ((fd = open(path, O_RDONLY, 0644)) == -1) { + static struct dirent *dummy; + + if (!Nflag) { + warn("%s", path); + return (NULL); + } + return (&dummy); } - *epp = cp; -} + fstat(fd, &sb); -void -only(struct dir *dp, int which) -{ - char *file = which == 1 ? file1 : file2; - char *efile = which == 1 ? efile1 : efile2; + bufsize = sb.st_size; + if (bufsize < sb.st_blksize) + bufsize = sb.st_blksize; + buf = emalloc(bufsize); - printf("Only in %.*s: %s\n", (int)(efile - file - 1), file, dp->d_entry); -} + nbytes = getdirentries(fd, buf, bufsize, &base); + if (nbytes <= 0) { + free(buf); + warn("%s", path); + return (NULL); + } + ebuf = buf + nbytes; + close(fd); -struct dir * -setupdir(char *cp) -{ - struct dir *dp, *ep; - struct dirent *rp; - int nitems; - DIR *dirp; - - dirp = opendir(cp); - if (dirp == NULL) - error("%s", cp); - nitems = 0; - dp = emalloc(sizeof(struct dir)); - while ((rp = readdir(dirp))) { - ep = &dp[nitems++]; - ep->d_reclen = rp->d_reclen; - ep->d_namlen = rp->d_namlen; - ep->d_entry = 0; - ep->d_flags = 0; - if (ep->d_namlen > 0) { - ep->d_entry = emalloc(ep->d_namlen + 1); - strlcpy(ep->d_entry, rp->d_name, ep->d_namlen + 1); - } - dp = erealloc(dp, (nitems + 1) * sizeof(struct dir)); + /* + * We now have all the directory entries in our buffer. + * However, in order to easily sort them we need to convert + * the buffer into an array. + */ + for (entries = 0, cp = buf; cp < ebuf; ) { + dp = (struct dirent *)cp; + if (dp->d_fileno != 0 && dp->d_type != DT_WHT) + entries++; + if (dp->d_reclen <= 0) + break; + cp += dp->d_reclen; } - dp[nitems].d_entry = 0; /* delimiter */ - closedir(dirp); - qsort(dp, nitems, sizeof(struct dir), entcmp); - return (dp); + dirlist = emalloc(sizeof(struct dirent *) * (entries + 1)); + for (entries = 0, cp = buf; cp < ebuf; ) { + dp = (struct dirent *)cp; + if (dp->d_fileno != 0 && dp->d_type != DT_WHT && + !excluded(dp->d_name)) + dirlist[entries++] = dp; + if (dp->d_reclen <= 0) + break; + cp += dp->d_reclen; + } + dirlist[entries] = NULL; + + qsort(dirlist, entries, sizeof(struct dir *), dircompare); + + *bufp = buf; + return (dirlist); } +/* + * Compare d_name in two dirent structures; for qsort(3). + */ static int -entcmp(const void *v1, const void *v2) +dircompare(const void *vp1, const void *vp2) { - const struct dir *d1, *d2; + struct dirent *dp1 = *((struct dirent **) vp1); + struct dirent *dp2 = *((struct dirent **) vp2); - d1 = v1; - d2 = v2; - return (strcmp(d1->d_entry, d2->d_entry)); + return (strcmp(dp1->d_name, dp2->d_name)); } +/* + * Do the actual diff by calling either diffreg() or diffdir(). + */ static void -compare(struct dir *dp) +diffit(struct dirent *dp, char *path1, size_t plen1, char *path2, size_t plen2) { - char buf1[BUFSIZ], buf2[BUFSIZ]; - int i, j, f1, f2, fmt1, fmt2; - struct stat stb1, stb2; + int flags = D_HEADER; - strlcpy(efile1, dp->d_entry, file1 + MAXPATHLEN - efile1); - strlcpy(efile2, dp->d_entry, file2 + MAXPATHLEN - efile2); - f1 = open(file1, 0); - if (f1 < 0) { - warn("%s", file1); - return; + strlcpy(path1 + plen1, dp->d_name, MAXPATHLEN - plen1); + if (stat(path1, &stb1) != 0) { + if (!Nflag || errno != ENOENT) { + warn("%s", path1); + return; + } + flags |= D_EMPTY1; + memset(&stb1, 0, sizeof(stb1)); } - f2 = open(file2, 0); - if (f2 < 0) { - warn("%s", file2); - close(f1); - return; - } - fstat(f1, &stb1); - fstat(f2, &stb2); - fmt1 = stb1.st_mode & S_IFMT; - fmt2 = stb2.st_mode & S_IFMT; - if (fmt1 != S_IFREG || fmt2 != S_IFREG) { - if (fmt1 == fmt2) { - if (fmt1 != S_IFDIR && stb1.st_rdev == stb2.st_rdev) - goto same; - if (fmt1 == S_IFDIR) { - dp->d_flags = DIRECT; - if (opt == D_EDIT) - goto closem; - printf("Common subdirectories: %s and %s\n", - file1, file2); - goto closem; - } + + strlcpy(path2 + plen2, dp->d_name, MAXPATHLEN - plen2); + if (stat(path2, &stb2) != 0) { + if (!Nflag || errno != ENOENT) { + warn("%s", path2); + return; } - goto notsame; + flags |= D_EMPTY2; + memset(&stb2, 0, sizeof(stb2)); + stb2.st_mode = stb1.st_mode; } - if (stb1.st_size != stb2.st_size) - goto notsame; - for (;;) { - i = read(f1, buf1, BUFSIZ); - j = read(f2, buf2, BUFSIZ); - if (i < 0 || j < 0 || i != j) - goto notsame; - if (i == 0 && j == 0) - goto same; - for (j = 0; j < i; j++) - if (buf1[j] != buf2[j]) - goto notsame; - } -same: - if (sflag != 0) - printf("Files %s and %s are identical\n", file1, file2); - goto closem; -notsame: - dirstatus |= 1; - if (!ascii(f1) || !ascii(f2)) { - if (opt == D_NORMAL || opt == D_CONTEXT || opt == D_UNIFIED) - printf("Binary files %s and %s differ\n", - file1, file2); - goto closem; - } - close(f1); - close(f2); - anychange = 1; - if (opt == D_EDIT) { - printf("ed - %s << '-*-END-*-'\n", dp->d_entry); - calldiff(); - } else { - printf("%s%s %s\n", title, file1, file2); - calldiff(); - } - if (opt == D_EDIT) - printf("w\nq\n-*-END-*-\n"); - return; -closem: - close(f1); - close(f2); -} + if (stb1.st_mode == 0) + stb1.st_mode = stb2.st_mode; -static void -calldiff(void) -{ - int lstatus; - pid_t pid; - - fflush(stdout); - pid = fork(); - if (pid == -1) - errorx("No more processes"); - if (pid == 0) { - execv(_PATH_DIFF, diffargv); - error("%s", _PATH_DIFF); + if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) { + /* XXX GNU diff always prints this for dirs XXX */ + if (format != D_EDIT) + printf("Common subdirectories: %s and %s\n", + path1, path2); + if (rflag) + diffdir(path1, path2); + return; } - while (wait(&lstatus) != pid) - continue; - /* - if ((lstatus >> 8) >= 2) - done(0); - */ - dirstatus |= lstatus >> 8; + diffreg(path1, path2, flags); } -int -ascii(int f) +/* + * Exclude the given directory entry? + */ +static int +excluded(const char *entry) { - char buf[BUFSIZ], *cp; - int cnt; + struct excludes *excl; - if (aflag) + /* always skip "." and ".." */ + if (entry[0] == '.' && + (entry[1] == '\0' || (entry[1] == '.' && entry[2] == '\0'))) return (1); - lseek(f, (off_t)0, SEEK_SET); - cnt = read(f, buf, BUFSIZ); - cp = buf; - while (--cnt >= 0) - if (*cp++ & 0200) - return (0); - return (1); -} + /* check excludes list */ + for (excl = excludes_list; excl != NULL; excl = excl->next) + if (fnmatch(excl->pattern, entry, FNM_PATHNAME) == 0) + return (1); -/* - * THIS IS CRUDE. - */ -int -useless(char *cp) -{ - if (cp[0] == '.') { - if (cp[1] == '\0') - return (1); /* directory "." */ - if (cp[1] == '.' && cp[2] == '\0') - return (1); /* directory ".." */ - } - if (start && strcmp(start, cp) > 0) - return (1); return (0); }