| /* Copyright 2008 Rob Landley <rob@landley.net> |
| * |
| * See http://opengroup.org/onlinepubs/9699919799/utilities/cp.html |
| * |
| * TODO: "R+ra+d+p+r" sHLPR |
| |
| USE_CP(NEWTOY(cp, "<2"USE_CP_MORE("rdavsl")"RHLPfip", TOYFLAG_BIN)) |
| |
| config CP |
| bool "cp (broken by dirtree changes)" |
| default n |
| help |
| usage: cp [-fipRHLP] 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) |
| -H Follow symlinks listed on command line |
| -L Follow all symlinks |
| -P Do not follow symlinks [default] |
| |
| config CP_MORE |
| bool "cp -rdavsl options" |
| default y |
| depends on CP |
| help |
| usage: cp [-rdavsl] |
| |
| -r synonym for -R |
| -d don't dereference symlinks |
| -a same as -dpr |
| -l hard link instead of copy |
| -s symlink instead of copy |
| -v verbose |
| */ |
| |
| #define FOR_cp |
| #include "toys.h" |
| |
| // TODO: PLHlsd |
| |
| GLOBALS( |
| char *destname; |
| int destisdir; |
| int keep_symlinks; |
| ) |
| |
| // 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) |
| { |
| char *dpath = NULL; |
| struct stat st, std; |
| int i; |
| |
| // 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); |
| |
| // 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; |
| |
| // 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: |
| perror_msg("bad '%s'", src); |
| toys.exitval = 1; |
| continue; |
| } |
| |
| // 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; |
| } |