diff options
-rw-r--r-- | toys/posix/cp.c | 119 |
1 files changed, 86 insertions, 33 deletions
diff --git a/toys/posix/cp.c b/toys/posix/cp.c index 803bff85..d5e92f20 100644 --- a/toys/posix/cp.c +++ b/toys/posix/cp.c @@ -7,7 +7,7 @@ // options shared between mv/cp must be in same order (right to left) // for FLAG macros to work out right in shared infrastructure. -USE_CP(NEWTOY(cp, "<2RHLPp"USE_CP_MORE("rdaslvnF(remove-destination)")"fi[-HLP"USE_CP_MORE("d")"]"USE_CP_MORE("[-ni]"), TOYFLAG_BIN)) +USE_CP(NEWTOY(cp, "<2"USE_CP_PRESERVE("(preserve):;")"RHLPp"USE_CP_MORE("rdaslvnF(remove-destination)")"fi[-HLP"USE_CP_MORE("d")"]"USE_CP_MORE("[-ni]"), TOYFLAG_BIN)) USE_MV(NEWTOY(mv, "<2"USE_CP_MORE("vnF")"fi"USE_CP_MORE("[-ni]"), TOYFLAG_BIN)) USE_INSTALL(NEWTOY(install, "<1cdDpsvm:o:g:", TOYFLAG_USR|TOYFLAG_BIN)) @@ -44,6 +44,21 @@ config CP_MORE -s symlink instead of copy -v verbose +config CP_PRESERVE + bool "cp --preserve support" + default y + depends on CP_MORE + help + usage: cp [--preserve=mota] + + --preserve takes either a comma separated list of attributes, or the first + letter(s) of: + + mode - permissions (ignore umask for rwx, copy suid and sticky bit) + ownership - user and group + timestamps - file creation, modification, and access times. + all - all of the above + config MV bool "mv" default y @@ -87,16 +102,24 @@ config INSTALL #include "toys.h" GLOBALS( - // install's options - char *group; - char *user; - char *mode; + union { + struct { + // install's options + char *group; + char *user; + char *mode; + } i; + struct { + char *preserve; + } c; + }; char *destname; struct stat top; int (*callback)(struct dirtree *try); uid_t uid; gid_t gid; + int pflags; ) // Callback from dirtree_read() for each file/directory under a source dir. @@ -245,28 +268,27 @@ int cp_node(struct dirtree *try) if (fdin < 0) { catch = try->name; break; - } else { - fdout = openat(cfd, catch, O_RDWR|O_CREAT|O_TRUNC, try->st.st_mode); - if (fdout >= 0) { - xsendfile(fdin, fdout); - err = 0; - } - close(fdin); } + fdout = openat(cfd, catch, O_RDWR|O_CREAT|O_TRUNC, try->st.st_mode); + if (fdout >= 0) { + xsendfile(fdin, fdout); + err = 0; + } + close(fdin); } } while (err && (flags & (FLAG_f|FLAG_n)) && !unlinkat(cfd, catch, 0)); } + // Did we make a thing? if (fdout != -1) { - if (flags & (FLAG_a|FLAG_p)) { - struct timespec times[2]; - int rc; + int rc; - // Inability to set these isn't fatal, some require root access. + // Inability to set --preserve isn't fatal, some require root access. - times[0] = try->st.st_atim; - times[1] = try->st.st_mtim; + // ownership + if (TT.pflags & 2) { + // permission bits already correct for mknod and don't apply to symlink // If we can't get a filehandle to the actual object, use racy functions if (fdout == AT_FDCWD) rc = fchownat(cfd, catch, try->st.st_uid, try->st.st_gid, @@ -278,16 +300,21 @@ int cp_node(struct dirtree *try) perror_msg("chown '%s'", pp = dirtree_path(try, 0)); free(pp); } + } + + // timestamp + if (TT.pflags & 4) { + struct timespec times[] = {try->st.st_atim, try->st.st_mtim}; - // permission bits already correct for mknod and don't apply to symlink if (fdout == AT_FDCWD) utimensat(cfd, catch, times, AT_SYMLINK_NOFOLLOW); - else { - futimens(fdout, times); - fchmod(fdout, try->st.st_mode); - } + else futimens(fdout, times); } - if (fdout != AT_FDCWD) xclose(fdout); + // mode comes last because other syscalls can strip suid bit + if (fdout != AT_FDCWD) { + if (TT.pflags & 1) fchmod(fdout, try->st.st_mode); + xclose(fdout); + } if (CFG_MV && toys.which->name[0] == 'm') if (unlinkat(tfd, try->name, S_ISDIR(try->st.st_mode) ? AT_REMOVEDIR :0)) @@ -300,17 +327,41 @@ int cp_node(struct dirtree *try) void cp_main(void) { - char *destname = toys.optargs[--toys.optc]; + char *destname = toys.optargs[--toys.optc], + *preserve[] = {"mode", "ownership", "timestamps"}; int i, destdir = !stat(destname, &TT.top) && S_ISDIR(TT.top.st_mode); if (toys.optc>1 && !destdir) error_exit("'%s' not directory", destname); - if (toys.which->name[0] == 'm') toys.optflags |= FLAG_d|FLAG_p|FLAG_R; - if (toys.optflags & (FLAG_a|FLAG_p)) umask(0); + if (toys.optflags & (FLAG_a|FLAG_p)) { + TT.pflags = 7; // preserve=mot + umask(0); + } + if (CFG_CP_PRESERVE && TT.c.preserve) { + char *pre = xstrdup(TT.c.preserve), *s; + + if (comma_scan(pre, "all", 1)) TT.pflags = ~0; + for (i=0; i<ARRAY_LEN(preserve); i++) + if (comma_scan(pre, preserve[i], 1)) TT.pflags |= 1<<i; + if (*pre) { + + // Try to interpret as letters, commas won't set anything this doesn't. + for (s = TT.c.preserve; *s; s++) { + for (i=0; i<ARRAY_LEN(preserve); i++) + if (*s == *preserve[i]) TT.pflags |= 1<<i; + if (i == ARRAY_LEN(preserve)) { + if (*s == 'a') TT.pflags = ~0; + else break; + } + } + + if (*s) error_exit("bad --preserve=%s", pre); + } + free(pre); + } if (!TT.callback) TT.callback = cp_node; // Loop through sources - for (i=0; i<toys.optc; i++) { struct dirtree *new; char *src = toys.optargs[i]; @@ -351,6 +402,8 @@ void cp_main(void) void mv_main(void) { + toys.optflags |= FLAG_d|FLAG_p|FLAG_R; + cp_main(); } @@ -360,9 +413,9 @@ void mv_main(void) static int install_node(struct dirtree *try) { - if (TT.mode) try->st.st_mode = string_to_mode(TT.mode, try->st.st_mode); - if (TT.group) try->st.st_gid = TT.gid; - if (TT.user) try->st.st_uid = TT.uid; + if (TT.i.mode) try->st.st_mode = string_to_mode(TT.i.mode, try->st.st_mode); + if (TT.i.group) try->st.st_gid = TT.gid; + if (TT.i.user) try->st.st_uid = TT.uid; // Always returns 0 because no -r cp_node(try); @@ -401,8 +454,8 @@ void install_main(void) if (flags & FLAG_v) toys.optflags |= 8; // cp's FLAG_v if (flags & (FLAG_p|FLAG_o|FLAG_g)) toys.optflags |= 512; // cp's FLAG_p - if (TT.user) TT.uid = xgetpwnamid(TT.user)->pw_uid; - if (TT.group) TT.gid = xgetgrnamid(TT.group)->gr_gid; + if (TT.i.user) TT.uid = xgetpwnamid(TT.i.user)->pw_uid; + if (TT.i.group) TT.gid = xgetgrnamid(TT.i.group)->gr_gid; TT.callback = install_node; cp_main(); |