aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/diff/diffdir.c
diff options
context:
space:
mode:
authorDag-Erling Smørgrav <des@FreeBSD.org>2025-06-20 11:10:35 +0000
committerDag-Erling Smørgrav <des@FreeBSD.org>2025-06-20 11:10:35 +0000
commit42092e1b6625b8226de5f34d22b9a96c713626cb (patch)
treeaab1d1a1ee4973b560699f795786670de4c8b7e7 /usr.bin/diff/diffdir.c
parentdeeebfdecab56729fa898271ae53d01c8e156302 (diff)
Diffstat (limited to 'usr.bin/diff/diffdir.c')
-rw-r--r--usr.bin/diff/diffdir.c85
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) {