aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Landley <rob@landley.net>2013-01-16 06:57:44 -0600
committerRob Landley <rob@landley.net>2013-01-16 06:57:44 -0600
commit6be5ac6a9287f4c48daf60d36eae44f04d7f9a80 (patch)
treea05685219498613a76a3f82517f38612ebbb42c7
parentae9243aa425458eda4e3e0b8bc7dd8f19fc8113a (diff)
downloadtoybox-6be5ac6a9287f4c48daf60d36eae44f04d7f9a80.tar.gz
Make "sudo cp -rp /dev/null blah" work. Still not happy with it, fchmodat(AT_SYMLINK_NOFOLLOW) doesn't work (there's a glibc bug open for this. It's really a missing kernel syscall, but glibc fails without ever making any syscall if you feed it that flag, which isn't helpful).
-rw-r--r--toys/posix/cp.c29
1 files changed, 21 insertions, 8 deletions
diff --git a/toys/posix/cp.c b/toys/posix/cp.c
index d9a0d29c..55f4fe71 100644
--- a/toys/posix/cp.c
+++ b/toys/posix/cp.c
@@ -97,6 +97,8 @@ int cp_node(struct dirtree *try)
// Loop for -f retry after unlink
do {
+ // directory, hardlink, symlink, mknod (char, block, fifo, socket), file
+
// Copy directory
if (S_ISDIR(try->st.st_mode)) {
@@ -116,7 +118,7 @@ int cp_node(struct dirtree *try)
// that what we open _is_ a directory rather than something else.
if (!mkdirat(cfd, catch, try->st.st_mode | 0200) || errno == EEXIST)
- if (-1 != (try->extra = openat(cfd, catch, 0)))
+ if (-1 != (try->extra = openat(cfd, catch, O_NOFOLLOW)))
if (!fstat(try->extra, &st2))
if (S_ISDIR(st2.st_mode)) return DIRTREE_COMEAGAIN;
@@ -135,9 +137,10 @@ int cp_node(struct dirtree *try)
if (i < 1 || i >= sizeof(toybuf)) break;
else if (!symlinkat(toybuf, cfd, catch)) err = 0;
// block, char, fifo, socket
- } else if (!mknodat(cfd, catch, try->st.st_mode, try->st.st_dev))
- if (!(flags & (FLAG_a|FLAG_p))
- || -1 != (fdout = openat(cfd, catch, O_RDONLY))) err = 0;
+ } else if (!mknodat(cfd, catch, try->st.st_mode, try->st.st_rdev)) {
+ err = 0;
+ if (flags & (FLAG_a|FLAG_p)) fdout = AT_FDCWD;
+ }
// Copy contents of file.
} else {
@@ -165,14 +168,24 @@ int cp_node(struct dirtree *try)
// Inability to set these isn't fatal, some require root access.
- fchown(fdout, try->st.st_uid, try->st.st_gid);
times[0] = try->st.st_atim;
times[1] = try->st.st_mtim;
- futimens(fdout, times);
- fchmod(fdout, try->st.st_mode);
+
+ // If we can't get a filehandle to the actual object, use racy functions
+ if (fdout == AT_FDCWD) {
+ if (fchownat(cfd, catch, try->st.st_uid, try->st.st_gid,
+ AT_SYMLINK_NOFOLLOW)
+ || utimensat(cfd, catch, times, AT_SYMLINK_NOFOLLOW)
+ || fchmodat(cfd, catch, try->st.st_mode&07777, 0))
+ err = "%s";
+ } else {
+ if (fchown(fdout, try->st.st_uid, try->st.st_gid)
+ || futimens(fdout, times) || fchmod(fdout, try->st.st_mode&07777))
+ err = "%s";
+ }
}
- xclose(fdout);
+ if (fdout != AT_FDCWD) xclose(fdout);
}
if (err) perror_msg(err, catch);