aboutsummaryrefslogtreecommitdiff
path: root/toys/posix/cp.c
diff options
context:
space:
mode:
authorRob Landley <rob@landley.net>2012-08-25 14:25:22 -0500
committerRob Landley <rob@landley.net>2012-08-25 14:25:22 -0500
commit3a9241add947cb6d24b5de7a8927517426a78795 (patch)
treed122ab6570439cd6b17c7d73ed8d4e085e0b8a95 /toys/posix/cp.c
parent689f095bc976417bf50810fe59a3b3ac32b21105 (diff)
downloadtoybox-3a9241add947cb6d24b5de7a8927517426a78795.tar.gz
Move commands into "posix", "lsb", and "other" menus/directories.
Diffstat (limited to 'toys/posix/cp.c')
-rw-r--r--toys/posix/cp.c224
1 files changed, 224 insertions, 0 deletions
diff --git a/toys/posix/cp.c b/toys/posix/cp.c
new file mode 100644
index 00000000..bc922b3d
--- /dev/null
+++ b/toys/posix/cp.c
@@ -0,0 +1,224 @@
+/* vi: set sw=4 ts=4:
+ *
+ * cp.c - Copy files.
+ *
+ * Copyright 2008 Rob Landley <rob@landley.net>
+ *
+ * See http://www.opengroup.org/onlinepubs/009695399/utilities/cp.html
+ *
+ * "R+ra+d+p+r"
+USE_CP(NEWTOY(cp, "<2vslrRdpaHLPif", TOYFLAG_BIN))
+
+config CP
+ bool "cp (broken by dirtree changes)"
+ default n
+ help
+ usage: cp -fiprdal SOURCE... DEST
+
+ Copy files from SOURCE to DEST. If more than one SOURCE, DEST must
+ be a directory.
+
+ -f force copy by deleting destination file
+ -i interactive, prompt before overwriting existing DEST
+ -p preserve timestamps, ownership, and permissions
+ -r recurse into subdirectories (DEST must be a directory)
+ -d don't dereference symlinks
+ -a same as -dpr
+ -l hard link instead of copying
+ -v verbose
+*/
+
+#include "toys.h"
+
+#define FLAG_f 1
+#define FLAG_i 2
+#define FLAG_P 4 // todo
+#define FLAG_L 8 // todo
+#define FLAG_H 16 // todo
+#define FLAG_a 32
+#define FLAG_p 64
+#define FLAG_d 128 // todo
+#define FLAG_R 256
+#define FLAG_r 512
+#define FLAG_l 1024 // todo
+#define FLAG_s 2048 // todo
+#define FLAG_v 4098
+
+DEFINE_GLOBALS(
+ char *destname;
+ int destisdir;
+ int destisnew;
+ int keep_symlinks;
+)
+
+#define TT this.cp
+
+// Copy an individual file or directory to target.
+
+void cp_file(char *src, char *dst, struct stat *srcst)
+{
+ int fdout = -1;
+
+ // -i flag is specified and dst file exists.
+ if ((toys.optflags&FLAG_i) && !access(dst, R_OK)
+ && !yesno("cp: overwrite", 1))
+ return;
+
+ if (toys.optflags & FLAG_v)
+ printf("'%s' -> '%s'\n", src, dst);
+
+ // Copy directory or file to destination.
+
+ if (S_ISDIR(srcst->st_mode)) {
+ struct stat st2;
+
+ // 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
+ // 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 (toys.optflags & FLAG_l) {
+ if (link(src, dst)) perror_msg("link '%s'");
+ return;
+ } 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);
+ }
+ 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;
+
+ (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.
+
+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.
+
+ n = node;
+ if (!TT.destisdir) n = n->parent;
+ for (;;n = n->parent) {
+ while (s!=path) {
+ if (*(--s)=='/') break;
+ }
+ if (!n) break;
+ }
+ 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.
+
+ return 0;
+}
+
+void cp_main(void)
+{
+ struct stat st;
+ int i;
+
+ // Grab target argument. (Guaranteed to be there due to "<2" above.)
+
+ TT.destname = toys.optargs[--toys.optc];
+
+ // If destination doesn't exist, are we ok with that?
+
+ if (stat(TT.destname, &st)) {
+ if (toys.optc>1) goto error_notdir;
+ TT.destisnew++;
+
+ // If destination exists...
+
+ } else {
+ if (S_ISDIR(st.st_mode)) TT.destisdir++;
+ else if (toys.optc > 1) goto error_notdir;
+ }
+
+ // Handle sources
+
+ for (i=0; i<toys.optc; i++) {
+ char *src = toys.optargs[i];
+ char *dst;
+
+ // Skip src==dest (TODO check inodes to catch "cp blah ./blah").
+
+ if (!strcmp(src, TT.destname)) continue;
+
+ // Skip nonexistent sources.
+
+ TT.keep_symlinks = toys.optflags & (FLAG_d|FLAG_a);
+ if (TT.keep_symlinks ? lstat(src, &st) : stat(src, &st))
+ {
+ perror_msg("'%s'", src);
+ toys.exitval = 1;
+ continue;
+ }
+
+ // Copy directory or file.
+
+ if (TT.destisdir) {
+ 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++;
+ strncpy(toybuf, src, sizeof(toybuf)-1);
+ toybuf[sizeof(toybuf)-1]=0;
+ dirtree_read(toybuf, cp_node);
+ } else error_msg("Skipped dir '%s'", src);
+ } else cp_file(src, dst, &st);
+ if (TT.destisdir) free(dst);
+ }
+
+ return;
+
+error_notdir:
+ error_exit("'%s' isn't a directory", TT.destname);
+}