From unknown Fri Aug 15 14:45:30 2025 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 X-Mailer: MIME-tools 5.509 (Entity 5.509) Content-Type: text/plain; charset=utf-8 From: bug#67805 <67805@debbugs.gnu.org> To: bug#67805 <67805@debbugs.gnu.org> Subject: Status: [PATCH] cp: Add --keep-directory-symlink option Reply-To: bug#67805 <67805@debbugs.gnu.org> Date: Fri, 15 Aug 2025 21:45:30 +0000 retitle 67805 [PATCH] cp: Add --keep-directory-symlink option reassign 67805 coreutils submitter 67805 Daan De Meyer severity 67805 normal tag 67805 patch thanks From debbugs-submit-bounces@debbugs.gnu.org Tue Dec 12 20:40:17 2023 Received: (at submit) by debbugs.gnu.org; 13 Dec 2023 01:40:18 +0000 Received: from localhost ([127.0.0.1]:57975 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rDEES-0000NC-O0 for submit@debbugs.gnu.org; Tue, 12 Dec 2023 20:40:17 -0500 Received: from lists.gnu.org ([2001:470:142::17]:49946) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rDA31-0000nN-W0 for submit@debbugs.gnu.org; Tue, 12 Dec 2023 16:12:12 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rDA2d-00017u-9a for bug-coreutils@gnu.org; Tue, 12 Dec 2023 16:11:47 -0500 Received: from mail-ej1-x636.google.com ([2a00:1450:4864:20::636]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1rDA2b-0006o0-3x for bug-coreutils@gnu.org; Tue, 12 Dec 2023 16:11:46 -0500 Received: by mail-ej1-x636.google.com with SMTP id a640c23a62f3a-a1f0616a15bso695374266b.2 for ; Tue, 12 Dec 2023 13:11:44 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1702415502; x=1703020302; darn=gnu.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=h8re+zyo8TFnQLrICN68DcDB+9ecxbmCZeeTuHx23ww=; b=kkUmdbRIv5n6JtHoS2unVne8C+lEojOyogPMQTVEny1LGVjyuSEHFLEXI/Fe2KA7gw TXRaqMPCA15MFdrY6ZSMYwaE3NS6LuJWnrl3B8mElpt7Qib6ferfOPlclmg35nL5Hver 6SYcNM1QoZEpOkpPdf1skkwRXHKfQ8GmFezEwy4EOOadTeYYhtCkE98tAytSaSPYVfUM P+pphntzp7uKapNkUIhQ44uMMb60vGGiJ0z+yC4985uqPY2hH0xfJYP0wdeGHnr+6g3D CDUVSHeZ0rrHGHPZgOYEKFS6TgkE4rYOjCM/lJR+ty5+bdPS3TXLkUl6t3ru+ohMHKbB 0A8Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1702415502; x=1703020302; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=h8re+zyo8TFnQLrICN68DcDB+9ecxbmCZeeTuHx23ww=; b=W2EUQlO6891W2nLQC19KCnviu57dtbT/Ueq9P19lKZwwIhvqbLiYn6JHh4vumlmDZA llFNPVBaN9f+U37Ugf64pIfR6ZHBPifxIUZLZYk/LDmmxtgAaqRapUx+lIhb27m5qaZJ Ve6TiKTL4Mzj67p8jR+Idbn3MbLI3urY5EZ44oCCla+dL0DyGjxf8Efiiyrb+9fTmmMO FiSx1H0yLdBfZ2xD8ni7ZdYCpZRbLMThZXAfAN+Tqpko7smsHVafvuCZf6o3xFQdqr3y GJ+QzvXbmnQVKtm0yZGIXG2W4CZ2mkJxbvyBhnZ/kol7m+pbB1Vx4GbrCbyLd3O68SOx R+BA== X-Gm-Message-State: AOJu0Ywu3CVOaXP2COCGMS8fe5oqqo7DtHcsFuF9pLDdQegmPTgPHxQ+ oLFjaPuZCTkP7M2BpcO9/9kngG3kU3k= X-Google-Smtp-Source: AGHT+IH5NXBbBAtSQ2A8UmbHlHBQsUETgDN4wQoPxCLniP/p8Obk1TEiJr/5pBB2Tx/ebKoKmt1sYg== X-Received: by 2002:a17:906:1045:b0:a1f:8b53:df18 with SMTP id j5-20020a170906104500b00a1f8b53df18mr3189327ejj.61.1702415501685; Tue, 12 Dec 2023 13:11:41 -0800 (PST) Received: from localhost.localdomain ([2a02:a03f:864b:8201:e534:34f4:1c34:8de7]) by smtp.googlemail.com with ESMTPSA id lm11-20020a17090718cb00b00a1cf3fce937sm6757927ejc.162.2023.12.12.13.11.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 12 Dec 2023 13:11:41 -0800 (PST) From: Daan De Meyer To: bug-coreutils@gnu.org Subject: [PATCH] cp: Add --keep-directory-symlink option Date: Tue, 12 Dec 2023 22:11:15 +0100 Message-ID: <20231212211118.709267-1-daan.j.demeyer@gmail.com> X-Mailer: git-send-email 2.43.0 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Received-SPF: pass client-ip=2a00:1450:4864:20::636; envelope-from=daan.j.demeyer@gmail.com; helo=mail-ej1-x636.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-Spam-Score: 1.0 (+) X-Debbugs-Envelope-To: submit X-Mailman-Approved-At: Tue, 12 Dec 2023 20:40:15 -0500 Cc: Daan De Meyer X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -0.0 (/) 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 . + +. "${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