aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--toys/posix/cp.c29
1 files changed, 18 insertions, 11 deletions
diff --git a/toys/posix/cp.c b/toys/posix/cp.c
index ced756aa..9f22a6f0 100644
--- a/toys/posix/cp.c
+++ b/toys/posix/cp.c
@@ -54,6 +54,7 @@ int cp_node(struct dirtree *try)
{
int fdout, cfd = try->parent ? try->parent->extra : AT_FDCWD,
tfd = dirtree_parentfd(try);
+ unsigned flags = toys.optflags;
char *catch = try->parent ? try->name : TT.destname, *err = "%s";
struct stat cst;
@@ -66,6 +67,9 @@ int cp_node(struct dirtree *try)
goto dashp;
}
+ // -d is only the same as -r for symlinks, not for directories
+ if (S_ISLNK(try->st.st_mode) & (flags & FLAG_d)) flags |= FLAG_r;
+
// Detect recursive copies via repeated top node (cp -R .. .) or
// identical source/target (fun with hardlinks).
if ((TT.top.st_dev == try->st.st_dev && TT.top.st_ino == try->st.st_ino
@@ -82,10 +86,10 @@ int cp_node(struct dirtree *try)
// Handle -i and -v
- if ((toys.optflags & FLAG_i) && !faccessat(cfd, catch, R_OK, 0)
+ if ((flags & FLAG_i) && !faccessat(cfd, catch, R_OK, 0)
&& !yesno("cp: overwrite", 1)) return 0;
- if (toys.optflags & FLAG_v) {
+ if (flags & FLAG_v) {
char *s = dirtree_path(try, 0);
printf("cp '%s'\n", s);
free(s);
@@ -96,7 +100,7 @@ int cp_node(struct dirtree *try)
if (S_ISDIR(try->st.st_mode)) {
struct stat st2;
- if (!(toys.optflags & (FLAG_a|FLAG_r|FLAG_R))) {
+ if (!(flags & (FLAG_a|FLAG_r|FLAG_R))) {
err = "Skipped dir '%s'";
catch = try->name;
@@ -111,13 +115,16 @@ int cp_node(struct dirtree *try)
|| 0>(try->extra = openat(cfd, catch, 0)) || fstat(try->extra, &st2)
|| !S_ISDIR(st2.st_mode));
else return DIRTREE_COMEAGAIN;
- } else if (S_ISLNK(try->st.st_mode)
- && (try->parent || (toys.optflags & (FLAG_a|FLAG_d))))
- {
- int i = readlinkat(tfd, try->name, toybuf, sizeof(toybuf));
- if (i > 0 && i < sizeof(toybuf) && !symlinkat(toybuf, cfd, catch)) err = 0;
- } else if (toys.optflags & FLAG_l) {
+ } else if (flags & FLAG_l) {
if (!linkat(tfd, try->name, cfd, catch, 0)) err = 0;
+ } else if ((try->parent || (flags & (FLAG_a|FLAG_r)))
+ && !S_ISREG(try->st.st_mode))
+ {
+ if (S_ISLNK(try->st.st_mode)) {
+ int i = readlinkat(tfd, try->name, toybuf, sizeof(toybuf));
+ if (i > 0 && i < sizeof(toybuf) && !symlinkat(toybuf, cfd, catch))
+ err = 0;
+ } else if (!mknodat(cfd, catch, try->st.st_mode, try->st.st_dev)) err = 0;
} else {
int fdin, i;
@@ -126,7 +133,7 @@ int cp_node(struct dirtree *try)
else {
for (i=2 ; i; i--) {
fdout = openat(cfd, catch, O_RDWR|O_CREAT|O_TRUNC, try->st.st_mode);
- if (fdout>=0 || !(toys.optflags & FLAG_f)) break;
+ if (fdout>=0 || !(flags & FLAG_f)) break;
unlinkat(cfd, catch, 0);
}
if (fdout >= 0) {
@@ -137,7 +144,7 @@ int cp_node(struct dirtree *try)
}
dashp:
- if (toys.optflags & (FLAG_a|FLAG_p)) {
+ if (!(flags & FLAG_l) && (flags & (FLAG_a|FLAG_p))) {
struct timespec times[2];
// Inability to set these isn't fatal, some require root access.