Subject: Re: bug#33761: Allow split to write into FIFOs

From: Paul Eggert <[email-masked]>
Date: Sat, 15 Dec 2018 12:20:00 -0800

Thanks for reporting that. I see similar problems in a couple of other coreutils 
programs. The style elsewhere seems to be to try ftruncate first, and report an 
error if it fails only if the file is not one for which POSIX specifies the 
behavior, so I think it'd be better if 'split' did that too. So I installed the 
attached patch to fix the problems that I found, and this should fix the bug you 
reported so I'll close the bug report.

Thanks also for the test case. To install that I'll need a copyright assignment 
form signed; can you do that for us? If so, please let me know and I'll get the 
paperwork ball rolling for you.
>From 5af28df87d7af0736b2ad5e09ae6b3784f6af3af Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Sat, 15 Dec 2018 12:13:58 -0800
Subject: [PATCH] shred,sort,split: fix ftruncate error reporting

Problem reported for split by Scott Worley (Bug#33761):
* src/shred.c (do_wipefd):
Also report an error if ftruncate fails	on a shared memory object.
* src/sort.c (get_outstatus): New function.
(stream_open, avoid_trashing_input): Use it.
* src/sort.c (stream_open):
* src/split.c (create):
If ftruncate fails, do not report an error
unless it is a regular file or a shared memory object.
---
 src/shred.c | 10 ++++++----
 src/sort.c  | 39 ++++++++++++++++++++++++++-------------
 src/split.c |  3 ++-
 3 files changed, 34 insertions(+), 18 deletions(-)

diff --git a/src/shred.c b/src/shred.c
index 270b1e942..3a6510cf3 100644
--- a/src/shred.c
+++ b/src/shred.c
@@ -975,11 +975,13 @@ do_wipefd (int fd, char const *qname, struct randint_source *s,
         }
     }
 
-  /* Now deallocate the data.  The effect of ftruncate on
-     non-regular files is unspecified, so don't worry about any
-     errors reported for them.  */
+  /* Now deallocate the data.  The effect of ftruncate is specified
+     on regular files and shared memory objects (also directories, but
+     they are not possible here); don't worry about errors reported
+     for other file types.  */
+
   if (flags->remove_file && ftruncate (fd, 0) != 0
-      && S_ISREG (st.st_mode))
+      && (S_ISREG (st.st_mode) || S_TYPEISSHM (&st)))
     {
       error (0, errno, _("%s: error truncating"), qname);
       ok = false;
diff --git a/src/sort.c b/src/sort.c
index dd6bce47f..ebe86d3fb 100644
--- a/src/sort.c
+++ b/src/sort.c
@@ -895,8 +895,21 @@ create_temp_file (int *pfd, bool survive_fd_exhaustion)
   return node;
 }
 
-/* Return a stream for FILE, opened with mode HOW.  A null FILE means
-   standard output; HOW should be "w".  When opening for input, "-"
+/* Return a pointer to stdout status, or NULL on failure.  */
+
+static struct stat *
+get_outstatus (void)
+{
+  static int outstat_errno;
+  static struct stat outstat;
+  if (outstat_errno == 0)
+    outstat_errno = fstat (STDOUT_FILENO, &outstat) == 0 ? -1 : errno;
+  return outstat_errno < 0 ? &outstat : NULL;
+}
+
+/* Return a stream for FILE, opened with mode HOW.  If HOW is "w",
+   the file is already open on standard output, and needs to be
+   truncated unless FILE is null.  When opening for input, "-"
    means standard input.  To avoid confusion, do not return file
    descriptors STDIN_FILENO, STDOUT_FILENO, or STDERR_FILENO when
    opening an ordinary FILE.  Return NULL if unsuccessful.
@@ -964,8 +977,13 @@ stream_open (char const *file, char const *how)
   else if (*how == 'w')
     {
       if (file && ftruncate (STDOUT_FILENO, 0) != 0)
-        die (SORT_FAILURE, errno, _("%s: error truncating"),
-             quotef (file));
+        {
+          int ftruncate_errno = errno;
+          struct stat *outst = get_outstatus ();
+          if (!outst || S_ISREG (outst->st_mode) || S_TYPEISSHM (outst))
+            die (SORT_FAILURE, ftruncate_errno, _("%s: error truncating"),
+                 quotef (file));
+        }
       fp = stdout;
     }
   else
@@ -3699,8 +3717,6 @@ static void
 avoid_trashing_input (struct sortfile *files, size_t ntemps,
                       size_t nfiles, char const *outfile)
 {
-  bool got_outstat = false;
-  struct stat outstat;
   struct tempnode *tempcopy = NULL;
 
   for (size_t i = ntemps; i < nfiles; i++)
@@ -3713,18 +3729,15 @@ avoid_trashing_input (struct sortfile *files, size_t ntemps,
         same = true;
       else
         {
-          if (! got_outstat)
-            {
-              if (fstat (STDOUT_FILENO, &outstat) != 0)
-                break;
-              got_outstat = true;
-            }
+          struct stat *outst = get_outstatus ();
+          if (!outst)
+            break;
 
           same = (((is_stdin
                     ? fstat (STDIN_FILENO, &instat)
                     : stat (files[i].name, &instat))
                    == 0)
-                  && SAME_INODE (instat, outstat));
+                  && SAME_INODE (instat, *outst));
         }
 
       if (same)
diff --git a/src/split.c b/src/split.c
index 60c14d36c..1431bdf65 100644
--- a/src/split.c
+++ b/src/split.c
@@ -470,7 +470,8 @@ create (const char *name)
       if (SAME_INODE (in_stat_buf, out_stat_buf))
         die (EXIT_FAILURE, 0, _("%s would overwrite input; aborting"),
              quoteaf (name));
-      if (ftruncate (fd, 0) != 0)
+      if (ftruncate (fd, 0) != 0
+          && (S_ISREG (out_stat_buf.st_mode) || S_TYPEISSHM (&out_stat_buf)))
         die (EXIT_FAILURE, errno, _("%s: error truncating"), quotef (name));
 
       return fd;
-- 
2.17.1