[Toybox] [PATCH] cpio: Implement -u (copy unconditionally)

Yi-Yo Chiang yochiang at google.com
Sun Feb 14 05:09:41 PST 2021


If -u is specified, then replace any existing files or directories.

If -u is not specified and the path to be inflated already exist, then
report EEXIST error. This behaves slightly different from GNU cpio, as
GNU cpio checks the timestamp and replaces when the timestamp of the
existing file is older than the one from the archive. This still
conforms to SUSv2 as it doesn't define how to behave when -u is not
specified.

There is an exception, if we are creating an existing directory, then
don't report any error or try to rmdir() the directory as we can
"create" the directory by chmod() / chown() the existing one.

---
 tests/cpio.test   | 16 ++++++++++++++++
 toys/posix/cpio.c | 46 ++++++++++++++++++++++++++++++++++++----------
 2 files changed, 52 insertions(+), 10 deletions(-)

diff --git a/tests/cpio.test b/tests/cpio.test
index 6ab3665a..7e2955a1 100755
--- a/tests/cpio.test
+++ b/tests/cpio.test
@@ -42,4 +42,20 @@ touch a; chmod a-rwx a; ln -s a/cant b
 toyonly testing "archives unreadable empty files" "cpio -o -H newc|cpio -it" "b\na\n" "" "b\na\n"
 chmod u+rw a; rm -f a b
 
+mkdir a
+echo "old" >a/b
+echo "a/b" | cpio -o -H newc >a.cpio
+rm -rf a
+testing "-i doesn't create leading directories" "cpio -i <a.cpio 2>/dev/null; [ -e a ] || echo yes" "yes\n" "" ""
+rm -rf a
+testing "-id creates leading directories" "cpio -id <a.cpio && cat a/b" "old\n" "" ""
+rm -rf a a.cpio
 
+mkdir a
+echo "old" >a/b
+find a | cpio -o -H newc >a.cpio
+testing "-i keeps existing files" "echo new >a/b && cpio -i <a.cpio 2>/dev/null; cat a/b" "new\n" "" ""
+testing "-id keeps existing files" "echo new >a/b && cpio -id <a.cpio 2>/dev/null; cat a/b" "new\n" "" ""
+testing "-iu replaces existing files; no error" "echo new >a/b && cpio -iu <a.cpio && cat a/b" "old\n" "" ""
+testing "-idu replaces existing files; no error" "echo new >a/b && cpio -idu <a.cpio && cat a/b" "old\n" "" ""
+rm -rf a a.cpio
diff --git a/toys/posix/cpio.c b/toys/posix/cpio.c
index 31c777c9..795f890c 100644
--- a/toys/posix/cpio.c
+++ b/toys/posix/cpio.c
@@ -22,7 +22,7 @@ config CPIO
   default y
   help
     usage: cpio -{o|t|i|p DEST} [-v] [--verbose] [-F FILE] [--no-preserve-owner]
-           [ignored: -mdu -H newc]
+           [ignored: -m -H newc]
 
     Copy files into and out of a "newc" format cpio archive.
 
@@ -32,6 +32,7 @@ config CPIO
     -o	Create archive (stdin=list of files, stdout=archive)
     -t	Test files (list only, stdin=archive, stdout=list of files)
     -d	Create directories if needed
+    -u	Copy unconditionally
     -v	Verbose
     --no-preserve-owner (don't set ownership during extract)
 */
@@ -113,7 +114,8 @@ void cpio_main(void)
   if (FLAG(i) || FLAG(t)) for (;;) {
     char *name, *tofree, *data;
     unsigned size, mode, uid, gid, timestamp;
-    int test = FLAG(t), err = 0;
+    int test = FLAG(t), err = 0, exist = 0;
+    struct stat st;
 
     // Read header and name.
     if (!(size =readall(afd, toybuf, 110))) break;
@@ -137,25 +139,50 @@ void cpio_main(void)
     // (This output is unaffected by --quiet.)
     if (FLAG(t) || FLAG(v)) puts(name);
 
-    if (!test && FLAG(d) && strrchr(name, '/') && mkpath(name)) {
+    if (!test) exist = !lstat(name, &st);
+
+    // Create leading directories if |name| doesn't exist.
+    if (!test && !exist && FLAG(d) && strrchr(name, '/') && mkpath(name)) {
       perror_msg("mkpath '%s'", name);
       test++;
     }
 
+    // Don't report error or try to rmdir(name) if we want to mkdir(name) later.
+    if (!test && exist && !(S_ISDIR(st.st_mode) && S_ISDIR(mode))) {
+      if (!FLAG(u)) {
+        errno = EEXIST;
+        perror_msg_raw(name);
+        test++;
+      } else if (S_ISDIR(st.st_mode) ? rmdir(name) : unlink(name)) {
+        perror_msg("remove '%s'", name);
+        test++;
+      } else {
+        exist = 0;
+      }
+    }
+
     // Consume entire record even if it couldn't create file, so we're
     // properly aligned with next file.
 
     if (S_ISDIR(mode)) {
-      if (!test) err = mkdir(name, mode);
+      if (!test) {
+        // If |name| already exist as a directory, then just do a chmod() to fix
+        // up the directory permissions.
+        if (!exist) err = mkdir(name, mode);
+        else if (S_ISDIR(st.st_mode)) err = chmod(name, mode);
+        else err = errno = EEXIST;
+      }
     } else if (S_ISLNK(mode)) {
       data = strpad(afd, size, 0);
-      if (!test) err = symlink(data, name);
+      if (!test) {
+        err = symlink(data, name);
+        // Can't get a filehandle to a symlink, so do special chown
+        if (!err && !geteuid() && !FLAG(no_preserve_owner))
+          err = lchown(name, uid, gid);
+      }
       free(data);
-      // Can't get a filehandle to a symlink, so do special chown
-      if (!err && !geteuid() && !FLAG(no_preserve_owner))
-        err = lchown(name, uid, gid);
     } else if (S_ISREG(mode)) {
-      int fd = test ? 0 : open(name, O_CREAT|O_WRONLY|O_TRUNC|O_NOFOLLOW, mode);
+      int fd = test ? 0 : open(name, O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, mode);
 
       // If write fails, we still need to read/discard data to continue with
       // archive. Since doing so overwrites errno, report error now
@@ -197,7 +224,6 @@ void cpio_main(void)
           && !FLAG(no_preserve_owner))
       {
         int fd = open(name, O_RDONLY|O_NOFOLLOW);
-        struct stat st;
 
         if (fd != -1 && !fstat(fd, &st) && (st.st_mode&S_IFMT) == (mode&S_IFMT))
           err = fchown(fd, uid, gid);
-- 
2.30.0.478.g8a0d178c01-goog



More information about the Toybox mailing list