Skip to content

Commit

Permalink
lib/checkout: support filtering
Browse files Browse the repository at this point in the history
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 coreos/rpm-ostree#1227 for details.
  • Loading branch information
jlebon committed Feb 1, 2018
1 parent aad9bf7 commit 654add0
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 31 deletions.
1 change: 1 addition & 0 deletions src/libostree/ostree-core-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ _ostree_make_temporary_symlink_at (int tmp_dirfd,
GError **error);

GFileInfo * _ostree_stbuf_to_gfileinfo (const struct stat *stbuf);
void _ostree_gfileinfo_to_stbuf (GFileInfo *file_info, struct stat *out_stbuf);
gboolean _ostree_gfileinfo_equal (GFileInfo *a, GFileInfo *b);
gboolean _ostree_stbuf_equal (struct stat *stbuf_a, struct stat *stbuf_b);
GFileInfo * _ostree_mode_uidgid_to_gfileinfo (mode_t mode, uid_t uid, gid_t gid);
Expand Down
20 changes: 20 additions & 0 deletions src/libostree/ostree-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -1686,6 +1686,26 @@ _ostree_stbuf_to_gfileinfo (const struct stat *stbuf)
return ret;
}

/**
* _ostree_gfileinfo_to_stbuf:
* @file_info: File info
* @out_stbuf: (out): stat buffer
*
* Map GFileInfo data from @file_info onto @out_stbuf.
*/
void
_ostree_gfileinfo_to_stbuf (GFileInfo *file_info,
struct stat *out_stbuf)
{
struct stat stbuf = {0,};
stbuf.st_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode");
stbuf.st_uid = g_file_info_get_attribute_uint32 (file_info, "unix::uid");
stbuf.st_gid = g_file_info_get_attribute_uint32 (file_info, "unix::gid");
if (S_ISREG (stbuf.st_mode))
stbuf.st_size = g_file_info_get_attribute_uint64 (file_info, "standard::size");
*out_stbuf = stbuf;
}

/**
* _ostree_gfileinfo_equal:
* @a: First file info
Expand Down
128 changes: 102 additions & 26 deletions src/libostree/ostree-repo-checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
gboolean shared_path_buf; /* we try to use the same buf if we can */
} 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->shared_path_buf)
g_string_free (state->selabel_path_buf, TRUE);
}
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(CheckoutState, checkout_state_clear)
Expand Down Expand Up @@ -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,
Expand All @@ -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));
Expand Down Expand Up @@ -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->shared_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->shared_path_buf)
g_string_truncate (state->selabel_path_buf, state->selabel_path_buf->len - n);
}

/*
* checkout_tree_at:
* @self: Repo
Expand Down Expand Up @@ -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().
*/
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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 */
}
Expand All @@ -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);
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -980,6 +1032,17 @@ checkout_tree_at_recurse (OstreeRepo *self,
return TRUE;
}

static GString*
new_canonicalized_path_buf (const char *path)
{
GString *buf = g_string_new (path);
g_assert_cmpint (buf->len, >, 0);
/* Ensure it ends with / */
if (buf->str[buf->len-1] != '/')
g_string_append_c (buf, '/');
return buf;
}

/* Begin a checkout process */
static gboolean
checkout_tree_at (OstreeRepo *self,
Expand All @@ -992,18 +1055,28 @@ 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 = new_canonicalized_path_buf (options->subpath);

/* 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);

if (options->sepolicy_prefix || !state.path_buf)
{
state.selabel_path_buf =
new_canonicalized_path_buf (options->sepolicy_prefix ?: options->subpath);
state.shared_path_buf = FALSE;
}
else
{
/* just use the same scratchpad if we can */
state.selabel_path_buf = state.path_buf;
state.shared_path_buf = TRUE;
}
}

/* Special case handling for subpath of a non-directory */
Expand All @@ -1017,7 +1090,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)
Expand All @@ -1027,6 +1100,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,
Expand Down
32 changes: 31 additions & 1 deletion src/libostree/ostree-repo.h
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,34 @@ ostree_repo_checkout_tree (OstreeRepo *self,
GCancellable *cancellable,
GError **error);

/**
* OstreeRepoCheckoutFilterResult:
* @OSTREE_REPO_CHECKOUT_FILTER_ALLOW: Do checkout this object
* @OSTREE_REPO_CHECKOUT_FILTER_SKIP: Ignore this object
*
* Since: 2018.2
*/
typedef enum {
OSTREE_REPO_CHECKOUT_FILTER_ALLOW,
OSTREE_REPO_CHECKOUT_FILTER_SKIP
} OstreeRepoCheckoutFilterResult;

/**
* OstreeRepoCheckoutFilter:
* @repo: Repo
* @path: Path to file
* @stbuf: File information
* @user_data: User data
*
* Returns: #OstreeRepoCheckoutFilterResult saying whether or not to checkout this file
*
* Since: 2018.2
*/
typedef OstreeRepoCheckoutFilterResult (*OstreeRepoCheckoutFilter) (OstreeRepo *repo,
const char *path,
struct stat *stbuf,
gpointer user_data);

/**
* OstreeRepoCheckoutAtOptions:
*
Expand Down Expand Up @@ -969,7 +997,9 @@ typedef struct {
OstreeRepoDevInoCache *devino_to_csum_cache;

int unused_ints[6];
gpointer unused_ptrs[5];
gpointer unused_ptrs[3];
OstreeRepoCheckoutFilter filter; /* Since: 2018.2 */
gpointer filter_user_data; /* Since: 2018.2 */
OstreeSePolicy *sepolicy; /* Since: 2017.6 */
const char *sepolicy_prefix;
} OstreeRepoCheckoutAtOptions;
Expand Down
37 changes: 36 additions & 1 deletion src/ostree/ot-builtin-checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ static gboolean opt_disable_fsync;
static gboolean opt_require_hardlinks;
static gboolean opt_force_copy;
static gboolean opt_bareuseronly_dirs;
static char *opt_skiplist_file;
static char *opt_selinux_policy;
static char *opt_selinux_prefix;

Expand Down Expand Up @@ -85,11 +86,34 @@ static GOptionEntry options[] = {
{ "require-hardlinks", 'H', 0, G_OPTION_ARG_NONE, &opt_require_hardlinks, "Do not fall back to full copies if hardlinking fails", 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" },
{ "selinux-policy", 0, 0, G_OPTION_ARG_FILENAME, &opt_selinux_policy, "Set SELinux labels based on policy in root filesystem PATH (may be /); implies --force-copy", "PATH" },
{ "selinux-prefix", 0, 0, G_OPTION_ARG_STRING, &opt_selinux_prefix, "When setting SELinux labels, prefix all paths by PREFIX", "PREFIX" },
{ NULL }
};

static gboolean
handle_skiplist_line (const char *line,
void *data,
GError **error)
{
GHashTable *files = data;
g_hash_table_add (files, g_strdup (line));
return TRUE;
}

static OstreeRepoCheckoutFilterResult
checkout_filter (OstreeRepo *self,
const char *path,
struct stat *st_buf,
gpointer user_data)
{
GHashTable *skiplist = user_data;
if (g_hash_table_contains (skiplist, path))
return OSTREE_REPO_CHECKOUT_FILTER_SKIP;
return OSTREE_REPO_CHECKOUT_FILTER_ALLOW;
}

static gboolean
process_one_checkout (OstreeRepo *repo,
const char *resolved_commit,
Expand All @@ -107,7 +131,7 @@ process_one_checkout (OstreeRepo *repo,
*/
if (opt_disable_cache || opt_whiteouts || opt_require_hardlinks ||
opt_union_add || opt_force_copy || opt_bareuseronly_dirs || opt_union_identical ||
opt_selinux_policy || opt_selinux_prefix)
opt_skiplist_file || opt_selinux_policy || opt_selinux_prefix)
{
OstreeRepoCheckoutAtOptions options = { 0, };

Expand Down Expand Up @@ -181,6 +205,17 @@ process_one_checkout (OstreeRepo *repo,
options.sepolicy_prefix = opt_selinux_prefix;
}

g_autoptr(GHashTable) skip_list =
g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
if (opt_skiplist_file)
{
if (!ot_parse_file_by_line (opt_skiplist_file, handle_skiplist_line, skip_list,
cancellable, error))
goto out;
options.filter = checkout_filter;
options.filter_user_data = skip_list;
}

options.no_copy_fallback = opt_require_hardlinks;
options.force_copy = opt_force_copy;
options.bareuseronly_dirs = opt_bareuseronly_dirs;
Expand Down
Loading

0 comments on commit 654add0

Please sign in to comment.