diff --git a/bin/cp/cp.c b/bin/cp/cp.c --- a/bin/cp/cp.c +++ b/bin/cp/cp.c @@ -331,10 +331,18 @@ 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 @@ (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 @@ 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 @@ } 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 @@ */ 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 @@ * 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 --- a/bin/cp/tests/cp_test.sh +++ b/bin/cp/tests/cp_test.sh @@ -557,6 +557,28 @@ 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_add_test_case to_dirlink atf_add_test_case to_deaddirlink atf_add_test_case to_link_outside + atf_add_test_case dstmode }