git: 0c5c50353026 - stable/14 - diff: Improve directory loop detection
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sun, 15 Feb 2026 08:58:49 UTC
The branch stable/14 has been updated by des:
URL: https://cgit.FreeBSD.org/src/commit/?id=0c5c50353026bd9d7dca862b6033bcc9cbd8cea1
commit 0c5c50353026bd9d7dca862b6033bcc9cbd8cea1
Author: Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2026-02-11 16:24:54 +0000
Commit: Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2026-02-15 08:58:35 +0000
diff: Improve directory loop detection
When we're done processing a directory, remove its entry from the tree
of visited inodes, ensuring that we only report a loop when we encounter
a descendant-to-ancestor link, not when we encounter a cousin-to-cousin
or sibling-to-sibling link.
MFC after: 1 week
Reported by: Bakul Shah <bakul@iitbombay.org>
Sponsored by: Klara, Inc.
Reviewed by: kevans
Differential Revision: https://reviews.freebsd.org/D55248
(cherry picked from commit 71569594d860a59d8362770a56d806e1d31fb946)
---
usr.bin/diff/diffdir.c | 20 +++++++++++++++++---
usr.bin/diff/tests/diff_test.sh | 5 +++++
2 files changed, 22 insertions(+), 3 deletions(-)
diff --git a/usr.bin/diff/diffdir.c b/usr.bin/diff/diffdir.c
index 7ea07645d751..d943e3bfaaf7 100644
--- a/usr.bin/diff/diffdir.c
+++ b/usr.bin/diff/diffdir.c
@@ -61,7 +61,8 @@ 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,
+vscandir(struct inodetree *tree, struct inode **inop,
+ const char *path, struct dirent ***dirp,
int (*selectf)(const struct dirent *),
int (*comparf)(const struct dirent **, const struct dirent **))
{
@@ -86,6 +87,7 @@ vscandir(struct inodetree *tree, const char *path, struct dirent ***dirp,
goto fail;
RB_INSERT(inodetree, tree, ino);
close(fd);
+ *inop = ino;
return (ret);
fail:
serrno = errno;
@@ -97,6 +99,13 @@ fail:
return (-1);
}
+static void
+leavedir(struct inodetree *tree, struct inode *ino)
+{
+ RB_REMOVE(inodetree, tree, ino);
+ free(ino);
+}
+
/*
* Diff directory traversal. Will be called recursively if -r was specified.
*/
@@ -105,6 +114,7 @@ diffdir(char *p1, char *p2, int flags)
{
struct dirent *dent1, **dp1, **edp1, **dirp1 = NULL;
struct dirent *dent2, **dp2, **edp2, **dirp2 = NULL;
+ struct inode *ino1 = NULL, *ino2 = NULL;
size_t dirlen1, dirlen2;
char path1[PATH_MAX], path2[PATH_MAX];
int pos;
@@ -132,7 +142,7 @@ diffdir(char *p1, char *p2, int flags)
* Get a list of entries in each directory, skipping "excluded" files
* and sorting alphabetically.
*/
- pos = vscandir(&v1, path1, &dirp1, selectfile, alphasort);
+ pos = vscandir(&v1, &ino1, path1, &dirp1, selectfile, alphasort);
if (pos == -1) {
if (errno == ENOENT && (Nflag || Pflag)) {
pos = 0;
@@ -144,7 +154,7 @@ diffdir(char *p1, char *p2, int flags)
dp1 = dirp1;
edp1 = dirp1 + pos;
- pos = vscandir(&v2, path2, &dirp2, selectfile, alphasort);
+ pos = vscandir(&v2, &ino2, path2, &dirp2, selectfile, alphasort);
if (pos == -1) {
if (errno == ENOENT && Nflag) {
pos = 0;
@@ -218,11 +228,15 @@ diffdir(char *p1, char *p2, int flags)
closem:
if (dirp1 != NULL) {
+ if (ino1 != NULL)
+ leavedir(&v1, ino1);
for (dp1 = dirp1; dp1 < edp1; dp1++)
free(*dp1);
free(dirp1);
}
if (dirp2 != NULL) {
+ if (ino2 != NULL)
+ leavedir(&v2, ino2);
for (dp2 = dirp2; dp2 < edp2; dp2++)
free(*dp2);
free(dirp2);
diff --git a/usr.bin/diff/tests/diff_test.sh b/usr.bin/diff/tests/diff_test.sh
index fb6080ee6916..61fa618b8d2c 100755
--- a/usr.bin/diff/tests/diff_test.sh
+++ b/usr.bin/diff/tests/diff_test.sh
@@ -378,6 +378,11 @@ dirloop_body()
-e match:"a/foo/bar/up: Directory loop detected" \
-e match:"b/foo/bar/up: Directory loop detected" \
diff -r a b
+ atf_check rm [ab]/foo/bar/up
+ atf_check mkdir -p b/foo/bar
+ atf_check ln -s foo a/baz
+ atf_check ln -s foo b/baz
+ atf_check diff -r a b
}
bigc_head()