]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
cat: don’t trust st_size on /proc files
authorPaul Eggert <eggert@cs.ucla.edu>
Sat, 6 Apr 2024 22:13:23 +0000 (15:13 -0700)
committerPaul Eggert <eggert@cs.ucla.edu>
Sat, 6 Apr 2024 22:18:27 +0000 (15:18 -0700)
* src/cat.c (main):
Improve test for when copying will exhaust the output device.
Do not rely on st_size, which is unreliable in /proc.
Use lseek instead; this is good enough here.
* tests/cat/cat-self.sh: Test the relaxation of the heuristic
for self-copying.

src/cat.c
tests/cat/cat-self.sh

index 4ed404363a34201663adceee4bf7c7058a0600ef..b33faeb35e76d5de499d8b0bb5824230e7d2cc60 100644 (file)
--- a/src/cat.c
+++ b/src/cat.c
@@ -645,9 +645,10 @@ main (int argc, char **argv)
   /* Optimal size of i/o operations of output.  */
   idx_t outsize = io_blksize (&stat_buf);
 
-  /* Device and I-node number of the output.  */
+  /* Device, I-node number and lazily-acquired flags of the output.  */
   dev_t out_dev = stat_buf.st_dev;
   ino_t out_ino = stat_buf.st_ino;
+  int out_flags = -2;
 
   /* True if the output is a regular file.  */
   bool out_isreg = S_ISREG (stat_buf.st_mode) != 0;
@@ -701,17 +702,27 @@ main (int argc, char **argv)
 
       fdadvise (input_desc, 0, 0, FADVISE_SEQUENTIAL);
 
-      /* Don't copy a nonempty regular file to itself, as that would
-         merely exhaust the output device.  It's better to catch this
-         error earlier rather than later.  */
+      /* Don't copy a file to itself if that would merely exhaust the
+         output device.  It's better to catch this error earlier
+         rather than later.  */
 
-      if (out_isreg
-          && stat_buf.st_dev == out_dev && stat_buf.st_ino == out_ino
-          && lseek (input_desc, 0, SEEK_CUR) < stat_buf.st_size)
+      if (stat_buf.st_dev == out_dev && stat_buf.st_ino == out_ino)
         {
-          error (0, 0, _("%s: input file is output file"), quotef (infile));
-          ok = false;
-          goto contin;
+          if (out_flags < -1)
+            out_flags = fcntl (STDOUT_FILENO, F_GETFL);
+          bool exhausting = 0 <= out_flags && out_flags & O_APPEND;
+          if (!exhausting)
+            {
+              off_t in_pos = lseek (input_desc, 0, SEEK_CUR);
+              if (0 <= in_pos)
+                exhausting = in_pos < lseek (STDOUT_FILENO, 0, SEEK_CUR);
+            }
+          if (exhausting)
+            {
+              error (0, 0, _("%s: input file is output file"), quotef (infile));
+              ok = false;
+              goto contin;
+            }
         }
 
       /* Pointer to the input buffer.  */
index e0f6455c0bea247c0b6d52c7d8360778c3ee16e4..854825def9fc11cd72a32d9915ca12bbf3e63a84 100755 (executable)
@@ -30,4 +30,24 @@ echo y >doc.end || framework_failure_
 cat doc doc.end >doc || fail=1
 compare doc doc.end || fail=1
 
+# This terminates even though it copies a file to itself.
+# Coreutils 9.5 and earlier rejected this.
+echo x >fx || framework_failure_
+echo y >fy || framework_failure_
+cat fx fy >fxy || fail=1
+for i in 1 2; do
+  cat fx >fxy$i || fail=1
+done
+for i in 3 4 5 6; do
+  cat fx >fx$i || fail=1
+done
+cat - fy <fxy1 1<>fxy1 || fail=1
+compare fxy fxy1 || fail=1
+cat fxy2 fy 1<>fxy2 || fail=1
+compare fxy fxy2 || fail=1
+returns_ 1 cat fx fx3 1<>fx3 || fail=1
+returns_ 1 cat - fx4 <fx 1<>fx4 || fail=1
+returns_ 1 cat fx5 >>fx5 || fail=1
+returns_ 1 cat <fx6 >>fx6 || fail=1
+
 Exit $fail