lib/checkout: add filter API to skip over files

This is analogous to the filtering support for the commit API: we allow
library users to skip over checking out specific files. This is useful
in some tricky situations where we *know* that the files to be checked
out will conflict with existing files in subtle ways.

One such example is in rpm-ostree support for multilib. There, we want
to allow checking out a package onto an existing tree, but skipping over
files that are not coloured to our preferred value (e.g. not overwriting
an i686 version of `ldconfig` if we already have the `x86_64` version).
See https://github.com/projectatomic/rpm-ostree/pull/1227 for details.

Closes: #1441
Approved by: cgwalters
This commit is contained in:
Jonathan Lebon authored and Atomic Bot committed 2018-02-06 15:38:20 +00:00
1 parent 6bed647648
commit 2e95e06616
7 files changed
+226 -31

No files matched your search

+94 -26
View File
@@ -38,13 +38,17 @@
/* Per-checkout call state/caching */
typedef struct {
GString *selabel_path_buf;
GString *path_buf; /* buffer for real path if filtering enabled */
GString *selabel_path_buf; /* buffer for selinux path if labeling enabled; this may be
the same buffer as path_buf */
} CheckoutState;
static void
checkout_state_clear (CheckoutState *state)
{
if (state->selabel_path_buf)
if (state->path_buf)
g_string_free (state->path_buf, TRUE);
if (state->selabel_path_buf && (state->selabel_path_buf != state->path_buf))
g_string_free (state->selabel_path_buf, TRUE);
}
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(CheckoutState, checkout_state_clear)
@@ -529,7 +533,7 @@ checkout_file_hardlink (OstreeRepo *self,
static gboolean
checkout_one_file_at (OstreeRepo *repo,
OstreeRepoCheckoutAtOptions *options,
OstreeRepoCheckoutAtOptions *options,
CheckoutState *state,
const char *checksum,
int destination_dfd,
@@ -545,12 +549,24 @@ checkout_one_file_at (OstreeRepo *repo,
gboolean is_bare_user_symlink = FALSE;
char loose_path_buf[_OSTREE_LOOSE_PATH_MAX];
/* FIXME - avoid the GFileInfo here */
g_autoptr(GFileInfo) source_info = NULL;
if (!ostree_repo_load_file (repo, checksum, NULL, &source_info, NULL,
cancellable, error))
return FALSE;
if (options->filter)
{
/* use struct stat for when we can get rid of GFileInfo; though for now, we end up
* packing and unpacking in the non-archive case; blehh */
struct stat stbuf = {0,};
_ostree_gfileinfo_to_stbuf (source_info, &stbuf);
if (options->filter (repo, state->path_buf->str, &stbuf, options->filter_user_data) ==
OSTREE_REPO_CHECKOUT_FILTER_SKIP)
return TRUE; /* Note early return */
}
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));
@@ -750,6 +766,41 @@ checkout_one_file_at (OstreeRepo *repo,
return TRUE;
}
static inline void
push_path_element_once (GString *buf,
const char *name,
gboolean is_dir)
{
g_string_append (buf, name);
if (is_dir)
g_string_append_c (buf, '/');
}
static inline void
push_path_element (OstreeRepoCheckoutAtOptions *options,
CheckoutState *state,
const char *name,
gboolean is_dir)
{
if (state->path_buf)
push_path_element_once (state->path_buf, name, is_dir);
if (state->selabel_path_buf && (state->selabel_path_buf != state->path_buf))
push_path_element_once (state->selabel_path_buf, name, is_dir);
}
static inline void
pop_path_element (OstreeRepoCheckoutAtOptions *options,
CheckoutState *state,
const char *name,
gboolean is_dir)
{
const size_t n = strlen (name) + (is_dir ? 1 : 0);
if (state->path_buf)
g_string_truncate (state->path_buf, state->path_buf->len - n);
if (state->selabel_path_buf && (state->selabel_path_buf != state->path_buf))
g_string_truncate (state->selabel_path_buf, state->selabel_path_buf->len - n);
}
/*
* checkout_tree_at:
* @self: Repo
@@ -800,6 +851,17 @@ checkout_tree_at_recurse (OstreeRepo *self,
gid = GUINT32_FROM_BE (gid);
mode = GUINT32_FROM_BE (mode);
if (options->filter)
{
struct stat stbuf = { 0, };
stbuf.st_mode = mode;
stbuf.st_uid = uid;
stbuf.st_gid = gid;
if (options->filter (self, state->path_buf->str, &stbuf, options->filter_user_data)
== OSTREE_REPO_CHECKOUT_FILTER_SKIP)
return TRUE; /* Note early return */
}
/* First, make the directory. Push a new scope in case we end up using
* setfscreatecon().
*/
@@ -865,7 +927,6 @@ checkout_tree_at_recurse (OstreeRepo *self,
return FALSE;
}
GString *selabel_path_buf = state->selabel_path_buf;
/* Process files in this subdir */
{ g_autoptr(GVariant) dir_file_contents = g_variant_get_child_value (dirtree, 0);
GVariantIter viter;
@@ -874,9 +935,7 @@ checkout_tree_at_recurse (OstreeRepo *self,
g_autoptr(GVariant) contents_csum_v = NULL;
while (g_variant_iter_loop (&viter, "(&s@ay)", &fname, &contents_csum_v))
{
const size_t origlen = selabel_path_buf ? selabel_path_buf->len : 0;
if (selabel_path_buf)
g_string_append (selabel_path_buf, fname);
push_path_element (options, state, fname, FALSE);
char tmp_checksum[OSTREE_SHA256_STRING_LEN+1];
_ostree_checksum_inplace_from_bytes_v (contents_csum_v, tmp_checksum);
@@ -887,8 +946,7 @@ checkout_tree_at_recurse (OstreeRepo *self,
cancellable, error))
return FALSE;
if (selabel_path_buf)
g_string_truncate (selabel_path_buf, origlen);
pop_path_element (options, state, fname, FALSE);
}
contents_csum_v = NULL; /* iter_loop freed it */
}
@@ -912,12 +970,7 @@ checkout_tree_at_recurse (OstreeRepo *self,
if (!ot_util_filename_validate (dname, error))
return FALSE;
const size_t origlen = selabel_path_buf ? selabel_path_buf->len : 0;
if (selabel_path_buf)
{
g_string_append (selabel_path_buf, dname);
g_string_append_c (selabel_path_buf, '/');
}
push_path_element (options, state, dname, TRUE);
char subdirtree_checksum[OSTREE_SHA256_STRING_LEN+1];
_ostree_checksum_inplace_from_bytes_v (subdirtree_csum_v, subdirtree_checksum);
@@ -929,8 +982,7 @@ checkout_tree_at_recurse (OstreeRepo *self,
cancellable, error))
return FALSE;
if (selabel_path_buf)
g_string_truncate (selabel_path_buf, origlen);
pop_path_element (options, state, dname, TRUE);
}
}
@@ -992,18 +1044,31 @@ checkout_tree_at (OstreeRepo *self,
GError **error)
{
g_auto(CheckoutState) state = { 0, };
// If SELinux labeling is enabled, we need to keep track of the full path string
if (options->filter)
state.path_buf = g_string_new ("/");
/* If SELinux labeling is enabled, we need to keep track of the full path string */
if (options->sepolicy)
{
GString *buf = g_string_new (options->sepolicy_prefix ?: options->subpath);
g_assert_cmpint (buf->len, >, 0);
// Ensure it ends with /
if (buf->str[buf->len-1] != '/')
g_string_append_c (buf, '/');
state.selabel_path_buf = buf;
/* Otherwise it'd just be corrupting things, and there's no use case */
g_assert (options->force_copy);
const char *prefix = options->sepolicy_prefix ?: options->subpath;
if (g_str_equal (prefix, "/") && state.path_buf)
{
/* just use the same scratchpad if we can */
state.selabel_path_buf = state.path_buf;
}
else
{
GString *buf = g_string_new (prefix);
g_assert_cmpint (buf->len, >, 0);
/* Ensure it ends with / */
if (buf->str[buf->len-1] != '/')
g_string_append_c (buf, '/');
state.selabel_path_buf = buf;
}
}
/* Special case handling for subpath of a non-directory */
@@ -1017,7 +1082,7 @@ checkout_tree_at (OstreeRepo *self,
*/
int destination_dfd = destination_parent_fd;
glnx_autofd int destination_dfd_owned = -1;
if (strcmp (destination_name, ".") != 0)
if (!g_str_equal (destination_name, "."))
{
if (mkdirat (destination_parent_fd, destination_name, 0700) < 0
&& errno != EEXIST)
@@ -1027,6 +1092,9 @@ checkout_tree_at (OstreeRepo *self,
return FALSE;
destination_dfd = destination_dfd_owned;
}
/* let's just ignore filter here; I can't think of a useful case for filtering when
* only checking out one path */
options->filter = NULL;
return checkout_one_file_at (self, options, &state,
ostree_repo_file_get_checksum (source),
destination_dfd,