From 8fdd58a02257baf8f2bca1c086571ea7b7e17365 Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Sun, 8 Apr 2018 22:37:33 -0500 Subject: Add cp --parents --- lib/lib.c | 23 +++++++++++++++++++++++ lib/lib.h | 2 ++ toys/posix/cp.c | 20 ++++++++++++++++---- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/lib/lib.c b/lib/lib.c index 27d14be7..8c46678f 100644 --- a/lib/lib.c +++ b/lib/lib.c @@ -1002,6 +1002,17 @@ void mode_to_string(mode_t mode, char *buf) *buf = c; } +// dirname() can modify its argument or return a pointer to a constant string +// This always returns a malloc() copy of everyting before last (run of ) '/'. +char *getdirname(char *name) +{ + char *s = xstrdup(name), *ss = strrchr(s, '/'); + + while (*ss && *ss == '/' && s != ss) *ss-- = 0; + + return s; +} + // basename() can modify its argument or return a pointer to a constant string // This just gives after the last '/' or the whole stirng if no / char *getbasename(char *name) @@ -1013,6 +1024,18 @@ char *getbasename(char *name) return name; } +// Is this file under this directory? +int fileunderdir(char *file, char *dir) +{ + char *s1 = xabspath(dir, 1), *s2 = xabspath(file, -1), *ss = s2; + int rc = s1 && s2 && strstart(&ss, s1) && (!s1[1] || s2[strlen(s1)] == '/'); + + free(s1); + free(s2); + + return rc; +} + // Execute a callback for each PID that matches a process name from a list. void names_to_pid(char **names, int (*callback)(pid_t pid, char *name)) { diff --git a/lib/lib.h b/lib/lib.h index ccf342cc..353e262d 100644 --- a/lib/lib.h +++ b/lib/lib.h @@ -337,7 +337,9 @@ char *num_to_sig(int sig); mode_t string_to_mode(char *mode_str, mode_t base); void mode_to_string(mode_t mode, char *buf); +char *getdirname(char *name); char *getbasename(char *name); +int fileunderdir(char *file, char *dir); void names_to_pid(char **names, int (*callback)(pid_t pid, char *name)); pid_t __attribute__((returns_twice)) xvforkwrap(pid_t pid); diff --git a/toys/posix/cp.c b/toys/posix/cp.c index d4475a02..f0522af9 100644 --- a/toys/posix/cp.c +++ b/toys/posix/cp.c @@ -15,7 +15,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, "<2"USE_CP_PRESERVE("(preserve):;")"RHLPprdaslvnF(remove-destination)fi[-HLPd][-ni]", TOYFLAG_BIN)) +USE_CP(NEWTOY(cp, "<2"USE_CP_PRESERVE("(preserve):;")"D(parents)RHLPprdaslvnF(remove-destination)fi[-HLPd][-ni]", TOYFLAG_BIN)) USE_MV(NEWTOY(mv, "<2vnF(remove-destination)fi[-ni]", TOYFLAG_BIN)) USE_INSTALL(NEWTOY(install, "<1cdDpsvm:o:g:", TOYFLAG_USR|TOYFLAG_BIN)) @@ -28,6 +28,7 @@ config CP Copy files from SOURCE to DEST. If more than one SOURCE, DEST must be a directory. + -D create leading dirs under DEST (--parents) -f delete destination files we can't write to -F delete any existing destination file first (--remove-destination) -i interactive, prompt before overwriting existing DEST @@ -366,7 +367,8 @@ void cp_main(void) char *destname = toys.optargs[--toys.optc]; 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.optc>1 || (toys.optflags&FLAG_D)) && !destdir) + error_exit("'%s' not directory", destname); if (toys.optflags & (FLAG_a|FLAG_p)) { TT.pflags = CP_mode|CP_ownership|CP_timestamps; @@ -402,8 +404,18 @@ void cp_main(void) char *src = toys.optargs[i]; int rc = 1; - if (destdir) TT.destname = xmprintf("%s/%s", destname, basename(src)); - else TT.destname = destname; + if (destdir) { + char *s = (toys.optflags&FLAG_D) ? getdirname(src) : getbasename(src); + + TT.destname = xmprintf("%s/%s", destname, s); + if (toys.optflags&FLAG_D) { + free(s); + if (!fileunderdir(TT.destname, destname)) { + error_msg("%s not under %s", TT.destname, destname); + continue; + } else mkpath(TT.destname); + } + } else TT.destname = destname; errno = EXDEV; if (CFG_MV && toys.which->name[0] == 'm') { -- cgit v1.2.3