lib/checkout: For "process whiteouts" mode, replace directories too
authorColin Walters <walters@verbum.org>
Thu, 19 Oct 2017 15:18:00 +0000 (11:18 -0400)
committerAtomic Bot <atomic-devel@projectatomic.io>
Fri, 20 Oct 2017 13:20:27 +0000 (13:20 +0000)
I'm playing around with some ostree ⇔ OCI/Docker bits, and ran
into this while importing an OCI image that built from the Fedora
base image where `/home` is a regular directory, and I added a layer
that did the ostree bits of moving it to `/var` and leaving a symlink.

OCI/Docker supports this.  Now since "process whiteouts" is really the
"enable OCI/Docker" mode, let's only replace dirs if that's enabled.
This leaves the `UNION_FILES` targeted for its original use case
which is unioning components/packages.  (Although that use case itself
is now a bit superceded by `UNION_IDENTICAL`, but eh).

Closes: #1294
Approved by: jlebon

src/libostree/ostree-repo-checkout.c
src/libostree/ostree-repo.h
tests/basic-test.sh

index d144d03e6f7017967f93476996675d366b2e1068..c2639a223021eb84b0e7f348abaf3910e1f070ca 100644 (file)
@@ -228,8 +228,24 @@ create_file_copy_from_input_at (OstreeRepo     *repo,
               return glnx_throw_errno_prefix (error, "symlinkat");
             case OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES:
               {
-                /* Unioning?  Let's unlink and try again */
-                (void) unlinkat (destination_dfd, destination_name, 0);
+                /* For unioning, we further bifurcate a bit; for the "process whiteouts"
+                 * mode which is really "Docker/OCI", we need to match their semantics
+                 * and handle replacing a directory with a symlink.  See also equivalent
+                 * bits for regular files in checkout_file_hardlink().
+                 */
+                if (options->process_whiteouts)
+                  {
+                    if (!glnx_shutil_rm_rf_at (destination_dfd, destination_name, NULL, error))
+                      return FALSE;
+                  }
+                else
+                  {
+                    if (unlinkat (destination_dfd, destination_name, 0) < 0)
+                      {
+                        if (G_UNLIKELY (errno != ENOENT))
+                          return glnx_throw_errno_prefix (error, "unlinkat(%s)", destination_name);
+                      }
+                  }
                 if (symlinkat (target, destination_dfd, destination_name) < 0)
                   return glnx_throw_errno_prefix (error, "symlinkat");
               }
@@ -309,7 +325,17 @@ create_file_copy_from_input_at (OstreeRepo     *repo,
           /* Handled above */
           break;
         case OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES:
-          replace_mode = GLNX_LINK_TMPFILE_REPLACE;
+          /* Special case OCI/Docker - see similar code in checkout_file_hardlink()
+           * and above for symlinks.
+           */
+          if (options->process_whiteouts)
+            {
+              if (!glnx_shutil_rm_rf_at (destination_dfd, destination_name, NULL, error))
+                return FALSE;
+              /* Inherit the NOREPLACE default...we deleted whatever's there */
+            }
+          else
+            replace_mode = GLNX_LINK_TMPFILE_REPLACE;
           break;
         case OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES:
           replace_mode = GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST;
@@ -467,7 +493,15 @@ checkout_file_hardlink (OstreeRepo                          *self,
                 /* Make a link with a temp name */
                 if (!hardlink_add_tmp_name (self, srcfd, loose_path, tmpname, cancellable, error))
                   return FALSE;
-                /* Rename it into place */
+                /* For OCI/Docker mode, we need to handle replacing a directory with a regular
+                 * file.  See also the equivalent code for symlinks above.
+                 */
+                if (options->process_whiteouts)
+                  {
+                    if (!glnx_shutil_rm_rf_at (destination_dfd, destination_name, NULL, error))
+                      return FALSE;
+                  }
+                /* Rename it into place - for non-OCI this will overwrite files but not directories */
                 if (!glnx_renameat (self->tmp_dir_fd, tmpname, destination_dfd, destination_name, error))
                   return FALSE;
                 ret_result = HARDLINK_RESULT_LINKED;
index 4f73a0513c0c1f5edb5989f21161ce79a684a70d..e1daf08d4ea8c53beb908a5ea3a2ae98499885f3 100644 (file)
@@ -848,7 +848,7 @@ typedef enum {
 /**
  * OstreeRepoCheckoutOverwriteMode:
  * @OSTREE_REPO_CHECKOUT_OVERWRITE_NONE: No special options
- * @OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES: When layering checkouts, unlink() and replace existing files, but do not modify existing directories
+ * @OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES: When layering checkouts, unlink() and replace existing files, but do not modify existing directories (unless whiteouts are enabled, then directories are replaced)
  * @OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES: Only add new files/directories
  * @OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_IDENTICAL: Like UNION_FILES, but error if files are not identical (requires hardlink checkouts)
  */
index decaf603098d4d80ff29d506bec0da56d7acb184..d7c5425cf0c1db25cf00f6671c3cad496bd20408 100644 (file)
@@ -879,6 +879,20 @@ if touch overlay/baz/.wh.cow && touch overlay/.wh.deeper; then
     assert_has_file overlay-co/anewdir/blah
     assert_has_file overlay-co/anewfile
 
+    # And test replacing a directory wholesale with a symlink as well as a regular file
+    mkdir overlay
+    echo baz to file > overlay/baz
+    ln -s anewfile overlay/anewdir
+    $OSTREE --repo=repo commit ${COMMIT_ARGS} -b overlay-dir-convert --tree=dir=overlay
+    rm overlay -rf
+
+    rm overlay-co -rf
+    for branch in test2 overlay-dir-convert; do
+        $OSTREE --repo=repo checkout --union --whiteouts ${branch} overlay-co
+    done
+    assert_has_file overlay-co/baz
+    test -L overlay-co/anewdir
+
     echo "ok whiteouts enabled"
 
     # Now double check whiteouts are not processed without --whiteouts