The branch main has been updated by des: URL: https://cgit.FreeBSD.org/src/commit/?id=2d6b33f801d5352b8e078db83f6c90f6fe8291bb
commit 2d6b33f801d5352b8e078db83f6c90f6fe8291bb Author: Dag-Erling Smørgrav <d...@freebsd.org> AuthorDate: 2025-07-09 17:06:07 +0000 Commit: Dag-Erling Smørgrav <d...@freebsd.org> CommitDate: 2025-07-09 17:07:13 +0000 cp: Add an option to visit sources in order. This adds a --sort option which makes cp pass a comparison function to FTS, ensuring that sources are visited and traversed in a predictable order. This will help make certain test cases more reliable. Sponsored by: Klara, Inc. Reviewed by: kevans Differential Revision: https://reviews.freebsd.org/D51214 --- bin/cp/cp.1 | 12 ++++++++++++ bin/cp/cp.c | 14 ++++++++++++-- bin/cp/tests/cp_test.sh | 4 ++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/bin/cp/cp.1 b/bin/cp/cp.1 index 6edc8e403acd..5231fa72621c 100644 --- a/bin/cp/cp.1 +++ b/bin/cp/cp.1 @@ -184,6 +184,18 @@ If the source file has both its set-user-ID and set-group-ID bits on, 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 -sort +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 -sort , +the sources are both visited and traversed in lexicographical order. +This is mostly useful for testing. .It Fl s , Fl -symbolic-link Create symbolic links to regular files in a hierarchy instead of copying. .It Fl v , Fl -verbose diff --git a/bin/cp/cp.c b/bin/cp/cp.c index a1b62084a790..38fe65399d06 100644 --- a/bin/cp/cp.c +++ b/bin/cp/cp.c @@ -71,7 +71,7 @@ static char dot[] = "."; #define END(buf) (buf + sizeof(buf)) PATH_T to = { .dir = -1, .end = to.path }; bool Nflag, fflag, iflag, lflag, nflag, pflag, sflag, vflag; -static bool Hflag, Lflag, Pflag, Rflag, rflag; +static bool Hflag, Lflag, Pflag, Rflag, rflag, Sflag; volatile sig_atomic_t info; enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE }; @@ -96,6 +96,7 @@ static const struct option long_opts[] = { "symbolic-link", no_argument, NULL, 's' }, { "verbose", no_argument, NULL, 'v' }, { "one-file-system", no_argument, NULL, 'x' }, + { "sort", no_argument, NULL, SORT_OPT }, { 0 } }; @@ -167,6 +168,9 @@ main(int argc, char *argv[]) case 'x': fts_options |= FTS_XDEV; break; + case SORT_OPT: + Sflag = true; + break; default: usage(); } @@ -284,6 +288,12 @@ main(int argc, char *argv[]) &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) { @@ -327,7 +337,7 @@ copy(char *argv[], enum op type, int fts_options, struct stat *root_stat) } 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/tests/cp_test.sh b/bin/cp/tests/cp_test.sh index 6adbc45c5009..3c3dd309b9e4 100755 --- a/bin/cp/tests/cp_test.sh +++ b/bin/cp/tests/cp_test.sh @@ -657,7 +657,7 @@ unrdir_body() atf_check \ -s exit:1 \ -e match:"^cp: src/b: Permission denied" \ - cp -R src dst + cp -R --sort src dst atf_check test -d dst/a atf_check cmp src/a/f dst/a/f atf_check test -d dst/b @@ -681,7 +681,7 @@ unrfile_body() atf_check \ -s exit:1 \ -e match:"^cp: src/b: Permission denied" \ - cp -R src dst + cp -R --sort src dst atf_check test -d dst atf_check cmp src/a dst/a atf_check test ! -e dst/b