Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F116010722
D50093.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
30 KB
Referenced Files
None
Subscribers
None
D50093.diff
View Options
diff --git a/bin/cp/cp.c b/bin/cp/cp.c
--- a/bin/cp/cp.c
+++ b/bin/cp/cp.c
@@ -53,9 +53,11 @@
#include <assert.h>
#include <err.h>
#include <errno.h>
+#include <fcntl.h>
#include <fts.h>
#include <limits.h>
#include <signal.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -63,15 +65,11 @@
#include "extern.h"
-#define STRIP_TRAILING_SLASH(p) { \
- while ((p).p_end > (p).p_path + 1 && (p).p_end[-1] == '/') \
- *--(p).p_end = 0; \
-}
-
-static char emptystring[] = "";
-
-PATH_T to = { to.p_path, emptystring, "" };
+static char dot[] = ".";
+#define END(buf) (buf + sizeof(buf))
+PATH_T to = { .dir = -1, .end = to.path };
+int cwd;
int Nflag, fflag, iflag, lflag, nflag, pflag, sflag, vflag;
static int Hflag, Lflag, Pflag, Rflag, rflag;
volatile sig_atomic_t info;
@@ -86,8 +84,9 @@
{
struct stat to_stat, tmp_stat;
enum op type;
- int ch, fts_options, r, have_trailing_slash;
- char *target;
+ int ch, fts_options, r;
+ char *sep, *target;
+ bool have_trailing_slash = false;
fts_options = FTS_NOCHDIR | FTS_PHYSICAL;
while ((ch = getopt(argc, argv, "HLPRafilNnprsvx")) != -1)
@@ -175,19 +174,22 @@
}
(void)signal(SIGINFO, siginfo);
+ /* Save current working directory */
+ if ((cwd = open(".", O_DIRECTORY | O_SEARCH)) < 0)
+ err(1, "current working directory");
+
/* Save the target base in "to". */
target = argv[--argc];
- if (strlcpy(to.p_path, target, sizeof(to.p_path)) >= sizeof(to.p_path))
- errx(1, "%s: name too long", target);
- to.p_end = to.p_path + strlen(to.p_path);
- if (to.p_path == to.p_end) {
- *to.p_end++ = '.';
- *to.p_end = 0;
+ if (*target == '\0')
+ target = dot;
+ if ((sep = strrchr(target, '/')) != NULL && sep[1] == '\0') {
+ have_trailing_slash = true;
+ while (sep > target + 1 && *(sep - 1) == '/')
+ sep--;
+ *sep = '\0';
}
- have_trailing_slash = (to.p_end[-1] == '/');
- if (have_trailing_slash)
- STRIP_TRAILING_SLASH(to);
- to.target_end = to.p_end;
+ if (strlcpy(to.base, target, sizeof(to.base)) >= sizeof(to.base))
+ errc(1, ENAMETOOLONG, "%s", target);
/* Set end of argument list for fts(3). */
argv[argc] = NULL;
@@ -206,15 +208,15 @@
*
* In (2), the real target is not directory, but "directory/source".
*/
- r = stat(to.p_path, &to_stat);
+ r = stat(to.base, &to_stat);
if (r == -1 && errno != ENOENT)
- err(1, "%s", to.p_path);
+ err(1, "%s", target);
if (r == -1 || !S_ISDIR(to_stat.st_mode)) {
/*
* Case (1). Target is not a directory.
*/
if (argc > 1)
- errc(1, ENOTDIR, "%s", to.p_path);
+ errc(1, ENOTDIR, "%s", target);
/*
* Need to detect the case:
@@ -238,9 +240,9 @@
if (have_trailing_slash && type == FILE_TO_FILE) {
if (r == -1)
- errc(1, ENOENT, "%s", to.p_path);
+ errc(1, ENOENT, "%s", target);
else
- errc(1, ENOTDIR, "%s", to.p_path);
+ errc(1, ENOTDIR, "%s", target);
}
} else {
/*
@@ -260,17 +262,34 @@
&to_stat)));
}
+/*
+ * Returns 0 if two descriptors refer to the same file system object, 1 if
+ * they don't, -1 if an error occurred.
+ */
+static int
+fdcmp(int fd1, int fd2)
+{
+ struct stat sb1, sb2;
+
+ if (fstat(fd1, &sb1) != 0 || fstat(fd2, &sb2) != 0)
+ return (-1);
+ if (sb1.st_dev == sb2.st_dev && sb1.st_ino == sb2.st_ino)
+ return (0);
+ return (1);
+}
+
static int
copy(char *argv[], enum op type, int fts_options, struct stat *root_stat)
{
char rootname[NAME_MAX];
- struct stat created_root_stat, to_stat;
+ struct stat created_root_stat, to_stat, *curr_stat;
FTS *ftsp;
FTSENT *curr;
- int base = 0, dne, badcp, rval;
- size_t nlen;
- char *p, *recurse_path, *target_mid;
+ char *recpath = NULL;
+ int atflags, dne, badcp, len, rval;
mode_t mask, mode;
+ bool beneath = type != FILE_TO_FILE;
+ bool skipdp = false;
/*
* Keep an inverted copy of the umask, for use in correcting
@@ -279,10 +298,24 @@
mask = ~umask(0777);
umask(~mask);
- recurse_path = NULL;
+ if (type == FILE_TO_FILE) {
+ to.dir = cwd;
+ to.end = to.path + strlcpy(to.path, to.base, sizeof(to.path));
+ strcpy(to.base, dot);
+ } else if (type == FILE_TO_DIR) {
+ to.dir = open(to.base, O_DIRECTORY | O_SEARCH);
+ if (to.dir < 0)
+ err(1, "%s", to.base);
+ if (fdcmp(to.dir, cwd) == 0) {
+ close(to.dir);
+ to.dir = cwd;
+ }
+ }
+
if ((ftsp = fts_open(argv, fts_options, NULL)) == NULL)
err(1, "fts_open");
- for (badcp = rval = 0; (curr = fts_read(ftsp)) != NULL; badcp = 0) {
+ for (badcp = rval = 0; (curr = fts_read(ftsp)) != NULL; badcp = 0, *to.end = '\0') {
+ curr_stat = curr->fts_statp;
switch (curr->fts_info) {
case FTS_NS:
case FTS_DNR:
@@ -294,119 +327,117 @@
warnx("%s: directory causes a cycle", curr->fts_path);
badcp = rval = 1;
continue;
- default:
- ;
- }
-
- /*
- * Stash the root basename off for detecting recursion later.
- *
- * This will be essential if the root is a symlink and we're
- * rolling with -L or -H. The later bits will need this bit in
- * particular.
- */
- if (curr->fts_level == FTS_ROOTLEVEL) {
- strlcpy(rootname, curr->fts_name, sizeof(rootname));
- }
-
- /*
- * If we are in case (2) or (3) above, we need to append the
- * source name to the target name.
- */
- if (type != FILE_TO_FILE) {
+ case FTS_D:
/*
- * Need to remember the roots of traversals to create
- * correct pathnames. If there's a directory being
- * copied to a non-existent directory, e.g.
- * cp -R a/dir noexist
- * the resulting path name should be noexist/foo, not
- * noexist/dir/foo (where foo is a file in dir), which
- * is the case where the target exists.
- *
- * Also, check for "..". This is for correct path
- * concatenation for paths ending in "..", e.g.
- * cp -R .. /tmp
- * Paths ending in ".." are changed to ".". This is
- * tricky, but seems the easiest way to fix the problem.
+ * Stash the root basename off for detecting
+ * recursion later.
*
- * XXX
- * Since the first level MUST be FTS_ROOTLEVEL, base
- * is always initialized.
+ * This will be essential if the root is a symlink
+ * and we're rolling with -L or -H. The later
+ * bits will need this bit in particular.
*/
if (curr->fts_level == FTS_ROOTLEVEL) {
- if (type != DIR_TO_DNE) {
- p = strrchr(curr->fts_path, '/');
- base = (p == NULL) ? 0 :
- (int)(p - curr->fts_path + 1);
-
- if (strcmp(curr->fts_path + base, "..")
- == 0)
- base += 1;
- } else
- base = curr->fts_pathlen;
+ strlcpy(rootname, curr->fts_name,
+ sizeof(rootname));
}
-
- p = &curr->fts_path[base];
- nlen = curr->fts_pathlen - base;
- target_mid = to.target_end;
- if (*p != '/' && target_mid[-1] != '/')
- *target_mid++ = '/';
- *target_mid = 0;
- if (target_mid - to.p_path + nlen >= PATH_MAX) {
- warnx("%s%s: name too long (not copied)",
- to.p_path, p);
- badcp = rval = 1;
- continue;
- }
- (void)strncat(target_mid, p, nlen);
- to.p_end = target_mid + nlen;
- *to.p_end = 0;
- STRIP_TRAILING_SLASH(to);
-
/*
- * We're on the verge of recursing on ourselves. Either
- * we need to stop right here (we knowingly just created
- * it), or we will in an immediate descendant. Record
- * the path of the immediate descendant to make our
- * lives a little less complicated looking.
+ * If we FTS_SKIP while handling FTS_D, we will
+ * immediately get FTS_DP for the same directory.
+ * If this happens before we've appended the name
+ * to to.path, we need to remember not to perform
+ * the reverse operation.
*/
- if (curr->fts_info == FTS_D && root_stat != NULL &&
- root_stat->st_dev == curr->fts_statp->st_dev &&
- root_stat->st_ino == curr->fts_statp->st_ino) {
- assert(recurse_path == NULL);
-
- if (root_stat == &created_root_stat) {
- /*
- * This directory didn't exist when we
- * started, we created it as part of
- * traversal. Stop right here before we
- * do something silly.
- */
+ skipdp = true;
+ /* we must have a destination! */
+ if (type == DIR_TO_DNE &&
+ curr->fts_level == FTS_ROOTLEVEL) {
+ assert(to.dir < 0);
+ assert(root_stat == NULL);
+ mode = curr_stat->st_mode | S_IRWXU;
+ if (mkdir(to.base, mode) != 0) {
+ warn("%s", to.base);
+ (void)fts_set(ftsp, curr, FTS_SKIP);
+ badcp = rval = 1;
+ continue;
+ }
+ to.dir = open(to.base, O_DIRECTORY | O_SEARCH);
+ if (to.dir < 0) {
+ warn("%s", to.base);
+ (void)rmdir(to.base);
+ (void)fts_set(ftsp, curr, FTS_SKIP);
+ badcp = rval = 1;
+ continue;
+ }
+ if (fstat(to.dir, &created_root_stat) != 0) {
+ warn("%s", to.base);
+ (void)close(to.dir);
+ (void)rmdir(to.base);
+ (void)fts_set(ftsp, curr, FTS_SKIP);
+ to.dir = -1;
+ badcp = rval = 1;
+ continue;
+ }
+ root_stat = &created_root_stat;
+ } else {
+ /* entering a directory; append its name to to.path */
+ len = snprintf(to.end, END(to.path) - to.end, "%s%s",
+ to.end > to.path ? "/" : "", curr->fts_name);
+ if (to.end + len >= END(to.path)) {
+ *to.end = '\0';
+ warnc(ENAMETOOLONG, "%s/%s%s%s", to.base,
+ to.path, to.end > to.path ? "/" : "",
+ curr->fts_name);
fts_set(ftsp, curr, FTS_SKIP);
+ badcp = rval = 1;
continue;
}
-
- if (asprintf(&recurse_path, "%s/%s", to.p_path,
- rootname) == -1)
- err(1, "asprintf");
+ to.end += len;
}
-
- if (recurse_path != NULL &&
- strcmp(to.p_path, recurse_path) == 0) {
+ skipdp = false;
+ /*
+ * We're on the verge of recursing on ourselves.
+ * Either we need to stop right here (we knowingly
+ * just created it), or we will in an immediate
+ * descendant. Record the path of the immediate
+ * descendant to make our lives a little less
+ * complicated looking.
+ */
+ if (type != FILE_TO_FILE &&
+ root_stat->st_dev == curr_stat->st_dev &&
+ root_stat->st_ino == curr_stat->st_ino) {
+ assert(recpath == NULL);
+ debug("recursion guard");
+ if (root_stat == &created_root_stat) {
+ /*
+ * This directory didn't exist
+ * when we started, we created it
+ * as part of traversal. Stop
+ * right here before we do
+ * something silly.
+ */
+ (void)fts_set(ftsp, curr, FTS_SKIP);
+ continue;
+ }
+ if (asprintf(&recpath, "%s/%s", to.path,
+ rootname) < 0) {
+ warnc(ENOMEM, NULL);
+ (void)fts_set(ftsp, curr, FTS_SKIP);
+ badcp = rval = 1;
+ continue;
+ }
+ }
+ if (recpath != NULL &&
+ strcmp(recpath, to.path) == 0) {
fts_set(ftsp, curr, FTS_SKIP);
continue;
}
- }
-
- if (curr->fts_info == FTS_DP) {
+ break;
+ case FTS_DP:
/*
* We are nearly finished with this directory. If we
* didn't actually copy it, or otherwise don't need to
* change its attributes, then we are done.
- */
- if (!curr->fts_number)
- continue;
- /*
+ *
* If -p is in effect, set all the attributes.
* Otherwise, set the correct permissions, limited
* by the umask. Optimise by avoiding a chmod()
@@ -415,41 +446,84 @@
* honour setuid, setgid and sticky bits, but we
* normally want to preserve them on directories.
*/
- if (pflag) {
- if (setfile(curr->fts_statp, -1))
+ if (curr->fts_number && pflag) {
+ if (setfile(curr_stat, -1, true))
rval = 1;
- if (preserve_dir_acls(curr->fts_statp,
- curr->fts_accpath, to.p_path) != 0)
+ if (preserve_dir_acls(curr_stat,
+ curr->fts_accpath, to.path) != 0)
rval = 1;
+ } else if (curr->fts_number) {
+ mode = curr_stat->st_mode;
+ if (((mode & (S_ISUID | S_ISGID | S_ISTXT)) ||
+ ((mode | S_IRWXU) & mask) != (mode & mask)) &&
+ fchmodat(to.dir, to.path, mode & mask, 0) != 0) {
+ warn("chmod: %s/%s", to.base, to.path);
+ rval = 1;
+ }
+ }
+ /* are we leaving a directory we failed to enter? */
+ if (skipdp)
+ continue;
+ /* leaving a directory; remove its name from to.path */
+ if (type == DIR_TO_DNE &&
+ curr->fts_level == FTS_ROOTLEVEL) {
+ /* this is actually our created root */
} else {
- mode = curr->fts_statp->st_mode;
- if ((mode & (S_ISUID | S_ISGID | S_ISTXT)) ||
- ((mode | S_IRWXU) & mask) != (mode & mask))
- if (chmod(to.p_path, mode & mask) !=
- 0) {
- warn("chmod: %s", to.p_path);
- rval = 1;
- }
+ while (to.end > to.path && *to.end != '/')
+ to.end--;
+ assert(strcmp(to.end + (*to.end == '/'), curr->fts_name) == 0);
+ *to.end = '\0';
}
continue;
+ default:
+ /* something else: append its name to to.path */
+ if (type == FILE_TO_FILE)
+ break;
+ len = snprintf(to.end, END(to.path) - to.end, "%s%s",
+ to.end > to.path ? "/" : "", curr->fts_name);
+ if (to.end + len >= END(to.path)) {
+ *to.end = '\0';
+ warnc(ENAMETOOLONG, "%s/%s%s%s", to.base,
+ to.path, to.end > to.path ? "/" : "",
+ curr->fts_name);
+ badcp = rval = 1;
+ continue;
+ }
+ /* intentionally do not update to.end */
+ break;
+ }
+
+ /* Not an error but need to remember it happened. */
+ if (to.path[0] == '\0') {
+ /*
+ * This can happen in two cases:
+ * - DIR_TO_DNE; we created the directory and
+ * populated root_stat earlier.
+ * - FILE_TO_DIR if a source has a trailing slash;
+ * the caller populated root_stat.
+ */
+ dne = false;
+ to_stat = *root_stat;
+ } else {
+ atflags = beneath ? AT_RESOLVE_BENEATH : 0;
+ if (curr->fts_info == FTS_D || curr->fts_info == FTS_SL)
+ atflags |= AT_SYMLINK_NOFOLLOW;
+ dne = fstatat(to.dir, to.path, &to_stat, atflags) != 0;
}
/* Check if source and destination are identical. */
- if (stat(to.p_path, &to_stat) == 0 &&
- to_stat.st_dev == curr->fts_statp->st_dev &&
- to_stat.st_ino == curr->fts_statp->st_ino) {
- warnx("%s and %s are identical (not copied).",
- to.p_path, curr->fts_path);
+ if (!dne &&
+ to_stat.st_dev == curr_stat->st_dev &&
+ to_stat.st_ino == curr_stat->st_ino) {
+ warnx("%s/%s and %s are identical (not copied).",
+ to.base, to.path, curr->fts_path);
badcp = rval = 1;
- if (S_ISDIR(curr->fts_statp->st_mode))
+ if (S_ISDIR(curr_stat->st_mode))
(void)fts_set(ftsp, curr, FTS_SKIP);
continue;
}
- /* Not an error but need to remember it happened. */
- dne = lstat(to.p_path, &to_stat) != 0;
-
- switch (curr->fts_statp->st_mode & S_IFMT) {
+ switch (curr_stat->st_mode & S_IFMT) {
case S_IFLNK:
if ((fts_options & FTS_LOGICAL) ||
((fts_options & FTS_COMFOLLOW) &&
@@ -460,11 +534,11 @@
* nonexistent or inaccessible. Let
* copy_file() deal with the error.
*/
- if (copy_file(curr, dne))
+ if (copy_file(curr, dne, beneath))
badcp = rval = 1;
} else {
/* Copy the link. */
- if (copy_link(curr, !dne))
+ if (copy_link(curr, dne, beneath))
badcp = rval = 1;
}
break;
@@ -485,31 +559,15 @@
* umask blocks owner writes, we fail.
*/
if (dne) {
- mode = curr->fts_statp->st_mode | S_IRWXU;
- if (mkdir(to.p_path, mode) != 0) {
- warn("%s", to.p_path);
- (void)fts_set(ftsp, curr, FTS_SKIP);
- badcp = rval = 1;
- break;
- }
- /*
- * First DNE with a NULL root_stat is the root
- * path, so set root_stat. We can't really
- * tell in all cases if the target path is
- * within the src path, so we just stat() the
- * first directory we created and use that.
- */
- if (root_stat == NULL &&
- stat(to.p_path, &created_root_stat) != 0) {
- warn("%s", to.p_path);
+ mode = curr_stat->st_mode | S_IRWXU;
+ if (mkdirat(to.dir, to.path, mode) != 0) {
+ warn("%s/%s", to.base, to.path);
(void)fts_set(ftsp, curr, FTS_SKIP);
badcp = rval = 1;
break;
}
- if (root_stat == NULL)
- root_stat = &created_root_stat;
} else if (!S_ISDIR(to_stat.st_mode)) {
- warnc(ENOTDIR, "%s", to.p_path);
+ warnc(ENOTDIR, "%s/%s", to.base, to.path);
(void)fts_set(ftsp, curr, FTS_SKIP);
badcp = rval = 1;
break;
@@ -524,10 +582,10 @@
case S_IFBLK:
case S_IFCHR:
if (Rflag && !sflag) {
- if (copy_special(curr->fts_statp, !dne))
+ if (copy_special(curr_stat, dne, beneath))
badcp = rval = 1;
} else {
- if (copy_file(curr, dne))
+ if (copy_file(curr, dne, beneath))
badcp = rval = 1;
}
break;
@@ -537,25 +595,26 @@
break;
case S_IFIFO:
if (Rflag && !sflag) {
- if (copy_fifo(curr->fts_statp, !dne))
+ if (copy_fifo(curr_stat, dne, beneath))
badcp = rval = 1;
} else {
- if (copy_file(curr, dne))
+ if (copy_file(curr, dne, beneath))
badcp = rval = 1;
}
break;
default:
- if (copy_file(curr, dne))
+ if (copy_file(curr, dne, beneath))
badcp = rval = 1;
break;
}
if (vflag && !badcp)
- (void)printf("%s -> %s\n", curr->fts_path, to.p_path);
+ (void)printf("%s -> %s/%s\n", curr->fts_path, to.base, to.path);
}
if (errno)
err(1, "fts_read");
fts_close(ftsp);
- free(recurse_path);
+ close(to.dir);
+ free(recpath);
return (rval);
}
diff --git a/bin/cp/extern.h b/bin/cp/extern.h
--- a/bin/cp/extern.h
+++ b/bin/cp/extern.h
@@ -30,22 +30,36 @@
*/
typedef struct {
- char *p_end; /* pointer to NULL at end of path */
- char *target_end; /* pointer to end of target base */
- char p_path[PATH_MAX]; /* pointer to the start of a path */
+ int dir; /* base directory handle */
+ char *end; /* pointer to NUL at end of path */
+ char base[PATH_MAX]; /* base directory path */
+ char path[PATH_MAX]; /* target path */
} PATH_T;
extern PATH_T to;
+extern int cwd;
extern int Nflag, fflag, iflag, lflag, nflag, pflag, sflag, vflag;
extern volatile sig_atomic_t info;
__BEGIN_DECLS
-int copy_fifo(struct stat *, int);
-int copy_file(const FTSENT *, int);
-int copy_link(const FTSENT *, int);
-int copy_special(struct stat *, int);
-int setfile(struct stat *, int);
+int copy_fifo(struct stat *, bool, bool);
+int copy_file(const FTSENT *, bool, bool);
+int copy_link(const FTSENT *, bool, bool);
+int copy_special(struct stat *, bool, bool);
+int setfile(struct stat *, int, bool);
int preserve_dir_acls(struct stat *, char *, char *);
int preserve_fd_acls(int, int);
void usage(void) __dead2;
__END_DECLS
+
+/*
+ * The FreeBSD kernel returns ENOTCAPABLE when a path lookup violates a
+ * RESOLVE_BENEATH constraint. This results in confusing error messages,
+ * so translate it to the more widely recognized EACCES.
+ */
+#ifdef __FreeBSD__
+#define warn(...) \
+ warnc(errno == ENOTCAPABLE ? EACCES : errno, __VA_ARGS__)
+#define err(rv, ...) \
+ errc(rv, errno == ENOTCAPABLE ? EACCES : errno, __VA_ARGS__)
+#endif
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
@@ -392,6 +392,31 @@
cp -r bar foo
}
+atf_test_case to_dir_dne
+to_dir_dne_body()
+{
+ mkdir dir
+ echo "foo" >dir/foo
+ atf_check cp -r dir dne
+ atf_check test -d dne
+ atf_check test -f dne/foo
+ atf_check cmp dir/foo dne/foo
+}
+
+atf_test_case to_nondir
+to_nondir_body()
+{
+ echo "foo" >foo
+ echo "bar" >bar
+ echo "baz" >baz
+ # This is described as “case 1” in source code comments
+ atf_check cp foo bar
+ atf_check cmp -s foo bar
+ # This is “case 2”, the target must be a directory
+ atf_check -s not-exit:0 -e match:"Not a directory" \
+ cp foo bar baz
+}
+
atf_test_case to_deadlink
to_deadlink_body()
{
@@ -401,6 +426,19 @@
atf_check cmp -s foo bar
}
+atf_test_case to_deadlink_append
+to_deadlink_append_body()
+{
+ echo "foo" >foo
+ mkdir bar
+ ln -s baz bar/foo
+ atf_check cp foo bar
+ atf_check cmp -s foo bar/foo
+ rm -f bar/foo
+ atf_check cp foo bar/
+ atf_check cmp -s foo bar/foo
+}
+
atf_test_case to_dirlink
to_dirlink_body()
{
@@ -456,6 +494,18 @@
cp -R src/ dst/
}
+atf_test_case to_link_outside
+to_link_outside_body()
+{
+ mkdir dir dst dst/dir
+ echo "foo" >dir/file
+ ln -s ../../file dst/dir/file
+ atf_check \
+ -s exit:1 \
+ -e match:"dst/dir/file: Permission denied" \
+ cp -r dir dst
+}
+
atf_init_test_cases()
{
atf_add_test_case basic
@@ -483,7 +533,11 @@
atf_add_test_case symlink_exists_force
atf_add_test_case directory_to_symlink
atf_add_test_case overwrite_directory
+ atf_add_test_case to_dir_dne
+ atf_add_test_case to_nondir
atf_add_test_case to_deadlink
+ atf_add_test_case to_deadlink_append
atf_add_test_case to_dirlink
atf_add_test_case to_deaddirlink
+ atf_add_test_case to_link_outside
}
diff --git a/bin/cp/utils.c b/bin/cp/utils.c
--- a/bin/cp/utils.c
+++ b/bin/cp/utils.c
@@ -38,6 +38,7 @@
#include <fcntl.h>
#include <fts.h>
#include <limits.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
@@ -98,7 +99,7 @@
}
int
-copy_file(const FTSENT *entp, int dne)
+copy_file(const FTSENT *entp, bool dne, bool beneath)
{
struct stat sb, *fs;
ssize_t wcount;
@@ -142,12 +143,13 @@
if (!dne) {
if (nflag) {
if (vflag)
- printf("%s not overwritten\n", to.p_path);
+ printf("%s/%s not overwritten\n",
+ to.base, to.path);
rval = 1;
goto done;
} else if (iflag) {
- (void)fprintf(stderr, "overwrite %s? %s",
- to.p_path, YESNO);
+ (void)fprintf(stderr, "overwrite %s/%s? %s",
+ to.base, to.path, YESNO);
checkch = ch = getchar();
while (ch != '\n' && ch != EOF)
ch = getchar();
@@ -160,7 +162,8 @@
if (fflag) {
/* remove existing destination file */
- (void)unlink(to.p_path);
+ (void)unlinkat(to.dir, to.path,
+ beneath ? AT_RESOLVE_BENEATH : 0);
dne = 1;
}
}
@@ -168,16 +171,16 @@
rval = 0;
if (lflag) {
- if (link(entp->fts_path, to.p_path) != 0) {
- warn("%s", to.p_path);
+ if (linkat(AT_FDCWD, entp->fts_path, to.dir, to.path, 0) != 0) {
+ warn("%s/%s", to.base, to.path);
rval = 1;
}
goto done;
}
if (sflag) {
- if (symlink(entp->fts_path, to.p_path) != 0) {
- warn("%s", to.p_path);
+ if (symlinkat(entp->fts_path, to.dir, to.path) != 0) {
+ warn("%s/%s", to.base, to.path);
rval = 1;
}
goto done;
@@ -185,14 +188,17 @@
if (!dne) {
/* overwrite existing destination file */
- to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0);
+ to_fd = openat(to.dir, to.path,
+ O_WRONLY | O_TRUNC | (beneath ? O_RESOLVE_BENEATH : 0), 0);
} else {
/* create new destination file */
- to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
+ to_fd = openat(to.dir, to.path,
+ O_WRONLY | O_TRUNC | O_CREAT |
+ (beneath ? O_RESOLVE_BENEATH : 0),
fs->st_mode & ~(S_ISUID | S_ISGID));
}
if (to_fd == -1) {
- warn("%s", to.p_path);
+ warn("%s/%s", to.base, to.path);
rval = 1;
goto done;
}
@@ -214,8 +220,8 @@
if (info) {
info = 0;
(void)fprintf(stderr,
- "%s -> %s %3d%%\n",
- entp->fts_path, to.p_path,
+ "%s -> %s/%s %3d%%\n",
+ entp->fts_path, to.base, to.path,
cp_pct(wtotal, fs->st_size));
}
} while (wcount > 0);
@@ -230,12 +236,12 @@
* or its contents might be irreplaceable. It would only be safe
* to remove it if we created it and its length is 0.
*/
- if (pflag && setfile(fs, to_fd))
+ if (pflag && setfile(fs, to_fd, beneath))
rval = 1;
if (pflag && preserve_fd_acls(from_fd, to_fd) != 0)
rval = 1;
if (close(to_fd)) {
- warn("%s", to.p_path);
+ warn("%s/%s", to.base, to.path);
rval = 1;
}
@@ -246,14 +252,15 @@
}
int
-copy_link(const FTSENT *p, int exists)
+copy_link(const FTSENT *p, bool dne, bool beneath)
{
ssize_t len;
+ int atflags = beneath ? AT_RESOLVE_BENEATH : 0;
char llink[PATH_MAX];
- if (exists && nflag) {
+ if (!dne && nflag) {
if (vflag)
- printf("%s not overwritten\n", to.p_path);
+ printf("%s/%s not overwritten\n", to.base, to.path);
return (1);
}
if ((len = readlink(p->fts_path, llink, sizeof(llink) - 1)) == -1) {
@@ -261,81 +268,86 @@
return (1);
}
llink[len] = '\0';
- if (exists && unlink(to.p_path)) {
- warn("unlink: %s", to.p_path);
+ if (!dne && unlinkat(to.dir, to.path, atflags) != 0) {
+ warn("unlink: %s/%s", to.base, to.path);
return (1);
}
- if (symlink(llink, to.p_path)) {
+ if (symlinkat(llink, to.dir, to.path) != 0) {
warn("symlink: %s", llink);
return (1);
}
- return (pflag ? setfile(p->fts_statp, -1) : 0);
+ return (pflag ? setfile(p->fts_statp, -1, beneath) : 0);
}
int
-copy_fifo(struct stat *from_stat, int exists)
+copy_fifo(struct stat *from_stat, bool dne, bool beneath)
{
+ int atflags = beneath ? AT_RESOLVE_BENEATH : 0;
- if (exists && nflag) {
+ if (!dne && nflag) {
if (vflag)
- printf("%s not overwritten\n", to.p_path);
+ printf("%s/%s not overwritten\n", to.base, to.path);
return (1);
}
- if (exists && unlink(to.p_path)) {
- warn("unlink: %s", to.p_path);
+ if (!dne && unlinkat(to.dir, to.path, atflags) != 0) {
+ warn("unlink: %s/%s", to.base, to.path);
return (1);
}
- if (mkfifo(to.p_path, from_stat->st_mode)) {
- warn("mkfifo: %s", to.p_path);
+ if (mkfifoat(to.dir, to.path, from_stat->st_mode) != 0) {
+ warn("mkfifo: %s/%s", to.base, to.path);
return (1);
}
- return (pflag ? setfile(from_stat, -1) : 0);
+ return (pflag ? setfile(from_stat, -1, beneath) : 0);
}
int
-copy_special(struct stat *from_stat, int exists)
+copy_special(struct stat *from_stat, bool dne, bool beneath)
{
+ int atflags = beneath ? AT_RESOLVE_BENEATH : 0;
- if (exists && nflag) {
+ if (!dne && nflag) {
if (vflag)
- printf("%s not overwritten\n", to.p_path);
+ printf("%s/%s not overwritten\n", to.base, to.path);
return (1);
}
- if (exists && unlink(to.p_path)) {
- warn("unlink: %s", to.p_path);
+ if (!dne && unlinkat(to.dir, to.path, atflags) != 0) {
+ warn("unlink: %s/%s", to.base, to.path);
return (1);
}
- if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) {
- warn("mknod: %s", to.p_path);
+ if (mknodat(to.dir, to.path, from_stat->st_mode, from_stat->st_rdev) != 0) {
+ warn("mknod: %s/%s", to.base, to.path);
return (1);
}
- return (pflag ? setfile(from_stat, -1) : 0);
+ return (pflag ? setfile(from_stat, -1, beneath) : 0);
}
int
-setfile(struct stat *fs, int fd)
+setfile(struct stat *fs, int fd, bool beneath)
{
static struct timespec tspec[2];
struct stat ts;
+ int atflags = beneath ? AT_RESOLVE_BENEATH : 0;
int rval, gotstat, islink, fdval;
rval = 0;
fdval = fd != -1;
islink = !fdval && S_ISLNK(fs->st_mode);
+ if (islink)
+ atflags |= AT_SYMLINK_NOFOLLOW;
fs->st_mode &= S_ISUID | S_ISGID | S_ISVTX |
S_IRWXU | S_IRWXG | S_IRWXO;
tspec[0] = fs->st_atim;
tspec[1] = fs->st_mtim;
- if (fdval ? futimens(fd, tspec) : utimensat(AT_FDCWD, to.p_path, tspec,
- islink ? AT_SYMLINK_NOFOLLOW : 0)) {
- warn("utimensat: %s", to.p_path);
+ if (fdval ? futimens(fd, tspec) :
+ utimensat(to.dir, to.path, tspec, atflags)) {
+ warn("utimensat: %s/%s", to.base, to.path);
rval = 1;
}
if (fdval ? fstat(fd, &ts) :
- (islink ? lstat(to.p_path, &ts) : stat(to.p_path, &ts)))
+ fstatat(to.dir, to.path, &ts, atflags)) {
gotstat = 0;
- else {
+ } else {
gotstat = 1;
ts.st_mode &= S_ISUID | S_ISGID | S_ISVTX |
S_IRWXU | S_IRWXG | S_IRWXO;
@@ -346,30 +358,28 @@
* the mode; current BSD behavior is to remove all setuid bits on
* chown. If chown fails, lose setuid/setgid bits.
*/
- if (!gotstat || fs->st_uid != ts.st_uid || fs->st_gid != ts.st_gid)
+ if (!gotstat || fs->st_uid != ts.st_uid || fs->st_gid != ts.st_gid) {
if (fdval ? fchown(fd, fs->st_uid, fs->st_gid) :
- (islink ? lchown(to.p_path, fs->st_uid, fs->st_gid) :
- chown(to.p_path, fs->st_uid, fs->st_gid))) {
+ fchownat(to.dir, to.path, fs->st_uid, fs->st_gid, atflags)) {
if (errno != EPERM) {
- warn("chown: %s", to.p_path);
+ warn("chown: %s/%s", to.base, to.path);
rval = 1;
}
fs->st_mode &= ~(S_ISUID | S_ISGID);
}
+ }
- if (!gotstat || fs->st_mode != ts.st_mode)
+ if (!gotstat || fs->st_mode != ts.st_mode) {
if (fdval ? fchmod(fd, fs->st_mode) :
- (islink ? lchmod(to.p_path, fs->st_mode) :
- chmod(to.p_path, fs->st_mode))) {
- warn("chmod: %s", to.p_path);
+ fchmodat(to.dir, to.path, fs->st_mode, atflags)) {
+ warn("chmod: %s/%s", to.base, to.path);
rval = 1;
}
+ }
- if (!Nflag && (!gotstat || fs->st_flags != ts.st_flags))
- if (fdval ?
- fchflags(fd, fs->st_flags) :
- (islink ? lchflags(to.p_path, fs->st_flags) :
- chflags(to.p_path, fs->st_flags))) {
+ if (!Nflag && (!gotstat || fs->st_flags != ts.st_flags)) {
+ if (fdval ? fchflags(fd, fs->st_flags) :
+ chflagsat(to.dir, to.path, fs->st_flags, atflags)) {
/*
* NFS doesn't support chflags; ignore errors unless
* there's reason to believe we're losing bits. (Note,
@@ -378,10 +388,11 @@
* that we copied, i.e., that we didn't create.)
*/
if (errno != EOPNOTSUPP || fs->st_flags != 0) {
- warn("chflags: %s", to.p_path);
+ warn("chflags: %s/%s", to.base, to.path);
rval = 1;
}
}
+ }
return (rval);
}
@@ -398,7 +409,8 @@
acl_supported = 1;
acl_type = ACL_TYPE_NFS4;
} else if (ret < 0 && errno != EINVAL) {
- warn("fpathconf(..., _PC_ACL_NFS4) failed for %s", to.p_path);
+ warn("fpathconf(..., _PC_ACL_NFS4) failed for %s/%s",
+ to.base, to.path);
return (1);
}
if (acl_supported == 0) {
@@ -407,8 +419,8 @@
acl_supported = 1;
acl_type = ACL_TYPE_ACCESS;
} else if (ret < 0 && errno != EINVAL) {
- warn("fpathconf(..., _PC_ACL_EXTENDED) failed for %s",
- to.p_path);
+ warn("fpathconf(..., _PC_ACL_EXTENDED) failed for %s/%s",
+ to.base, to.path);
return (1);
}
}
@@ -417,11 +429,13 @@
acl = acl_get_fd_np(source_fd, acl_type);
if (acl == NULL) {
- warn("failed to get acl entries while setting %s", to.p_path);
+ warn("failed to get acl entries while setting %s/%s",
+ to.base, to.path);
return (1);
}
if (acl_is_trivial_np(acl, &trivial)) {
- warn("acl_is_trivial() failed for %s", to.p_path);
+ warn("acl_is_trivial() failed for %s/%s",
+ to.base, to.path);
acl_free(acl);
return (1);
}
@@ -430,7 +444,8 @@
return (0);
}
if (acl_set_fd_np(dest_fd, acl, acl_type) < 0) {
- warn("failed to set acl entries for %s", to.p_path);
+ warn("failed to set acl entries for %s/%s",
+ to.base, to.path);
acl_free(acl);
return (1);
}
@@ -516,11 +531,19 @@
acl_free(acl);
return (0);
}
+ if (to.dir != cwd && fchdir(to.dir) != 0) {
+ acl_free(acl);
+ return (1);
+ }
if (aclsetf(dest_dir, acl_type, acl) < 0) {
warn("failed to set acl entries on %s", dest_dir);
+ if (to.dir != cwd)
+ (void)fchdir(cwd);
acl_free(acl);
return (1);
}
+ if (to.dir != cwd)
+ (void)fchdir(cwd);
acl_free(acl);
return (0);
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, May 2, 1:46 PM (1 h, 56 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
17890796
Default Alt Text
D50093.diff (30 KB)
Attached To
Mode
D50093: cp: Partly restore symlink folllowing.
Attached
Detach File
Event Timeline
Log In to Comment