diff --git a/bin/cp/cp.1 b/bin/cp/cp.1 --- a/bin/cp/cp.1 +++ b/bin/cp/cp.1 @@ -29,7 +29,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd March 28, 2024 +.Dd July 1, 2025 .Dt CP 1 .Os .Sh NAME @@ -42,7 +42,7 @@ .Op Fl H | Fl L | Fl P .Oc .Op Fl f | i | n -.Op Fl alNpsvx +.Op Fl alNpSsvx .Ar source_file target_file .Nm .Oo @@ -50,15 +50,15 @@ .Op Fl H | Fl L | Fl P .Oc .Op Fl f | i | n -.Op Fl alNpsvx +.Op Fl alNpSsvx .Ar source_file ... target_directory .Nm .Op Fl f | i | n -.Op Fl alNPpsvx +.Op Fl alNPpSsvx .Ar source_file target_file .Nm .Op Fl f | i | n -.Op Fl alNPpsvx +.Op Fl alNPpSsvx .Ar source_file ... target_directory .Sh DESCRIPTION In the first synopsis form, the @@ -137,9 +137,7 @@ .Fl n options.) .It Fl i -Cause -.Nm -to write a prompt to the standard error output before copying a file +Write a prompt to the standard error output before copying a file that would overwrite an existing file. If the response from the standard input begins with the character .Sq Li y @@ -169,9 +167,7 @@ .Fl i options.) .It Fl p -Cause -.Nm -to preserve the following attributes of each source +Preserve the following attributes of each source file in the copy: modification time, access time, file flags, file mode, ACL, user ID, and group ID, as allowed by permissions. .Pp @@ -188,14 +184,25 @@ and either the user ID or group ID cannot be preserved, neither the set-user-ID nor set-group-ID bits are preserved in the copy's permissions. +.It Fl S +Visit and traverse sources in (non-localized) lexicographical order. +Normally, +.Nm +visits the sources in the order they were listed on the command line, +and if recursing, traverses their contents in whichever order they +were returned in by the kernel, which may be the order in which they +were created, lexicographical order, or something else entirely. +With +.Fl S , +the sources are both visited and traversed in lexicographical order. +This is mostly useful for testing. .It Fl s Create symbolic links to regular files in a hierarchy instead of copying. .It Fl v -Cause -.Nm -to be verbose, showing files as they are copied. +Be verbose, showing both the source and destination path of each file +as is copied. .It Fl x -File system mount points are not traversed. +Do not traverse file system mount points. .El .Pp For each destination file that already exists, its contents are diff --git a/bin/cp/cp.c b/bin/cp/cp.c --- a/bin/cp/cp.c +++ b/bin/cp/cp.c @@ -69,8 +69,8 @@ #define END(buf) (buf + sizeof(buf)) PATH_T to = { .dir = -1, .end = to.path }; -int Nflag, fflag, iflag, lflag, nflag, pflag, sflag, vflag; -static int Hflag, Lflag, Pflag, Rflag, rflag; +bool Nflag, fflag, iflag, lflag, nflag, pflag, sflag, vflag; +static bool Hflag, Lflag, Pflag, Rflag, rflag, Sflag; volatile sig_atomic_t info; enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE }; @@ -88,59 +88,62 @@ bool have_trailing_slash = false; fts_options = FTS_NOCHDIR | FTS_PHYSICAL; - while ((ch = getopt(argc, argv, "HLPRafilNnprsvx")) != -1) + while ((ch = getopt(argc, argv, "HLPRafilNnprSsvx")) != -1) switch (ch) { case 'H': - Hflag = 1; - Lflag = Pflag = 0; + Hflag = true; + Lflag = Pflag = false; break; case 'L': - Lflag = 1; - Hflag = Pflag = 0; + Lflag = true; + Hflag = Pflag = false; break; case 'P': - Pflag = 1; - Hflag = Lflag = 0; + Pflag = true; + Hflag = Lflag = false; break; case 'R': - Rflag = 1; + Rflag = true; break; case 'a': - pflag = 1; - Rflag = 1; - Pflag = 1; - Hflag = Lflag = 0; + pflag = true; + Rflag = true; + Pflag = true; + Hflag = Lflag = false; break; case 'f': - fflag = 1; - iflag = nflag = 0; + fflag = true; + iflag = nflag = false; break; case 'i': - iflag = 1; - fflag = nflag = 0; + iflag = true; + fflag = nflag = false; break; case 'l': - lflag = 1; + lflag = true; break; case 'N': - Nflag = 1; + Nflag = true; break; case 'n': - nflag = 1; - fflag = iflag = 0; + nflag = true; + fflag = iflag = false; break; case 'p': - pflag = 1; + pflag = true; break; case 'r': - rflag = Lflag = 1; - Hflag = Pflag = 0; + rflag = Lflag = true; + Hflag = Pflag = false; + break; + case 'S': + Sflag = true; break; case 's': - sflag = 1; + sflag = true; break; case 'v': - vflag = 1; + vflag = true; break; case 'x': fts_options |= FTS_XDEV; @@ -159,7 +162,7 @@ if (lflag && sflag) errx(1, "the -l and -s options may not be specified together"); if (rflag) - Rflag = 1; + Rflag = true; if (Rflag) { if (Hflag) fts_options |= FTS_COMFOLLOW; @@ -262,6 +265,12 @@ &to_stat))); } +static int +ftscmp(const FTSENT * const *a, const FTSENT * const *b) +{ + return (strcmp((*a)->fts_name, (*b)->fts_name)); +} + static int copy(char *argv[], enum op type, int fts_options, struct stat *root_stat) { @@ -305,7 +314,7 @@ } level = FTS_ROOTLEVEL; - if ((ftsp = fts_open(argv, fts_options, NULL)) == NULL) + if ((ftsp = fts_open(argv, fts_options, Sflag ? ftscmp : NULL)) == NULL) err(1, "fts_open"); for (badcp = rval = 0; (curr = fts_read(ftsp)) != NULL; diff --git a/bin/cp/extern.h b/bin/cp/extern.h --- a/bin/cp/extern.h +++ b/bin/cp/extern.h @@ -37,7 +37,7 @@ } PATH_T; extern PATH_T to; -extern int Nflag, fflag, iflag, lflag, nflag, pflag, sflag, vflag; +extern bool Nflag, fflag, iflag, lflag, nflag, pflag, sflag, vflag; extern volatile sig_atomic_t info; __BEGIN_DECLS diff --git a/bin/cp/tests/cp_test.sh b/bin/cp/tests/cp_test.sh --- a/bin/cp/tests/cp_test.sh +++ b/bin/cp/tests/cp_test.sh @@ -649,7 +649,7 @@ atf_check \ -s exit:1 \ -e match:"^cp: src/b: Permission denied" \ - cp -R src dst + cp -RS src dst atf_check test -d dst/a atf_check \ -o inline:"a\n" \ @@ -676,7 +676,7 @@ atf_check \ -s exit:1 \ -e match:"^cp: src/b: Permission denied" \ - cp -R src dst + cp -RS src dst atf_check test -d dst atf_check \ -o inline:"a\n" \ diff --git a/bin/cp/utils.c b/bin/cp/utils.c --- a/bin/cp/utils.c +++ b/bin/cp/utils.c @@ -105,7 +105,7 @@ ssize_t wcount; off_t wtotal; int ch, checkch, from_fd, rval, to_fd; - int use_copy_file_range = 1; + bool use_copy_file_range = true; fs = entp->fts_statp; from_fd = to_fd = -1; @@ -210,7 +210,7 @@ to_fd, NULL, SSIZE_MAX, 0); if (wcount < 0 && errno == EINVAL) { /* probably a non-seekable descriptor */ - use_copy_file_range = 0; + use_copy_file_range = false; } } if (!use_copy_file_range) {