GNU bug report logs - #67805
[PATCH] cp: Add --keep-directory-symlink option

Previous Next

Package: coreutils;

Reported by: Daan De Meyer <daan.j.demeyer <at> gmail.com>

Date: Wed, 13 Dec 2023 01:41:02 UTC

Severity: normal

Tags: patch

To reply to this bug, email your comments to 67805 AT debbugs.gnu.org.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to bug-coreutils <at> gnu.org:
bug#67805; Package coreutils. (Wed, 13 Dec 2023 01:41:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to Daan De Meyer <daan.j.demeyer <at> gmail.com>:
New bug report received and forwarded. Copy sent to bug-coreutils <at> gnu.org. (Wed, 13 Dec 2023 01:41:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Daan De Meyer <daan.j.demeyer <at> gmail.com>
To: bug-coreutils <at> gnu.org
Cc: Daan De Meyer <daan.j.demeyer <at> gmail.com>
Subject: [PATCH] cp: Add --keep-directory-symlink option
Date: Tue, 12 Dec 2023 22:11:15 +0100
When recursively copying files into OS trees, it often happens that
some subdirectory of the source directory is a symlink in the target
directory. Currently, cp will fail in that scenario with the error:

"cannot overwrite non-directory %s with directory %s"

However, we'd like cp in this scenario to follow the destination
directory symlink and copy the files into the symlinked directory
instead. Let's support this by adding a new option
--keep-directory-symlink that makes cp follow destination directory
symlinks.

We name the option --keep-directory-symlink to keep consistent with
tar which has the same option with the same effect.
---
 doc/coreutils.texi                 |  4 ++++
 src/copy.c                         |  3 ++-
 src/copy.h                         |  3 +++
 src/cp.c                           | 12 +++++++++++-
 tests/cp/keep-directory-symlink.sh | 27 +++++++++++++++++++++++++++
 5 files changed, 47 insertions(+), 2 deletions(-)
 create mode 100755 tests/cp/keep-directory-symlink.sh

diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index d445ea228..51c460929 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -10281,6 +10281,10 @@ option is also specified.
 @opindex --verbose
 Print the name of each file before moving it.
 
+@item --keep-directory-symlink
+@opindex --keep-directory-symlink
+Follow existing symlinks to directories when copying.
+
 @optStripTrailingSlashes
 
 @optBackupSuffix
diff --git a/src/copy.c b/src/copy.c
index f54253e5b..bbadca293 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -2311,7 +2311,8 @@ copy_internal (char const *src_name, char const *dst_name,
           bool use_lstat
             = ((! S_ISREG (src_mode)
                 && (! x->copy_as_regular
-                    || S_ISDIR (src_mode) || S_ISLNK (src_mode)))
+                    || (S_ISDIR (src_mode) && !x->keep_directory_symlink)
+                    || S_ISLNK (src_mode)))
                || x->move_mode || x->symbolic_link || x->hard_link
                || x->backup_type != no_backups
                || x->unlink_dest_before_opening);
diff --git a/src/copy.h b/src/copy.h
index 3809f8d23..834685c51 100644
--- a/src/copy.h
+++ b/src/copy.h
@@ -256,6 +256,9 @@ struct cp_options
   /* If true, display the names of the files before copying them. */
   bool verbose;
 
+  /* If true, follow existing symlinks to directories when copying. */
+  bool keep_directory_symlink;
+
   /* If true, display details of how files were copied.  */
   bool debug;
 
diff --git a/src/cp.c b/src/cp.c
index 04a5cbee3..992d28e1e 100644
--- a/src/cp.c
+++ b/src/cp.c
@@ -68,7 +68,8 @@ enum
   REFLINK_OPTION,
   SPARSE_OPTION,
   STRIP_TRAILING_SLASHES_OPTION,
-  UNLINK_DEST_BEFORE_OPENING
+  UNLINK_DEST_BEFORE_OPENING,
+  KEEP_DIRECTORY_SYMLINK_OPTION
 };
 
 /* True if the kernel is SELinux enabled.  */
@@ -141,6 +142,7 @@ static struct option const long_opts[] =
   {"target-directory", required_argument, nullptr, 't'},
   {"update", optional_argument, nullptr, 'u'},
   {"verbose", no_argument, nullptr, 'v'},
+  {"keep-directory-symlink", no_argument, nullptr, KEEP_DIRECTORY_SYMLINK_OPTION},
   {GETOPT_SELINUX_CONTEXT_OPTION_DECL},
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
@@ -230,6 +232,9 @@ Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.\n\
 "), stdout);
       fputs (_("\
   -v, --verbose                explain what is being done\n\
+"), stdout);
+      fputs (_("\
+      --keep-directory-symlink  preserve existing symlinks to directories\n\
 "), stdout);
       fputs (_("\
   -x, --one-file-system        stay on this file system\n\
@@ -859,6 +864,7 @@ cp_option_init (struct cp_options *x)
 
   x->update = false;
   x->verbose = false;
+  x->keep_directory_symlink = false;
 
   /* By default, refuse to open a dangling destination symlink, because
      in general one cannot do that safely, give the current semantics of
@@ -1161,6 +1167,10 @@ main (int argc, char **argv)
           x.verbose = true;
           break;
 
+        case KEEP_DIRECTORY_SYMLINK_OPTION:
+          x.keep_directory_symlink = true;
+          break;
+
         case 'x':
           x.one_file_system = true;
           break;
diff --git a/tests/cp/keep-directory-symlink.sh b/tests/cp/keep-directory-symlink.sh
new file mode 100755
index 000000000..6ae116cd8
--- /dev/null
+++ b/tests/cp/keep-directory-symlink.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Test that cp --keep-directory-symlink follows symlinks.
+
+# Copyright (C) 2006-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ cp
+
+mkdir -p a/b b/d/e || framework_failure_
+ln -s b a/d || framework_failure_
+
+cp -RT --copy-contents b a && framework_failure_
+cp -RT --copy-contents --keep-directory-symlink b a || framework_failure_
+ls a/b/e
-- 
2.43.0





This bug report was last modified 141 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.