repo: Add a checkout option to not hardlink zero-sized files

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
This commit is contained in:
Colin Walters 2018-10-11 09:22:16 -04:00 committed by Atomic Bot
parent c70526841e
commit 673cacd633
4 changed files with 30 additions and 4 deletions

View 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_symlink = (g_file_info_get_file_type (source_info) == G_FILE_TYPE_SYMBOLIC_LINK);
const gboolean is_whiteout = (!is_symlink && options->process_whiteouts && const gboolean is_whiteout = (!is_symlink && options->process_whiteouts &&
g_str_has_prefix (destination_name, WHITEOUT_PREFIX)); 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, /* First, see if it's a Docker whiteout,
* https://github.com/docker/docker/blob/1a714e76a2cb9008cd19609059e9988ff1660b78/pkg/archive/whiteouts.go * https://github.com/docker/docker/blob/1a714e76a2cb9008cd19609059e9988ff1660b78/pkg/archive/whiteouts.go
@ -604,6 +605,10 @@ checkout_one_file_at (OstreeRepo *repo,
need_copy = FALSE; need_copy = FALSE;
} }
else if (options->force_copy_zerosized && is_reg_zerosized)
{
need_copy = TRUE;
}
else if (!options->force_copy) else if (!options->force_copy)
{ {
HardlinkResult hardlink_res = HARDLINK_RESULT_NOT_SUPPORTED; HardlinkResult hardlink_res = HARDLINK_RESULT_NOT_SUPPORTED;
@ -699,6 +704,7 @@ checkout_one_file_at (OstreeRepo *repo,
if (can_cache if (can_cache
&& !is_whiteout && !is_whiteout
&& !is_symlink && !is_symlink
&& !is_reg_zerosized
&& need_copy && need_copy
&& repo->mode == OSTREE_REPO_MODE_ARCHIVE && repo->mode == OSTREE_REPO_MODE_ARCHIVE
&& options->mode == OSTREE_REPO_CHECKOUT_MODE_USER) && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER)
@ -762,7 +768,7 @@ checkout_one_file_at (OstreeRepo *repo,
* succeeded at hardlinking above. * succeeded at hardlinking above.
*/ */
if (options->no_copy_fallback) 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, if (!ostree_repo_load_file (repo, checksum, &input, NULL, &xattrs,
cancellable, error)) cancellable, error))
return FALSE; return FALSE;

View File

@ -933,7 +933,8 @@ typedef struct {
gboolean no_copy_fallback; gboolean no_copy_fallback;
gboolean force_copy; /* Since: 2017.6 */ gboolean force_copy; /* Since: 2017.6 */
gboolean bareuseronly_dirs; /* Since: 2017.7 */ 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 */ /* 4 byte hole on 64 bit */
const char *subpath; const char *subpath;

View File

@ -45,6 +45,7 @@ static char *opt_from_file;
static gboolean opt_disable_fsync; static gboolean opt_disable_fsync;
static gboolean opt_require_hardlinks; static gboolean opt_require_hardlinks;
static gboolean opt_force_copy; static gboolean opt_force_copy;
static gboolean opt_force_copy_zerosized;
static gboolean opt_bareuseronly_dirs; static gboolean opt_bareuseronly_dirs;
static char *opt_skiplist_file; static char *opt_skiplist_file;
static char *opt_selinux_policy; 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" }, { "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" }, { "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 }, { "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 }, { "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 }, { "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" }, { "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. * convenient infrastructure for testing C APIs with data.
*/ */
if (opt_disable_cache || opt_whiteouts || opt_require_hardlinks || 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) opt_skiplist_file || opt_selinux_policy || opt_selinux_prefix)
{ {
OstreeRepoCheckoutAtOptions options = { 0, }; OstreeRepoCheckoutAtOptions options = { 0, };
@ -218,6 +221,7 @@ process_one_checkout (OstreeRepo *repo,
options.no_copy_fallback = opt_require_hardlinks; options.no_copy_fallback = opt_require_hardlinks;
options.force_copy = opt_force_copy; options.force_copy = opt_force_copy;
options.force_copy_zerosized = opt_force_copy_zerosized;
options.bareuseronly_dirs = opt_bareuseronly_dirs; options.bareuseronly_dirs = opt_bareuseronly_dirs;
if (!ostree_repo_checkout_at (repo, &options, if (!ostree_repo_checkout_at (repo, &options,

View File

@ -21,7 +21,7 @@
set -euo pipefail set -euo pipefail
echo "1..$((83 + ${extra_basic_tests:-0}))" echo "1..$((84 + ${extra_basic_tests:-0}))"
CHECKOUT_U_ARG="" CHECKOUT_U_ARG=""
CHECKOUT_H_ARGS="-H" CHECKOUT_H_ARGS="-H"
@ -694,6 +694,21 @@ for v in bin link; do
done done
echo "ok checkout union identical conflicts" 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} cd ${test_tmpdir}
rm files -rf && mkdir files rm files -rf && mkdir files
mkdir files/worldwritable-dir mkdir files/worldwritable-dir