diff options
author | Dag-Erling Smørgrav <des@FreeBSD.org> | 2025-06-20 11:10:35 +0000 |
---|---|---|
committer | Dag-Erling Smørgrav <des@FreeBSD.org> | 2025-06-20 11:10:35 +0000 |
commit | 42092e1b6625b8226de5f34d22b9a96c713626cb (patch) | |
tree | aab1d1a1ee4973b560699f795786670de4c8b7e7 /usr.bin/diff/diffdir.c | |
parent | deeebfdecab56729fa898271ae53d01c8e156302 (diff) |
Diffstat (limited to 'usr.bin/diff/diffdir.c')
-rw-r--r-- | usr.bin/diff/diffdir.c | 85 |
1 files changed, 75 insertions, 10 deletions
diff --git a/usr.bin/diff/diffdir.c b/usr.bin/diff/diffdir.c index 8d12e868f90e..df65a84ca391 100644 --- a/usr.bin/diff/diffdir.c +++ b/usr.bin/diff/diffdir.c @@ -21,10 +21,12 @@ */ #include <sys/stat.h> +#include <sys/tree.h> #include <dirent.h> #include <err.h> #include <errno.h> +#include <fcntl.h> #include <fnmatch.h> #include <limits.h> #include <stdio.h> @@ -41,6 +43,61 @@ static void print_only(const char *, size_t, const char *); #define d_status d_type /* we need to store status for -l */ +struct inode { + dev_t dev; + ino_t ino; + RB_ENTRY(inode) entry; +}; + +static int +inodecmp(struct inode *a, struct inode *b) +{ + return (a->dev < b->dev ? -1 : a->dev > b->dev ? 1 : + a->ino < b->ino ? -1 : a->ino > b->ino ? 1 : 0); +} + +RB_HEAD(inodetree, inode); +static struct inodetree v1 = RB_INITIALIZER(&v1); +static struct inodetree v2 = RB_INITIALIZER(&v2); +RB_GENERATE_STATIC(inodetree, inode, entry, inodecmp); + +static int +vscandir(struct inodetree *tree, const char *path, struct dirent ***dirp, + int (*select)(const struct dirent *), + int (*compar)(const struct dirent **, const struct dirent **)) +{ + struct stat sb; + struct inode *ino = NULL; + int fd = -1, ret, serrno; + + if ((fd = open(path, O_DIRECTORY | O_RDONLY)) < 0 || + (ino = calloc(1, sizeof(*ino))) == NULL || + fstat(fd, &sb) != 0) + goto fail; + ino->dev = sb.st_dev; + ino->ino = sb.st_ino; + if (RB_FIND(inodetree, tree, ino)) { + free(ino); + close(fd); + warnx("%s: Directory loop detected", path); + *dirp = NULL; + return (0); + } + if ((ret = fscandir(fd, dirp, select, compar)) < 0) + goto fail; + RB_INSERT(inodetree, tree, ino); + close(fd); + return (ret); +fail: + serrno = errno; + if (ino != NULL) + free(ino); + if (fd >= 0) + close(fd); + errno = serrno; + return (-1); +} + /* * Diff directory traversal. Will be called recursively if -r was specified. */ @@ -61,26 +118,22 @@ diffdir(char *p1, char *p2, int flags) status |= 2; return; } - if (path1[dirlen1 - 1] != '/') { - path1[dirlen1++] = '/'; - path1[dirlen1] = '\0'; - } + while (dirlen1 > 1 && path1[dirlen1 - 1] == '/') + path1[--dirlen1] = '\0'; dirlen2 = strlcpy(path2, *p2 ? p2 : ".", sizeof(path2)); if (dirlen2 >= sizeof(path2) - 1) { warnc(ENAMETOOLONG, "%s", p2); status |= 2; return; } - if (path2[dirlen2 - 1] != '/') { - path2[dirlen2++] = '/'; - path2[dirlen2] = '\0'; - } + while (dirlen2 > 1 && path2[dirlen2 - 1] == '/') + path2[--dirlen2] = '\0'; /* * Get a list of entries in each directory, skipping "excluded" files * and sorting alphabetically. */ - pos = scandir(path1, &dirp1, selectfile, alphasort); + pos = vscandir(&v1, path1, &dirp1, selectfile, alphasort); if (pos == -1) { if (errno == ENOENT && (Nflag || Pflag)) { pos = 0; @@ -92,7 +145,7 @@ diffdir(char *p1, char *p2, int flags) dp1 = dirp1; edp1 = dirp1 + pos; - pos = scandir(path2, &dirp2, selectfile, alphasort); + pos = vscandir(&v2, path2, &dirp2, selectfile, alphasort); if (pos == -1) { if (errno == ENOENT && Nflag) { pos = 0; @@ -115,6 +168,18 @@ diffdir(char *p1, char *p2, int flags) } /* + * Append separator so children's names can be appended directly. + */ + if (path1[dirlen1 - 1] != '/') { + path1[dirlen1++] = '/'; + path1[dirlen1] = '\0'; + } + if (path2[dirlen2 - 1] != '/') { + path2[dirlen2++] = '/'; + path2[dirlen2] = '\0'; + } + + /* * Iterate through the two directory lists, diffing as we go. */ while (dp1 != edp1 || dp2 != edp2) { |