Subject: bug#35713: /dev/stdin problem in cp on Solaris

From: Paul Eggert <[email-masked]>
Date: Tue, 28 May 2019 13:46:08 -0700

Thanks for reporting the problem. Please try the attached patch, which 
I've installed in the development repository.

>From fe913993d0e93ce98f0b8b77a866144dbb8bbada Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Tue, 28 May 2019 12:42:24 -0700
Subject: [PATCH] cp: fix /dev/stdin problem on Solaris

Problem reported by Jakub Kulik (Bug#35713).
* NEWS: Mention this.
* configure.ac (DEV_FD_MIGHT_BE_CHR): New macro.
* src/copy.c (DEV_FD_MIGHT_BE_CHR): Default to false.
(follow_fstatat): New function.
(copy_internal): Use it.
* src/copy.h (XSTAT): Remove; no longer used.
---
 NEWS         |  4 ++++
 configure.ac |  9 +++++++++
 src/copy.c   | 46 ++++++++++++++++++++++++++++++++++++++++++----
 src/copy.h   |  5 -----
 4 files changed, 55 insertions(+), 9 deletions(-)

diff --git a/NEWS b/NEWS
index 12c864dcc..c9f2eff57 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,10 @@ GNU coreutils NEWS                                    -*- outline -*-
 
 ** Bug fixes
 
+  cp now copies /dev/fd/N correctly on platforms like Solaris where
+  it is a character-special file whose minor device number is N.
+  [bug introduced in coreutils-8.16]
+
   df now correctly parses the /proc/self/mountinfo file for unusual entries
   like ones with '\r' in a field value ("mount -t tmpfs tmpfs /foo$'\r'bar"),
   when the source field is empty ('mount -t tmpfs "" /mnt'), and when the
diff --git a/configure.ac b/configure.ac
index 781a305e2..d90c710e3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -353,6 +353,15 @@ case $utils_cv_func_setpriority,$ac_cv_func_nice in
   gl_ADD_PROG([optional_bin_progs], [nice])
 esac
 
+if test "$cross_compiling" = yes || test -c /dev/stdin <.; then
+  AC_DEFINE([DEV_FD_MIGHT_BE_CHR], [1],
+    [Define to 1 if /dev/std{in,out,err} and /dev/fd/N, if they exist, might be
+     character-special devices whose minor device number is the file
+     descriptor number, such as on Solaris.  Leave undefined if they are
+     definitely the actual files.  This determination should be done after any
+     symbolic links are followed.])
+fi
+
 AC_DEFUN([coreutils_DUMMY_1],
 [
   AC_REQUIRE([gl_READUTMP])
diff --git a/src/copy.c b/src/copy.c
index dc1f6d0fa..65cf65895 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -147,6 +147,42 @@ static bool owner_failure_ok (struct cp_options const *x);
 static char const *top_level_src_name;
 static char const *top_level_dst_name;
 
+#ifndef DEV_FD_MIGHT_BE_CHR
+# define DEV_FD_MIGHT_BE_CHR false
+#endif
+
+/* Act like fstat (DIRFD, FILENAME, ST, FLAGS), except when following
+   symbolic links on Solaris-like systems, treat any character-special
+   device like /dev/fd/0 as if it were the file it is open on.  */
+static int
+follow_fstatat (int dirfd, char const *filename, struct stat *st, int flags)
+{
+  int result = fstatat (dirfd, filename, st, flags);
+
+  if (DEV_FD_MIGHT_BE_CHR && result == 0 && !(flags & AT_SYMLINK_NOFOLLOW)
+      && S_ISCHR (st->st_mode))
+    {
+      static dev_t stdin_rdev;
+      static signed char stdin_rdev_status;
+      if (stdin_rdev_status == 0)
+        {
+          struct stat stdin_st;
+          if (stat ("/dev/stdin", &stdin_st) == 0 && S_ISCHR (stdin_st.st_mode)
+              && minor (stdin_st.st_rdev) == STDIN_FILENO)
+            {
+              stdin_rdev = stdin_st.st_rdev;
+              stdin_rdev_status = 1;
+            }
+          else
+            stdin_rdev_status = -1;
+        }
+      if (0 < stdin_rdev_status && major (stdin_rdev) == major (st->st_rdev))
+        result = fstat (minor (st->st_rdev), st);
+    }
+
+  return result;
+}
+
 /* Set the timestamp of symlink, FILE, to TIMESPEC.
    If this system lacks support for that, simply return 0.  */
 static inline int
@@ -1010,7 +1046,7 @@ is_probably_sparse (struct stat const *sb)
    X provides many option settings.
    Return true if successful.
    *NEW_DST is as in copy_internal.
-   SRC_SB is the result of calling XSTAT (aka stat) on SRC_NAME.  */
+   SRC_SB is the result of calling follow_fstatat on SRC_NAME.  */
 
 static bool
 copy_reg (char const *src_name, char const *dst_name,
@@ -1886,7 +1922,9 @@ copy_internal (char const *src_name, char const *dst_name,
       : rename_errno != EEXIST || x->interactive != I_ALWAYS_NO)
     {
       char const *name = rename_errno == 0 ? dst_name : src_name;
-      if (XSTAT (x, name, &src_sb) != 0)
+      int fstatat_flags
+        = x->dereference == DEREF_NEVER ? AT_SYMLINK_NOFOLLOW : 0;
+      if (follow_fstatat (AT_FDCWD, name, &src_sb, fstatat_flags) != 0)
         {
           error (0, errno, _("cannot stat %s"), quoteaf (name));
           return false;
@@ -1949,7 +1987,7 @@ copy_internal (char const *src_name, char const *dst_name,
                || x->backup_type != no_backups
                || x->unlink_dest_before_opening);
           int fstatat_flags = use_lstat ? AT_SYMLINK_NOFOLLOW : 0;
-          if (fstatat (AT_FDCWD, dst_name, &dst_sb, fstatat_flags) == 0)
+          if (follow_fstatat (AT_FDCWD, dst_name, &dst_sb, fstatat_flags) == 0)
             {
               have_dst_lstat = use_lstat;
               rename_errno = EEXIST;
@@ -2430,7 +2468,7 @@ copy_internal (char const *src_name, char const *dst_name,
       if (rename_errno != EXDEV)
         {
           /* There are many ways this can happen due to a race condition.
-             When something happens between the initial XSTAT and the
+             When something happens between the initial follow_fstatat and the
              subsequent rename, we can get many different types of errors.
              For example, if the destination is initially a non-directory
              or non-existent, but it is created as a directory, the rename
diff --git a/src/copy.h b/src/copy.h
index 7d2303c54..102516c68 100644
--- a/src/copy.h
+++ b/src/copy.h
@@ -276,11 +276,6 @@ struct cp_options
   Hash_table *src_info;
 };
 
-# define XSTAT(X, Src_name, Src_sb) \
-  ((X)->dereference == DEREF_NEVER \
-   ? lstat (Src_name, Src_sb) \
-   : stat (Src_name, Src_sb))
-
 /* Arrange to make rename calls go through the wrapper function
    on systems with a rename function that fails for a source file name
    specified with a trailing slash.  */
-- 
2.21.0