repo: Add a checkout option to not hardlink zero-sized files
authorColin Walters <walters@verbum.org>
Thu, 11 Oct 2018 13:22:16 +0000 (09:22 -0400)
committerAtomic Bot <atomic-devel@projectatomic.io>
Thu, 11 Oct 2018 16:32:25 +0000 (16:32 +0000)
In rpm-ostree we've hit a few cases where hardlinking zero-sized
files causes us problems.  The most prominent is lock files in
`/usr/etc`, such as `/usr/etc/selinux/semanage.LOCK`.  If there
are two zero-sized lock files to grab, but they're hardlinked,
then locking will fail.

Another case here is if one is using ostree inside a container
and don't have access to FUSE (i.e. `rofiles-fuse`), then the
ostree hardlinking can cause files that aren't ordinarily hardlinked
to become so, and mutation of one mutates all.  An example where
this is concerning is Python `__init__.py` files.

Now, these lock files should clearly not be in the tree to begin
with, but - we're not gaining a huge amount by hardlinking these
files either, so let's add an option to disable it.

Closes: #1752
Approved by: jlebon

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

index 6317c9e85ecc85617fa3825ae02694a3bd5e6558..fcae6a7f77feea5114f9db0be48cb798ccc0051a 100644 (file)
@@ -586,6 +586,7 @@ checkout_one_file_at (OstreeRepo                        *repo,
   const gboolean is_symlink = (g_file_info_get_file_type (source_info) == G_FILE_TYPE_SYMBOLIC_LINK);
   const gboolean is_whiteout = (!is_symlink && options->process_whiteouts &&
                                 g_str_has_prefix (destination_name, WHITEOUT_PREFIX));
+  const gboolean is_reg_zerosized = (!is_symlink && g_file_info_get_size (source_info) == 0);
 
   /* First, see if it's a Docker whiteout,
    * https://github.com/docker/docker/blob/1a714e76a2cb9008cd19609059e9988ff1660b78/pkg/archive/whiteouts.go
@@ -604,6 +605,10 @@ checkout_one_file_at (OstreeRepo                        *repo,
 
       need_copy = FALSE;
     }
+  else if (options->force_copy_zerosized && is_reg_zerosized)
+    {
+      need_copy = TRUE;
+    }
   else if (!options->force_copy)
     {
       HardlinkResult hardlink_res = HARDLINK_RESULT_NOT_SUPPORTED;
@@ -699,6 +704,7 @@ checkout_one_file_at (OstreeRepo                        *repo,
   if (can_cache
       && !is_whiteout
       && !is_symlink
+      && !is_reg_zerosized
       && need_copy
       && repo->mode == OSTREE_REPO_MODE_ARCHIVE
       && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER)
@@ -762,7 +768,7 @@ checkout_one_file_at (OstreeRepo                        *repo,
        * succeeded at hardlinking above.
        */
       if (options->no_copy_fallback)
-        g_assert (is_bare_user_symlink);
+        g_assert (is_bare_user_symlink || is_reg_zerosized);
       if (!ostree_repo_load_file (repo, checksum, &input, NULL, &xattrs,
                                   cancellable, error))
         return FALSE;
index a65447615e54e259053052e8c571378840066e24..eddbbf87012d2df743e95be76d206e988345ba91 100644 (file)
@@ -933,7 +933,8 @@ typedef struct {
   gboolean no_copy_fallback;
   gboolean force_copy; /* Since: 2017.6 */
   gboolean bareuseronly_dirs; /* Since: 2017.7 */
-  gboolean unused_bools[5];
+  gboolean force_copy_zerosized; /* Since: 2018.9 */
+  gboolean unused_bools[4];
   /* 4 byte hole on 64 bit */
 
   const char *subpath;
index e7d6a6346451bf4498026370c76af9c62e79a09b..1519e34efce0a8a36d3c59ea334763fd9d9affc4 100644 (file)
@@ -45,6 +45,7 @@ static char *opt_from_file;
 static gboolean opt_disable_fsync;
 static gboolean opt_require_hardlinks;
 static gboolean opt_force_copy;
+static gboolean opt_force_copy_zerosized;
 static gboolean opt_bareuseronly_dirs;
 static char *opt_skiplist_file;
 static char *opt_selinux_policy;
@@ -84,6 +85,7 @@ static GOptionEntry options[] = {
   { "from-file", 0, 0, G_OPTION_ARG_STRING, &opt_from_file, "Process many checkouts from input file", "FILE" },
   { "fsync", 0, 0, G_OPTION_ARG_CALLBACK, parse_fsync_cb, "Specify how to invoke fsync()", "POLICY" },
   { "require-hardlinks", 'H', 0, G_OPTION_ARG_NONE, &opt_require_hardlinks, "Do not fall back to full copies if hardlinking fails", NULL },
+  { "force-copy-zerosized", 'z', 0, G_OPTION_ARG_NONE, &opt_force_copy_zerosized, "Do not hardlink zero-sized files", NULL },
   { "force-copy", 'C', 0, G_OPTION_ARG_NONE, &opt_force_copy, "Never hardlink (but may reflink if available)", NULL },
   { "bareuseronly-dirs", 'M', 0, G_OPTION_ARG_NONE, &opt_bareuseronly_dirs, "Suppress mode bits outside of 0775 for directories (suid, world writable, etc.)", NULL },
   { "skip-list", 0, 0, G_OPTION_ARG_FILENAME, &opt_skiplist_file, "File containing list of files to skip", "PATH" },
@@ -130,7 +132,8 @@ process_one_checkout (OstreeRepo           *repo,
    * convenient infrastructure for testing C APIs with data.
    */
   if (opt_disable_cache || opt_whiteouts || opt_require_hardlinks ||
-      opt_union_add || opt_force_copy || opt_bareuseronly_dirs || opt_union_identical ||
+      opt_union_add || opt_force_copy || opt_force_copy_zerosized ||
+      opt_bareuseronly_dirs || opt_union_identical ||
       opt_skiplist_file || opt_selinux_policy || opt_selinux_prefix)
     {
       OstreeRepoCheckoutAtOptions options = { 0, };
@@ -218,6 +221,7 @@ process_one_checkout (OstreeRepo           *repo,
 
       options.no_copy_fallback = opt_require_hardlinks;
       options.force_copy = opt_force_copy;
+      options.force_copy_zerosized = opt_force_copy_zerosized;
       options.bareuseronly_dirs = opt_bareuseronly_dirs;
 
       if (!ostree_repo_checkout_at (repo, &options,
index a0c2f1f763a1d4f866709588b0b56b269e947b09..a817b9d152864eb8f739a59d96c14e3054190a97 100644 (file)
@@ -21,7 +21,7 @@
 
 set -euo pipefail
 
-echo "1..$((83 + ${extra_basic_tests:-0}))"
+echo "1..$((84 + ${extra_basic_tests:-0}))"
 
 CHECKOUT_U_ARG=""
 CHECKOUT_H_ARGS="-H"
@@ -694,6 +694,21 @@ for v in bin link; do
 done
 echo "ok checkout union identical conflicts"
 
+cd ${test_tmpdir}
+rm files -rf && mkdir files
+touch files/anemptyfile
+touch files/anotheremptyfile
+$CMD_PREFIX ostree --repo=repo commit --consume -b tree-with-empty-files --tree=dir=files
+$CMD_PREFIX ostree --repo=repo checkout ${CHECKOUT_H_ARGS} -z tree-with-empty-files tree-with-empty-files
+if files_are_hardlinked tree-with-empty-files/an{,other}emptyfile; then
+    fatal "--force-copy-zerosized failed"
+fi
+rm tree-with-empty-files -rf
+$CMD_PREFIX ostree --repo=repo checkout ${CHECKOUT_H_ARGS} tree-with-empty-files tree-with-empty-files
+assert_files_hardlinked tree-with-empty-files/an{,other}emptyfile
+rm tree-with-empty-files -rf
+echo "ok checkout --force-copy-zerosized"
+
 cd ${test_tmpdir}
 rm files -rf && mkdir files
 mkdir files/worldwritable-dir