diff options
Diffstat (limited to 'toys/posix/cp.c')
-rw-r--r-- | toys/posix/cp.c | 224 |
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); +} |