Subject: bug#76452: closed (Re: [bug-diffutils] bug#76452: 3.11 critical bug: diff -Naur a b does not show a unified diff when one of the file is empty)

From: [email-masked] (GNU bug Tracking System)
Date: Sat, 22 Feb 2025 06:12:04 +0000

Your bug report

#76452: 3.11 critical bug: diff -Naur a b does not show a unified diff when one of the file is empty

which was filed against the diffutils package, has been closed.

The explanation is attached below, along with your original report.
If you require more details, please reply to [email-masked].

-- 
76452: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=76452
GNU Bug Tracking System
Contact [email-masked] with problems

Subject: Re: [bug-diffutils] bug#76452: 3.11 critical bug: diff -Naur a b does not show a unified diff when one of the file is empty
From: Paul Eggert <[email-masked]>
To: Collin Funk <[email-masked]>
Cc: [email-masked], Kate Deplaix <[email-masked]>
Date: Fri, 21 Feb 2025 23:11:22 -0700
List-Id: <debbugs-submit.debbugs.gnu.org>

Thanks for the diagnosis and the patch. I installed the attached 
instead, so as to decouple this part of the code from the part that 
turns file types into strings.

From 6ce0ebd033c395265c262ae3aab6477a49d4c2f1 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Thu, 20 Feb 2025 21:59:55 -0800
Subject: [PATCH] diff: don't treat empty files as a different file type

Reported by Kate Deplaix <[email-masked]> in
<https://lists.gnu.org/r/bug-diffutils/2025-02/msg00005.html>.

* src/diff.c (compare_prepped_files): Don't rely on string
file type, as that might not agree with our idea of a file type.
---
 src/diff.c | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/src/diff.c b/src/diff.c
index fa6100e..6e1bbc5 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -1223,14 +1223,14 @@ compare_prepped_files (struct comparison const *parent,
      the type is unusual, then simply report their type.
      However, at the top level do this only if one file is a symlink
      and the other is not.  */
-  if (toplevel
-      ? (!S_ISLNK (cmp->file[0].stat.st_mode)
-	 != !S_ISLNK (cmp->file[1].stat.st_mode))
-      : (cmp->file[0].filetype != cmp->file[1].filetype
-	 || ! (S_ISREG (cmp->file[0].stat.st_mode)
-	       || S_ISLNK (cmp->file[0].stat.st_mode)
-	       || S_ISCHR (cmp->file[0].stat.st_mode)
-	       || S_ISBLK (cmp->file[0].stat.st_mode))))
+  mode_t mode0 = cmp->file[0].stat.st_mode;
+  mode_t mode1 = cmp->file[1].stat.st_mode;
+  if (toplevel ? !S_ISLNK (mode0) != !S_ISLNK (mode1)
+      : S_ISREG (mode0) ? !S_ISREG (mode1)
+      : S_ISLNK (mode0) ? !S_ISLNK (mode1)
+      : S_ISCHR (mode0) ? !S_ISCHR (mode1)
+      : S_ISBLK (mode0) ? !S_ISBLK (mode1)
+      : true)
     {
       /* POSIX 1003.1-2017 says any message will do, so long as it
 	 contains the file names.  */
@@ -1244,7 +1244,7 @@ compare_prepped_files (struct comparison const *parent,
     }
 
   /* If both files are symlinks, compare symlink contents.  */
-  if (S_ISLNK (cmp->file[0].stat.st_mode))
+  if (S_ISLNK (mode0))
     {
       /* We get here only if we are not dereferencing symlinks.  */
       dassert (no_dereference_symlinks);
@@ -1295,7 +1295,7 @@ compare_prepped_files (struct comparison const *parent,
      and report file types of all other non-regular files.
      POSIX 1003.1-2017 says any message will do,
      so long as it contains the file names.  */
-  if (!toplevel && !S_ISREG (cmp->file[0].stat.st_mode))
+  if (!toplevel && !S_ISREG (mode0))
     {
       if (cmp->file[0].stat.st_rdev == cmp->file[1].stat.st_rdev)
 	return EXIT_SUCCESS;
@@ -1311,7 +1311,7 @@ compare_prepped_files (struct comparison const *parent,
       for (int i = 0; i < n_num; i++)
 	sprintf (numbuf[i], "%"PRIdMAX, num[i]);
 
-      message ((S_ISCHR (cmp->file[0].stat.st_mode)
+      message ((S_ISCHR (mode0)
 		? ("Character special files %s (%s, %s)"
 		   " and %s (%s, %s) differ\n")
 		: ("Block special files %s (%s, %s)"
@@ -1323,8 +1323,8 @@ compare_prepped_files (struct comparison const *parent,
     }
 
   if (files_can_be_treated_as_binary
-      && S_ISREG (cmp->file[0].stat.st_mode)
-      && S_ISREG (cmp->file[1].stat.st_mode)
+      && S_ISREG (mode0)
+      && S_ISREG (mode1)
       && cmp->file[0].stat.st_size != cmp->file[1].stat.st_size
       && 0 <= cmp->file[0].stat.st_size
       && 0 <= cmp->file[1].stat.st_size)
-- 
2.34.1

From 706116c651fd9abc9284516b47cea6cd536c7bcd Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Fri, 21 Feb 2025 23:10:06 -0700
Subject: [PATCH] maint: mention bug#76452 in NEWS

---
 NEWS | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/NEWS b/NEWS
index 5c23daf..73d98d6 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,12 @@ GNU diffutils NEWS                                    -*- outline -*-
 
 * Noteworthy changes in release ?.? (????-??-??) [?]
 
+** Bug fixes
+
+  diff -r no longer merely summarizes when comparing an empty regular
+  file to a nonempty regular file.
+  [bug#76452 introduced in 3.11]
+
 
 * Noteworthy changes in release 3.11 (2025-02-02) [stable]
 
-- 
2.34.1

Subject: 3.11 critical bug: diff -Naur a b does not show a unified diff when one of the file is empty
From: Kate Deplaix <[email-masked]>
To: "[email-masked]" <[email-masked]>
Date: Thu, 20 Feb 2025 15:25:05 +0000
List-Id: <debbugs-submit.debbugs.gnu.org>

Hi,

While using diffutils 3.11 instead of 3.10, i've noticed a critical bug in diff reproducible with the following setup:

$ cd /tmp
$ mkdir a b
$ touch a/test
$ echo content > b/test

With diffutils 3.10, "diff -Naur a b" returns:

diff -Naur a/test b/test
--- a/test   2025-02-20 14:50:07.870258052 +0000
+++ b/test   2025-02-20 14:50:18.957287641 +0000
@@ -0,0 +1 @@
+content

but with diffutils 3.11, the same command returns:

File a/test is a regular empty file while file b/test is a regular file

Given no mention of this change appear in the changelog, i'm guessing this is an unintended change. In my opinion this bug is critical and the 3.11 release should be marked as broken to avoid more distributions using it.

Warm regards,
Kate Deplaix