Skip to content

Commit

Permalink
checkout: Add force_copy+SELinux options for checkout, use in deploy
Browse files Browse the repository at this point in the history
This is a variant of the efforts in ostreedev#741
Working on `rpm-ostree livefs`, I realized though I needed to just
check out *new* files directly into the live `/etc` (and possibly
delete obsolete files).

The way the current `/etc` merge works is fundamentally different from
that.  So my plan currently is to probably do something like:

 - Compute diff
 - Check out each *new* file individually (as a copy)
 - Optionally delete obsolete files

Also, a few other things become more important - in the current deploy code, we
copy all of the files, then relabel them. But we shouldn't expose to *live*
systems the race conditions of doing that, plus we should only relabel files we
checked out.

By converting the deploy's /etc code to use this, we fix the same TODO item
there around atomically having the label set up as we create files. And further,
if we kill the `/var` relabeling which I think is unnecessary since Anaconda
does it, we could delete large chunks of code there.
  • Loading branch information
cgwalters committed Apr 14, 2017
1 parent 5b5a339 commit e5e6b6b
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 49 deletions.
131 changes: 101 additions & 30 deletions src/libostree/ostree-repo-checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,17 @@
#include "otutil.h"

#include "ostree-repo-file.h"
#include "ostree-sepolicy-private.h"
#include "ostree-core-private.h"
#include "ostree-repo-private.h"

#define WHITEOUT_PREFIX ".wh."

/* Per-checkout call state/caching */
typedef struct {
GString *selabel_path_buf;
} CheckoutState;

static gboolean
checkout_object_for_uncompressed_cache (OstreeRepo *self,
const char *loose_path,
Expand Down Expand Up @@ -157,6 +163,7 @@ write_regular_file_content (OstreeRepo *self,
static gboolean
checkout_file_from_input_at (OstreeRepo *self,
OstreeRepoCheckoutAtOptions *options,
CheckoutState *state,
GFileInfo *file_info,
GVariant *xattrs,
GInputStream *input,
Expand All @@ -166,6 +173,17 @@ checkout_file_from_input_at (OstreeRepo *self,
GError **error)
{
int res;
g_auto(OstreeSepolicyFsCreatecon) fscreatecon = { 0, };

/* If we're doing SELinux labeling, prepare it */
if (options->sepolicy)
{
if (!_ostree_sepolicy_preparefscreatecon (&fscreatecon, options->sepolicy,
state->selabel_path_buf->str,
g_file_info_get_attribute_uint32 (file_info, "unix::mode"),
error))
return FALSE;
}

if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK)
{
Expand Down Expand Up @@ -371,6 +389,7 @@ checkout_file_hardlink (OstreeRepo *self,
static gboolean
checkout_one_file_at (OstreeRepo *repo,
OstreeRepoCheckoutAtOptions *options,
CheckoutState *state,
GFile *source,
GFileInfo *source_info,
int destination_dfd,
Expand All @@ -380,7 +399,7 @@ checkout_one_file_at (OstreeRepo *repo,
{
gboolean need_copy = TRUE;
gboolean is_bare_user_symlink = FALSE;
char loose_path_buf[_OSTREE_LOOSE_PATH_MAX];
char loose_selabel_path_buf[_OSTREE_LOOSE_PATH_MAX];
const gboolean is_symlink = g_file_info_get_file_type (source_info) == G_FILE_TYPE_SYMBOLIC_LINK;
const char *checksum = ostree_repo_file_get_checksum ((OstreeRepoFile*)source);
const gboolean is_whiteout = !is_symlink && options->process_whiteouts &&
Expand All @@ -403,7 +422,7 @@ checkout_one_file_at (OstreeRepo *repo,

need_copy = FALSE;
}
else
else if (!options->force_copy)
{
HardlinkResult hardlink_res = HARDLINK_RESULT_NOT_SUPPORTED;
/* Try to do a hardlink first, if it's a regular file. This also
Expand Down Expand Up @@ -451,10 +470,10 @@ checkout_one_file_at (OstreeRepo *repo,
{
/* Override repo mode; for archive-z2 we're looking in
the cache, which is in "bare" form */
_ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE);
_ostree_loose_path (loose_selabel_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE);
if (!checkout_file_hardlink (current_repo,
options,
loose_path_buf,
loose_selabel_path_buf,
destination_dfd, destination_name,
TRUE, &hardlink_res,
cancellable, error))
Expand Down Expand Up @@ -508,9 +527,9 @@ checkout_one_file_at (OstreeRepo *repo,
return FALSE;

/* Overwrite any parent repo from earlier */
_ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE);
_ostree_loose_path (loose_selabel_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE);

if (!checkout_object_for_uncompressed_cache (repo, loose_path_buf,
if (!checkout_object_for_uncompressed_cache (repo, loose_selabel_path_buf,
source_info, input,
cancellable, error))
return g_prefix_error (error, "Unpacking loose object %s: ", checksum), FALSE;
Expand Down Expand Up @@ -542,7 +561,7 @@ checkout_one_file_at (OstreeRepo *repo,
}
g_mutex_unlock (&repo->cache_lock);

if (!checkout_file_hardlink (repo, options, loose_path_buf,
if (!checkout_file_hardlink (repo, options, loose_selabel_path_buf,
destination_dfd, destination_name,
FALSE, &hardlink_res,
cancellable, error))
Expand Down Expand Up @@ -575,7 +594,8 @@ checkout_one_file_at (OstreeRepo *repo,
}
else
{
if (!checkout_file_from_input_at (repo, options, source_info, xattrs, input,
if (!checkout_file_from_input_at (repo, options, state,
source_info, xattrs, input,
destination_dfd,
destination_name,
cancellable, error))
Expand All @@ -596,6 +616,7 @@ checkout_one_file_at (OstreeRepo *repo,
* checkout_tree_at:
* @self: Repo
* @mode: Options controlling all files
* @state: Any state we're carrying through
* @overwrite_mode: Whether or not to overwrite files
* @destination_parent_fd: Place tree here
* @destination_name: Use this name for tree
Expand All @@ -608,14 +629,15 @@ checkout_one_file_at (OstreeRepo *repo,
* relative @destination_name, located by @destination_parent_fd.
*/
static gboolean
checkout_tree_at (OstreeRepo *self,
OstreeRepoCheckoutAtOptions *options,
int destination_parent_fd,
const char *destination_name,
OstreeRepoFile *source,
GFileInfo *source_info,
GCancellable *cancellable,
GError **error)
checkout_tree_at_recurse (OstreeRepo *self,
OstreeRepoCheckoutAtOptions *options,
CheckoutState *state,
int destination_parent_fd,
const char *destination_name,
OstreeRepoFile *source,
GFileInfo *source_info,
GCancellable *cancellable,
GError **error)
{
gboolean did_exist = FALSE;
int res;
Expand Down Expand Up @@ -669,7 +691,7 @@ checkout_tree_at (OstreeRepo *self,

if (g_file_info_get_file_type (source_info) != G_FILE_TYPE_DIRECTORY)
{
if (!checkout_one_file_at (self, options,
if (!checkout_one_file_at (self, options, state,
(GFile *) source,
source_info,
destination_dfd,
Expand All @@ -693,6 +715,7 @@ checkout_tree_at (OstreeRepo *self,
GFileInfo *file_info;
GFile *src_child;
const char *name;
GString *selabel_path_buf = state->selabel_path_buf;

if (!g_file_enumerator_iterate (dir_enum, &file_info, &src_child,
cancellable, error))
Expand All @@ -701,23 +724,33 @@ checkout_tree_at (OstreeRepo *self,
break;

name = g_file_info_get_name (file_info);
size_t namelen = strlen (name);
if (selabel_path_buf)
g_string_append_len (selabel_path_buf, name, namelen);

if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
{
if (!checkout_tree_at (self, options,
destination_dfd, name,
(OstreeRepoFile*)src_child, file_info,
cancellable, error))
if (selabel_path_buf)
g_string_append_c (selabel_path_buf, '/');
if (!checkout_tree_at_recurse (self, options, state,
destination_dfd, name,
(OstreeRepoFile*)src_child, file_info,
cancellable, error))
return FALSE;
if (state->selabel_path_buf)
g_string_truncate (selabel_path_buf, state->selabel_path_buf->len-1);
}
else
{
if (!checkout_one_file_at (self, options,
if (!checkout_one_file_at (self, options, state,
src_child, file_info,
destination_dfd, name,
cancellable, error))
return FALSE;
}

if (selabel_path_buf)
g_string_truncate (selabel_path_buf, selabel_path_buf->len - namelen);
}

/* We do fchmod/fchown last so that no one else could access the
Expand Down Expand Up @@ -766,6 +799,48 @@ checkout_tree_at (OstreeRepo *self,
return TRUE;
}

/* Begin a checkout process */
static gboolean
checkout_tree_at (OstreeRepo *self,
OstreeRepoCheckoutAtOptions *options,
int destination_parent_fd,
const char *destination_name,
OstreeRepoFile *source,
GFileInfo *source_info,
GCancellable *cancellable,
GError **error)
{
CheckoutState state = { 0, };
// 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;
}

return checkout_tree_at_recurse (self, options, &state, destination_parent_fd,
destination_name,
source, source_info,
cancellable, error);
}

static void
canonicalize_options (OstreeRepo *self,
OstreeRepoCheckoutAtOptions *options)
{
/* Canonicalize subpath to / */
if (!options->subpath)
options->subpath = "/";

/* Force USER mode for BARE_USER_ONLY always - nothing else makes sense */
if (ostree_repo_get_mode (self) == OSTREE_REPO_MODE_BARE_USER_ONLY)
options->mode = OSTREE_REPO_CHECKOUT_MODE_USER;
}

/**
* ostree_repo_checkout_tree:
* @self: Repo
Expand Down Expand Up @@ -793,14 +868,11 @@ ostree_repo_checkout_tree (OstreeRepo *self,
GError **error)
{
OstreeRepoCheckoutAtOptions options = { 0, };

if (ostree_repo_get_mode (self) == OSTREE_REPO_MODE_BARE_USER_ONLY)
mode = OSTREE_REPO_CHECKOUT_MODE_USER;

options.mode = mode;
options.overwrite_mode = overwrite_mode;
/* Backwards compatibility */
options.enable_uncompressed_cache = TRUE;
canonicalize_options (self, &options);

return checkout_tree_at (self, &options,
AT_FDCWD, gs_file_get_path_cached (destination),
Expand Down Expand Up @@ -894,9 +966,7 @@ ostree_repo_checkout_at (OstreeRepo *self,
/* Make a copy so we can modify the options */
real_options = *options;
options = &real_options;

if (ostree_repo_get_mode (self) == OSTREE_REPO_MODE_BARE_USER_ONLY)
options->mode = OSTREE_REPO_CHECKOUT_MODE_USER;
canonicalize_options (self, options);

g_autoptr(GFile) commit_root = (GFile*) _ostree_repo_file_new_for_commit (self, commit, error);
if (!commit_root)
Expand All @@ -906,7 +976,8 @@ ostree_repo_checkout_at (OstreeRepo *self,
return FALSE;

g_autoptr(GFile) target_dir = NULL;
if (options->subpath && strcmp (options->subpath, "/") != 0)

if (strcmp (options->subpath, "/") != 0)
target_dir = g_file_get_child (commit_root, options->subpath);
else
target_dir = g_object_ref (commit_root);
Expand Down
7 changes: 5 additions & 2 deletions src/libostree/ostree-repo.h
Original file line number Diff line number Diff line change
Expand Up @@ -767,14 +767,17 @@ typedef struct {
gboolean enable_fsync; /* Deprecated */
gboolean process_whiteouts;
gboolean no_copy_fallback;
gboolean unused_bools[7];
gboolean force_copy;
gboolean unused_bools[6];

const char *subpath;

OstreeRepoDevInoCache *devino_to_csum_cache;

int unused_ints[6];
gpointer unused_ptrs[7];
gpointer unused_ptrs[5];
OstreeSePolicy *sepolicy; /* Since: 2017.5 */
const char *sepolicy_prefix;
} OstreeRepoCheckoutAtOptions;

_OSTREE_PUBLIC
Expand Down
33 changes: 16 additions & 17 deletions src/libostree/ostree-sysroot-deploy.c
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,7 @@ selinux_relabel_var_if_needed (OstreeSysroot *sysroot,

static gboolean
merge_configuration (OstreeSysroot *sysroot,
OstreeRepo *repo,
OstreeDeployment *previous_deployment,
OstreeDeployment *deployment,
int deployment_dfd,
Expand Down Expand Up @@ -829,33 +830,31 @@ merge_configuration (OstreeSysroot *sysroot,
usretc_exists = TRUE;
etc_exists = FALSE;
}

if (usretc_exists)
{
glnx_fd_close int deployment_usr_dfd = -1;

if (!glnx_opendirat (deployment_dfd, "usr", TRUE, &deployment_usr_dfd, error))
goto out;

/* TODO - set out labels as we copy files */
g_assert (!etc_exists);
if (!copy_dir_recurse (deployment_usr_dfd, deployment_dfd, "etc",
sysroot->debug_flags, cancellable, error))
goto out;
/* We need copies of /etc from /usr/etc (so admins can use vi), and if
* SELinux is enabled, we need to relabel.
*/
OstreeRepoCheckoutAtOptions etc_co_opts = { .force_copy = TRUE,
.subpath = "/usr/etc",
.sepolicy_prefix = "/etc"};

/* Here, we initialize SELinux policy from the /usr/etc inside
* the root - this is before we've finalized the configuration
* merge into /etc. */
sepolicy = ostree_sepolicy_new (deployment_path, cancellable, error);
if (!sepolicy)
goto out;

if (ostree_sepolicy_get_name (sepolicy) != NULL)
{
if (!selinux_relabel_dir (sysroot, sepolicy, deployment_etc_path, "etc",
etc_co_opts.sepolicy = sepolicy;

/* Copy usr/etc → etc */
if (!ostree_repo_checkout_at (repo, &etc_co_opts,
deployment_dfd, "etc",
ostree_deployment_get_csum (deployment),
cancellable, error))
goto out;
}
goto out;
}

if (source_etc_path)
Expand Down Expand Up @@ -2017,7 +2016,7 @@ ostree_sysroot_deploy_tree (OstreeSysroot *self,
ostree_deployment_set_bootconfig (new_deployment, bootconfig);

glnx_unref_object OstreeSePolicy *sepolicy = NULL;
if (!merge_configuration (self, merge_deployment, new_deployment,
if (!merge_configuration (self, repo, merge_deployment, new_deployment,
deployment_dfd,
&sepolicy,
cancellable, error))
Expand Down

0 comments on commit e5e6b6b

Please sign in to comment.