aboutsummaryrefslogtreecommitdiff
path: root/toys/posix/cp.c
diff options
context:
space:
mode:
Diffstat (limited to 'toys/posix/cp.c')
-rw-r--r--toys/posix/cp.c227
1 files changed, 95 insertions, 132 deletions
diff --git a/toys/posix/cp.c b/toys/posix/cp.c
index e25dad46..ced756aa 100644
--- a/toys/posix/cp.c
+++ b/toys/posix/cp.c
@@ -2,13 +2,13 @@
*
* See http://opengroup.org/onlinepubs/9699919799/utilities/cp.html
*
- * TODO: "R+ra+d+p+r" sHLPR
+ * TODO: sHLP
USE_CP(NEWTOY(cp, "<2"USE_CP_MORE("rdavsl")"RHLPfip", TOYFLAG_BIN))
config CP
- bool "cp (broken by dirtree changes)"
- default n
+ bool "cp"
+ default y
help
usage: cp [-fipRHLP] SOURCE... DEST
@@ -45,175 +45,138 @@ config CP_MORE
GLOBALS(
char *destname;
- int destisdir;
- int keep_symlinks;
+ struct stat top;
)
-// Copy an individual file or directory to target.
+// Callback from dirtree_read() for each file/directory under a source dir.
-void cp_file(char *src, char *dst, struct stat *srcst)
+int cp_node(struct dirtree *try)
{
- int fdout = -1;
+ int fdout, cfd = try->parent ? try->parent->extra : AT_FDCWD,
+ tfd = dirtree_parentfd(try);
+ char *catch = try->parent ? try->name : TT.destname, *err = "%s";
+ struct stat cst;
+
+ if (!dirtree_notdotdot(try)) return 0;
+
+ // If returning from COMEAGAIN, jump straight to -p logic at end.
+ if (S_ISDIR(try->st.st_mode) && try->data == -1) {
+ fdout = try->extra;
+ err = 0;
+ goto dashp;
+ }
+
+ // 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
+ && (catch = TT.destname))
+ || (!fstatat(cfd, catch, &cst, 0) && cst.st_dev == try->st.st_dev
+ && cst.st_ino == try->st.st_ino))
+ {
+ char *s = dirtree_path(try, 0);
+ error_msg("'%s' is '%s'", catch, s);
+ free(s);
+
+ return 0;
+ }
- // -i flag is specified and dst file exists.
- if ((toys.optflags&FLAG_i) && !access(dst, R_OK)
- && !yesno("cp: overwrite", 1))
- return;
+ // Handle -i and -v
- if (toys.optflags & FLAG_v) printf("'%s' -> '%s'\n", src, dst);
+ if ((toys.optflags & FLAG_i) && !faccessat(cfd, catch, R_OK, 0)
+ && !yesno("cp: overwrite", 1)) return 0;
+
+ if (toys.optflags & FLAG_v) {
+ char *s = dirtree_path(try, 0);
+ printf("cp '%s'\n", s);
+ free(s);
+ }
// Copy directory or file to destination.
- if (S_ISDIR(srcst->st_mode)) {
+ if (S_ISDIR(try->st.st_mode)) {
struct stat st2;
+ if (!(toys.optflags & (FLAG_a|FLAG_r|FLAG_R))) {
+ err = "Skipped dir '%s'";
+ catch = try->name;
+
// Always make directory writeable to us, so we can create files in it.
//
// Yes, there's a race window between mkdir() and open() so it's
// possible that -p can be made to chown a directory other than the one
- // we created. The closest we can do to closing this is make sure
+ // we created. The closest we can do to closing this is make sure
// that what we open _is_ a directory rather than something else.
- if ((mkdir(dst, srcst->st_mode | 0200) && errno != EEXIST)
- || 0>(fdout=open(dst, 0)) || fstat(fdout, &st2) || !S_ISDIR(st2.st_mode))
- {
- perror_exit("mkdir '%s'", dst);
- }
- } else if (TT.keep_symlinks && S_ISLNK(srcst->st_mode)) {
- char *link = xreadlink(src);
-
- // Note: -p currently has no effect on symlinks. How do you get a
- // filehandle to them? O_NOFOLLOW causes the open to fail.
- if (!link || symlink(link, dst)) perror_msg("link '%s'", dst);
- free(link);
- return;
+ } else if ((mkdirat(cfd, catch, try->st.st_mode | 0200) && errno != EEXIST)
+ || 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) {
- if (link(src, dst)) perror_msg("link '%s'");
- return;
+ if (!linkat(tfd, try->name, cfd, catch, 0)) err = 0;
} else {
int fdin, i;
- fdin = xopen(src, O_RDONLY);
- for (i=2 ; i; i--) {
- fdout = open(dst, O_RDWR|O_CREAT|O_TRUNC, srcst->st_mode);
- if (fdout>=0 || !(toys.optflags & FLAG_f)) break;
- unlink(dst);
+ fdin = openat(tfd, try->name, O_RDONLY);
+ if (fdin < 0) catch = try->name;
+ 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;
+ unlinkat(cfd, catch, 0);
+ }
+ if (fdout >= 0) {
+ xsendfile(fdin, fdout);
+ err = 0;
+ }
+ close(fdin);
}
- if (fdout<0) perror_exit("%s", dst);
- xsendfile(fdin, fdout);
- close(fdin);
- }
-
- // Inability to set these isn't fatal, some require root access.
- // Can't do fchmod() etc here because -p works on mkdir, too.
- if (toys.optflags & (FLAG_p|FLAG_a)) {
- int mask = umask(0);
- struct utimbuf ut;
+dashp:
+ if (toys.optflags & (FLAG_a|FLAG_p)) {
+ struct timespec times[2];
- (void) fchown(fdout,srcst->st_uid, srcst->st_gid);
- ut.actime = srcst->st_atime;
- ut.modtime = srcst->st_mtime;
- utime(dst, &ut);
- umask(mask);
- }
- xclose(fdout);
-}
-
-// Callback from dirtree_read() for each file/directory under a source dir.
+ // Inability to set these isn't fatal, some require root access.
-int cp_node(struct dirtree *node)
-{
- char *path = dirtree_path(node, 0); // TODO: use openat() instead
- char *s = path+strlen(path);
- struct dirtree *n;
-
- // Find appropriate chunk of path for destination.
+ 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);
+ }
- n = node;
- if (!TT.destisdir) n = n->parent;
- for (;;n = n->parent) {
- while (s!=path) if (*(--s)=='/') break;
- if (!n) break;
+ xclose(fdout);
}
- if (s != path) s++;
-
- s = xmsprintf("%s/%s", TT.destname, s);
- cp_file(path, s, &(node->st));
- free(s);
- free(path); // redo this whole darn function.
+ if (err) perror_msg(err, catch);
return 0;
}
void cp_main(void)
{
- char *dpath = NULL;
- struct stat st, std;
- int i;
+ char *destname = toys.optargs[--toys.optc];
+ int i, destdir = !stat(destname, &TT.top) && S_ISDIR(TT.top.st_mode);
- // Identify destination
-
- if (!stat(TT.destname, &std) && S_ISDIR(std.st_mode)) TT.destisdir++;
- else if (toys.optc>1) error_exit("'%s' not directory", TT.destname);
-
- // TODO: This is too early: we haven't created it yet if we need to
- if (toys.optflags & (FLAG_R|FLAG_r|FLAG_a))
- dpath = realpath(TT.destname = toys.optargs[--toys.optc], NULL);
+ if (toys.optc>1 && !destdir) error_exit("'%s' not directory", destname);
// Loop through sources
for (i=0; i<toys.optc; i++) {
- char *dst, *src = toys.optargs[i];
-
- // Skip src==dest (TODO check inodes to catch "cp blah ./blah").
-
- if (!strncmp(src, TT.destname)) continue;
+ struct dirtree *new;
+ char *src = toys.optargs[i];
- // Skip nonexistent sources.
-
- TT.keep_symlinks = toys.optflags & (FLAG_d|FLAG_a);
- if (TT.keep_symlinks ? lstat(src, &st) : stat(src, &st)
- || (st.st_dev = dst.st_dev && st.st_ino == dst.dst_ino))
- {
-objection:
+ // Skip nonexistent sources
+ if (!(new = dirtree_add_node(0, src, !(toys.optflags & (FLAG_d|FLAG_a)))))
perror_msg("bad '%s'", src);
- toys.exitval = 1;
- continue;
+ else {
+ if (destdir) TT.destname = xmsprintf("%s/%s", destname, basename(src));
+ else TT.destname = destname;
+ dirtree_handle_callback(new, cp_node);
+ if (destdir) free(TT.destname);
}
-
- // Copy directory or file.
-
- if (TT.destisdir) {
- char *s;
-
- // Catch "cp -R .. ." and friends that would go on forever
- if (dpath && (s = realpath(src, NULL)) {
- int i = strlen(s);
- i = (!strncmp(s, dst, i) && (!s[i] || s[i]=='/'));
- free(s);
-
- if (i) goto objection;
- }
-
- // Create destination filename within directory
- dst = strrchr(src, '/');
- if (dst) dst++;
- else dst=src;
- dst = xmsprintf("%s/%s", TT.destname, dst);
- } else dst = TT.destname;
-
- if (S_ISDIR(st.st_mode)) {
- if (toys.optflags & (FLAG_r|FLAG_R|FLAG_a)) {
- cp_file(src, dst, &st);
-
- TT.keep_symlinks++;
- dirtree_read(src, cp_node);
- } else error_msg("Skipped dir '%s'", src);
- } else cp_file(src, dst, &st);
- if (TT.destisdir) free(dst);
}
-
- if (CFG_TOYBOX_FREE) free(dpath);
- return;
}