From nobody Tue Feb 10 15:28:13 2026 X-Original-To: dev-commits-src-branches@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4f9QSG4qcmz6RVkb for ; Tue, 10 Feb 2026 15:28:14 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R13" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4f9QSG0DCNz473P for ; Tue, 10 Feb 2026 15:28:14 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1770737294; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=ngqH7LmBGRFZYiwzFC4I1YD1BZEnB5b2rDY2FTBrmqI=; b=H2qGJB/lu/MOT8nK/bIwqIgf3Htn6XsLEcuVaYKlGBAz1MDONjOnxWu8mXK7Q9Vwubps34 z7/a2ZdbDbHvIDug5AZXaef58E6YwLefMNvnB4jIllI7kDTHcS7C2y6gGbkYNX9/2WEttj lBs4FSQVebchrA3Vjwjr1LA0gbCQsc+0nFzWG418VqxlIRtklFZm6ChZPS3JRisrDsbQYi nYpCwExu8cQZQiwdEr0RZJODZF9+mzjLR+0sjzwX3FwJ5hMIlvkTlltHaFvq0FXH1zZFEn BZ34g3kdfBD+4rNy/5v/SBb0fMoPnYYMdFs8dJztVBLtXRn7ZUK7IWpPwNIC+w== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1770737294; a=rsa-sha256; cv=none; b=cEbxr7Jx1mOJ+2v8BeRijVcHJER1Lf7OTFz+zvX8eNSZVhoMlriNtXmKRVTPbPeeJLXJqa FQ1MD22QtZ0NC9AOYHHbuNlW1fPZqaZ25TOYguGBytFTBm95vHbR9P4vcGIcbfFFKQHeZY rcFSLADfNqS6DlzGdfVkYFaTYHOjuoJ+ahfRCtNfRBty0PQrP2MhxXk6KoGCOkIbbVYt36 EHqTNrpjvb8gUAs7+M9i3GVCaIKMK6oQAnxHB7CyVjGwQakcuUmvTgzVcNzFBwXUGZZDm2 V37IVF7f/pzHQ3dh3gXdd11U6bRI6rX6BObNIRWKJJ+Kb44B/70t6FPlCgUN0Q== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1770737294; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=ngqH7LmBGRFZYiwzFC4I1YD1BZEnB5b2rDY2FTBrmqI=; b=DYEPGmr3p7aSvMcmMHRTyuklhO3P3o2c6OEiCBDmxc0aQO3Hj9Hj8rnRIXE3bY1+ia26L6 wuhxoaSty8ZVYAD6FXTvHYhHQMqHA/bQikZBnbikxw5nUx0zd/u4xu0sVSAwsBWvVzutdJ WPAE/lquevMcvvt6GUwW1CL+sJyqvzfHhAsnoF0bOPZp28QSkUblzevoeZV1ZFZQDCPxu8 qTH+SfUgAiiJZHctt7R2MN8c9VwhtpAEcVp40a81HltBg9Er+/fvBXnEkl3FJY9Poq0YsT +XLNQ8J+CYvNLoMeBsOv1euBtLNOky0/ZL+rY9fbJJdcVt3Nr48RCZ1AwitBqA== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4f9QSF5V3Bz1N1x for ; Tue, 10 Feb 2026 15:28:13 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 451dc by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Tue, 10 Feb 2026 15:28:13 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Dag-Erling=?utf-8?Q? Sm=C3=B8rg?=rav Subject: git: bfd89763d207 - stable/13 - diff: Detect loops when diffing directories. List-Id: Commits to the stable branches of the FreeBSD src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-branches List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-branches@freebsd.org Sender: owner-dev-commits-src-branches@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: des X-Git-Repository: src X-Git-Refname: refs/heads/stable/13 X-Git-Reftype: branch X-Git-Commit: bfd89763d2079a6f232e8324fc2c688cb336aed9 Auto-Submitted: auto-generated Date: Tue, 10 Feb 2026 15:28:13 +0000 Message-Id: <698b4e8d.451dc.5a4d9df5@gitrepo.freebsd.org> The branch stable/13 has been updated by des: URL: https://cgit.FreeBSD.org/src/commit/?id=bfd89763d2079a6f232e8324fc2c688cb336aed9 commit bfd89763d2079a6f232e8324fc2c688cb336aed9 Author: Dag-Erling Smørgrav AuthorDate: 2025-06-20 11:10:35 +0000 Commit: Dag-Erling Smørgrav CommitDate: 2026-02-10 14:24:30 +0000 diff: Detect loops when diffing directories. Sponsored by: Klara, Inc. Reviewed by: markj Differential Revision: https://reviews.freebsd.org/D50936 (cherry picked from commit 42092e1b6625b8226de5f34d22b9a96c713626cb) --- usr.bin/diff/diffdir.c | 88 +++++++++++++++++++++++++++++++++++------ usr.bin/diff/tests/diff_test.sh | 17 ++++++++ 2 files changed, 94 insertions(+), 11 deletions(-) diff --git a/usr.bin/diff/diffdir.c b/usr.bin/diff/diffdir.c index 1ffdf0a8de5b..627e5dd9b983 100644 --- a/usr.bin/diff/diffdir.c +++ b/usr.bin/diff/diffdir.c @@ -22,15 +22,18 @@ #include #include +#include #include #include #include +#include #include +#include #include #include #include -#include +#include #include "diff.h" @@ -39,6 +42,61 @@ static void diffit(struct dirent *, char *, size_t, char *, size_t, int); #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 = scandir(path, 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. */ @@ -59,26 +117,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; @@ -90,7 +144,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; @@ -112,6 +166,18 @@ diffdir(char *p1, char *p2, int flags) dp2++; } + /* + * 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. */ diff --git a/usr.bin/diff/tests/diff_test.sh b/usr.bin/diff/tests/diff_test.sh index 29ab0eaec161..027febf69f64 100755 --- a/usr.bin/diff/tests/diff_test.sh +++ b/usr.bin/diff/tests/diff_test.sh @@ -19,6 +19,7 @@ atf_test_case label atf_test_case report_identical atf_test_case non_regular_file atf_test_case binary +atf_test_case dirloop simple_body() { @@ -284,6 +285,21 @@ binary_body() atf_check -o inline:"176c\nx\n.\n" -s exit:1 diff -ae A B } +dirloop_head() +{ + atf_set "timeout" "10" +} +dirloop_body() +{ + atf_check mkdir -p a/foo/bar + atf_check ln -s .. a/foo/bar/up + atf_check cp -a a b + atf_check \ + -e match:"a/foo/bar/up: Directory loop detected" \ + -e match:"b/foo/bar/up: Directory loop detected" \ + diff -r a b +} + atf_init_test_cases() { atf_add_test_case simple @@ -306,4 +322,5 @@ atf_init_test_cases() atf_add_test_case report_identical atf_add_test_case non_regular_file atf_add_test_case binary + atf_add_test_case dirloop }