git: c3efa16dc9bc - main - cp: Add GNU-compatible long options.

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Wed, 09 Jul 2025 17:09:54 UTC
The branch main has been updated by des:

URL: https://cgit.FreeBSD.org/src/commit/?id=c3efa16dc9bcdcbe128c6b3c4b000867bbaf7991

commit c3efa16dc9bcdcbe128c6b3c4b000867bbaf7991
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2025-07-09 17:05:54 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2025-07-09 17:07:13 +0000

    cp: Add GNU-compatible long options.
    
    While here, fully switch boolean variables from int to bool, and clean
    up the manual page a little.
    
    Sponsored by:   Klara, Inc.
    Reviewed by:    kevans
    Differential Revision:  https://reviews.freebsd.org/D51213
---
 bin/cp/cp.1             | 39 +++++++++++--------------
 bin/cp/cp.c             | 78 +++++++++++++++++++++++++++++++------------------
 bin/cp/extern.h         |  2 +-
 bin/cp/tests/cp_test.sh | 16 ++++++++++
 bin/cp/utils.c          |  4 +--
 5 files changed, 86 insertions(+), 53 deletions(-)

diff --git a/bin/cp/cp.1 b/bin/cp/cp.1
index 2856391a029e..6edc8e403acd 100644
--- 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 9, 2025
 .Dt CP 1
 .Os
 .Sh NAME
@@ -84,16 +84,16 @@ If the
 .Fl R
 option is specified, symbolic links on the command line are followed.
 (Symbolic links encountered in the tree traversal are not followed.)
-.It Fl L
+.It Fl L , Fl -dereference
 If the
 .Fl R
 option is specified, all symbolic links are followed.
-.It Fl P
+.It Fl P , Fl -no-dereference
 No symbolic links are followed.
 This is the default if the
 .Fl R
 option is specified.
-.It Fl R
+.It Fl R , Fl -recursive
 If
 .Ar source_file
 designates a directory,
@@ -121,11 +121,11 @@ If you need to preserve hard links, consider using
 or
 .Xr pax 1
 instead.
-.It Fl a
+.It Fl a , Fl -archive
 Archive mode.
 Same as
 .Fl RpP .
-.It Fl f
+.It Fl f , Fl -force
 For each existing destination pathname, remove it and
 create a new file, without prompting for confirmation
 regardless of its permissions.
@@ -136,10 +136,8 @@ option overrides any previous
 or
 .Fl n
 options.)
-.It Fl i
-Cause
-.Nm
-to write a prompt to the standard error output before copying a file
+.It Fl i , Fl -interactive
+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
@@ -153,13 +151,13 @@ option overrides any previous
 or
 .Fl n
 options.)
-.It Fl l
+.It Fl l , Fl -link
 Create hard links to regular files in a hierarchy instead of copying.
 .It Fl N
 When used with
 .Fl p ,
 suppress copying file flags.
-.It Fl n
+.It Fl n , Fl -no-clobber
 Do not overwrite an existing file.
 (The
 .Fl n
@@ -169,9 +167,7 @@ or
 .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,13 @@ 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 s
+.It Fl s , Fl -symbolic-link
 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.
-.It Fl x
-File system mount points are not traversed.
+.It Fl v , Fl -verbose
+Be verbose, showing both the source and destination path of each file
+as is copied.
+.It Fl x , Fl -one-file-system
+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
index 7e97715c3ef4..a1b62084a790 100644
--- a/bin/cp/cp.c
+++ b/bin/cp/cp.c
@@ -55,6 +55,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <fts.h>
+#include <getopt.h>
 #include <limits.h>
 #include <signal.h>
 #include <stdbool.h>
@@ -69,8 +70,8 @@ static char dot[] = ".";
 
 #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;
 volatile sig_atomic_t info;
 
 enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE };
@@ -78,6 +79,26 @@ enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE };
 static int copy(char *[], enum op, int, struct stat *);
 static void siginfo(int __unused);
 
+enum {
+	SORT_OPT = CHAR_MAX,
+};
+
+static const struct option long_opts[] =
+{
+	{ "archive",		no_argument,		NULL,	'a' },
+	{ "force",		no_argument,		NULL,	'f' },
+	{ "interactive",	no_argument,		NULL,	'i' },
+	{ "dereference",	no_argument,		NULL,	'L' },
+	{ "link",		no_argument,		NULL,	'l' },
+	{ "no-clobber",		no_argument,		NULL,	'n' },
+	{ "no-dereference",	no_argument,		NULL,	'P' },
+	{ "recursive",		no_argument,		NULL,	'R' },
+	{ "symbolic-link",	no_argument,		NULL,	's' },
+	{ "verbose",		no_argument,		NULL,	'v' },
+	{ "one-file-system",	no_argument,		NULL,	'x' },
+	{ 0 }
+};
+
 int
 main(int argc, char *argv[])
 {
@@ -88,59 +109,60 @@ main(int argc, char *argv[])
 	bool have_trailing_slash = false;
 
 	fts_options = FTS_NOCHDIR | FTS_PHYSICAL;
-	while ((ch = getopt(argc, argv, "HLPRafilNnprsvx")) != -1)
+	while ((ch = getopt_long(argc, argv, "+HLPRafilNnprsvx", long_opts,
+	    NULL)) != -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 = 1;
+			sflag = true;
 			break;
 		case 'v':
-			vflag = 1;
+			vflag = true;
 			break;
 		case 'x':
 			fts_options |= FTS_XDEV;
@@ -159,7 +181,7 @@ main(int argc, char *argv[])
 	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;
diff --git a/bin/cp/extern.h b/bin/cp/extern.h
index c0c524756980..683e6e5f289f 100644
--- a/bin/cp/extern.h
+++ b/bin/cp/extern.h
@@ -37,7 +37,7 @@ typedef struct {
 } 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
index 1d2cd4292459..6adbc45c5009 100755
--- a/bin/cp/tests/cp_test.sh
+++ b/bin/cp/tests/cp_test.sh
@@ -688,6 +688,21 @@ unrfile_body()
 	atf_check cmp src/c dst/c
 }
 
+atf_test_case nopermute
+nopermute_head()
+{
+	atf_set descr "Check that getopt_long does not permute options"
+}
+nopermute_body()
+{
+	mkdir src dst
+	atf_check \
+	    -s exit:1 \
+	    -e match:'cp: -p: No such file' \
+	    cp -R src -p dst
+	atf_check test -d dst/src
+}
+
 atf_init_test_cases()
 {
 	atf_add_test_case basic
@@ -729,4 +744,5 @@ atf_init_test_cases()
 	atf_add_test_case dirloop
 	atf_add_test_case unrdir
 	atf_add_test_case unrfile
+	atf_add_test_case nopermute
 }
diff --git a/bin/cp/utils.c b/bin/cp/utils.c
index cfc0f0f12603..2036056ada68 100644
--- a/bin/cp/utils.c
+++ b/bin/cp/utils.c
@@ -105,7 +105,7 @@ copy_file(const FTSENT *entp, bool dne, bool beneath)
 	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 @@ copy_file(const FTSENT *entp, bool dne, bool beneath)
 			    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) {