>From fe913993d0e93ce98f0b8b77a866144dbb8bbada Mon Sep 17 00:00:00 2001 From: Paul Eggert 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