Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 10 May 2025 08:57:15 GMT
From:      Dag-Erling =?utf-8?Q?Sm=C3=B8rgrav?= <des@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 48578dcb6b7e - main - cp: Fix issues with destination directory mode.
Message-ID:  <202505100857.54A8vFOZ059761@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by des:

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

commit 48578dcb6b7ea14e095bf783152b5bc66c045a10
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2025-05-10 08:55:41 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2025-05-10 08:55:41 +0000

    cp: Fix issues with destination directory mode.
    
    Ensure that we are able to enter the destination directory after we
    create it, even if the current umask would normally prevent it, and
    that it has the expected permissions once we are done, even if we had
    to tweak them to be able to enter it.
    
    Fixes:          82fc0d09e862
    Sponsored by:   Klara, Inc.
    Reviewed by:    markj
    Differential Revision:  https://reviews.freebsd.org/D50266
---
 bin/cp/cp.c             | 33 +++++++++++++++++++++++++++++----
 bin/cp/tests/cp_test.sh | 23 +++++++++++++++++++++++
 2 files changed, 52 insertions(+), 4 deletions(-)

diff --git a/bin/cp/cp.c b/bin/cp/cp.c
index a3c8d910639c..c6b34198f20a 100644
--- a/bin/cp/cp.c
+++ b/bin/cp/cp.c
@@ -331,10 +331,18 @@ copy(char *argv[], enum op type, int fts_options, struct stat *root_stat)
 				assert(to.dir < 0);
 				assert(root_stat == NULL);
 				mode = curr_stat->st_mode | S_IRWXU;
+				/*
+				 * Will our umask prevent us from entering
+				 * the directory after we create it?
+				 */
+				if (~mask & S_IRWXU)
+					umask(~mask & ~S_IRWXU);
 				if (mkdir(to.base, mode) != 0) {
 					warn("%s", to.base);
 					fts_set(ftsp, curr, FTS_SKIP);
 					badcp = rval = 1;
+					if (~mask & S_IRWXU)
+						umask(~mask);
 					continue;
 				}
 				to.dir = open(to.base, O_DIRECTORY | O_SEARCH);
@@ -343,6 +351,8 @@ copy(char *argv[], enum op type, int fts_options, struct stat *root_stat)
 					(void)rmdir(to.base);
 					fts_set(ftsp, curr, FTS_SKIP);
 					badcp = rval = 1;
+					if (~mask & S_IRWXU)
+						umask(~mask);
 					continue;
 				}
 				if (fstat(to.dir, &created_root_stat) != 0) {
@@ -352,9 +362,14 @@ copy(char *argv[], enum op type, int fts_options, struct stat *root_stat)
 					fts_set(ftsp, curr, FTS_SKIP);
 					to.dir = -1;
 					badcp = rval = 1;
+					if (~mask & S_IRWXU)
+						umask(~mask);
 					continue;
 				}
+				if (~mask & S_IRWXU)
+					umask(~mask);
 				root_stat = &created_root_stat;
+				curr->fts_number = 1;
 			} else {
 				/* entering a directory; append its name to to.path */
 				len = snprintf(to.end, END(to.path) - to.end, "%s%s",
@@ -432,9 +447,7 @@ copy(char *argv[], enum op type, int fts_options, struct stat *root_stat)
 			} else if (curr->fts_number) {
 				const char *path = *to.path ? to.path : dot;
 				mode = curr_stat->st_mode;
-				if (((mode & (S_ISUID | S_ISGID | S_ISTXT)) ||
-				    ((mode | S_IRWXU) & mask) != (mode & mask)) &&
-				    fchmodat(to.dir, path, mode & mask, 0) != 0) {
+				if (fchmodat(to.dir, path, mode & mask, 0) != 0) {
 					warn("chmod: %s/%s", to.base, to.path);
 					rval = 1;
 				}
@@ -538,12 +551,22 @@ copy(char *argv[], enum op type, int fts_options, struct stat *root_stat)
 			 */
 			if (dne) {
 				mode = curr_stat->st_mode | S_IRWXU;
+				/*
+				 * Will our umask prevent us from entering
+				 * the directory after we create it?
+				 */
+				if (~mask & S_IRWXU)
+					umask(~mask & ~S_IRWXU);
 				if (mkdirat(to.dir, to.path, mode) != 0) {
 					warn("%s/%s", to.base, to.path);
 					fts_set(ftsp, curr, FTS_SKIP);
 					badcp = rval = 1;
+					if (~mask & S_IRWXU)
+						umask(~mask);
 					break;
 				}
+				if (~mask & S_IRWXU)
+					umask(~mask);
 			} else if (!S_ISDIR(to_stat.st_mode)) {
 				warnc(ENOTDIR, "%s/%s", to.base, to.path);
 				fts_set(ftsp, curr, FTS_SKIP);
@@ -554,8 +577,10 @@ copy(char *argv[], enum op type, int fts_options, struct stat *root_stat)
 			 * Arrange to correct directory attributes later
 			 * (in the post-order phase) if this is a new
 			 * directory, or if the -p flag is in effect.
+			 * Note that fts_number may already be set if this
+			 * is the newly created destination directory.
 			 */
-			curr->fts_number = pflag || dne;
+			curr->fts_number |= pflag || dne;
 			break;
 		case S_IFBLK:
 		case S_IFCHR:
diff --git a/bin/cp/tests/cp_test.sh b/bin/cp/tests/cp_test.sh
index bfc4009580cb..29dce783ffe2 100755
--- a/bin/cp/tests/cp_test.sh
+++ b/bin/cp/tests/cp_test.sh
@@ -557,6 +557,28 @@ to_link_outside_body()
 	    cp -r dir dst
 }
 
+atf_test_case dstmode
+dstmode_body()
+{
+	mkdir -m 0755 dir
+	echo "foo" >dir/file
+	umask 0177
+	#atf_check cp -R dir dst
+#begin
+	# atf-check stupidly refuses to work if the current umask is
+	# weird, instead of just dealing with the situation
+	cp -R dir dst >stdout 2>stderr
+	rc=$?
+	umask 022
+	atf_check_equal 0 $rc
+	atf_check cat stdout
+	atf_check cat stderr
+#end
+	atf_check -o inline:"40600\n" stat -f%p dst
+	atf_check chmod 0750 dst
+	atf_check cmp dir/file dst/file
+}
+
 atf_init_test_cases()
 {
 	atf_add_test_case basic
@@ -593,4 +615,5 @@ atf_init_test_cases()
 	atf_add_test_case to_dirlink
 	atf_add_test_case to_deaddirlink
 	atf_add_test_case to_link_outside
+	atf_add_test_case dstmode
 }



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202505100857.54A8vFOZ059761>