core: Maintain /usr/etc as /etc when running scripts
In preparation for running in default Docker permissions where we can `chroot()` and `makedev()` but not e.g. create bind mounts, move `/usr/etc` to `/etc` when running scripts. The script processing is also entangled with our passwd/group file handling, so change those functions called from the core too. It's tempting to basically maintain `/usr/etc` as `/etc` all the way from immediately after checkout to just before commit. We can't change how we do imports now; perhaps importing RPMs into ostree as `usr/etc` was just a mistake in retrospect, but oh well. Closes: #1592 Approved by: jlebon
This commit is contained in:
parent
a86ad96669
commit
1d031fe51e
@ -25,6 +25,9 @@
|
||||
#include <stdio.h>
|
||||
#include <systemd/sd-journal.h>
|
||||
|
||||
static void
|
||||
teardown_rofiles (GLnxTmpDir *mnt_tmp);
|
||||
|
||||
void
|
||||
rpmostree_ptrarray_append_strdup (GPtrArray *argv_array, ...)
|
||||
{
|
||||
@ -47,7 +50,8 @@ struct RpmOstreeBwrap {
|
||||
GSubprocessLauncher *launcher; /* 🚀 */
|
||||
GPtrArray *argv;
|
||||
const char *child_argv0;
|
||||
GLnxTmpDir rofiles_mnt;
|
||||
GLnxTmpDir rofiles_mnt_usr;
|
||||
GLnxTmpDir rofiles_mnt_etc;
|
||||
|
||||
GSpawnChildSetupFunc child_setup_func;
|
||||
gpointer child_setup_data;
|
||||
@ -67,37 +71,8 @@ rpmostree_bwrap_unref (RpmOstreeBwrap *bwrap)
|
||||
if (bwrap->refcount > 0)
|
||||
return;
|
||||
|
||||
if (bwrap->rofiles_mnt.initialized)
|
||||
{
|
||||
g_autoptr(GError) tmp_error = NULL;
|
||||
const char *fusermount_argv[] = { "fusermount", "-u", bwrap->rofiles_mnt.path, NULL};
|
||||
int estatus;
|
||||
|
||||
if (!g_spawn_sync (NULL, (char**)fusermount_argv, NULL, G_SPAWN_SEARCH_PATH,
|
||||
NULL, NULL, NULL, NULL, &estatus, &tmp_error))
|
||||
{
|
||||
g_prefix_error (&tmp_error, "Executing fusermount: ");
|
||||
goto out;
|
||||
}
|
||||
if (!g_spawn_check_exit_status (estatus, &tmp_error))
|
||||
{
|
||||
g_prefix_error (&tmp_error, "Executing fusermount: ");
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
/* We don't want a failure to unmount to be fatal, so all we do here
|
||||
* is log. Though in practice what we *really* want is for the
|
||||
* fusermount to be in the bwrap namespace, and hence tied by the
|
||||
* kernel to the lifecycle of the container. This would require
|
||||
* special casing for somehow doing FUSE mounts in bwrap. Which
|
||||
* would be hard because NO_NEW_PRIVS turns off the setuid bits for
|
||||
* fuse.
|
||||
*/
|
||||
if (tmp_error)
|
||||
sd_journal_print (LOG_WARNING, "%s", tmp_error->message);
|
||||
}
|
||||
(void)glnx_tmpdir_delete (&bwrap->rofiles_mnt, NULL, NULL);
|
||||
teardown_rofiles (&bwrap->rofiles_mnt_usr);
|
||||
teardown_rofiles (&bwrap->rofiles_mnt_etc);
|
||||
|
||||
g_clear_object (&bwrap->launcher);
|
||||
g_ptr_array_unref (bwrap->argv);
|
||||
@ -189,15 +164,20 @@ child_setup_fchdir (gpointer user_data)
|
||||
}
|
||||
|
||||
static gboolean
|
||||
setup_rofiles_usr (RpmOstreeBwrap *bwrap,
|
||||
GError **error)
|
||||
setup_rofiles (RpmOstreeBwrap *bwrap,
|
||||
const char *path,
|
||||
GLnxTmpDir *mnt_tmp,
|
||||
GError **error)
|
||||
{
|
||||
const char *rofiles_argv[] = { "rofiles-fuse", "--copyup", "./usr", NULL, NULL};
|
||||
GLNX_AUTO_PREFIX_ERROR ("rofiles setup", error);
|
||||
const char *relpath = path + strspn (path, "/");
|
||||
const char *rofiles_argv[] = { "rofiles-fuse", "--copyup", relpath, NULL, NULL};
|
||||
|
||||
if (!glnx_mkdtemp ("rpmostree-rofiles-fuse.XXXXXX", 0700, &bwrap->rofiles_mnt, error))
|
||||
g_auto(GLnxTmpDir) local_mnt_tmp = { 0, };
|
||||
if (!glnx_mkdtemp ("rpmostree-rofiles-fuse.XXXXXX", 0700, &local_mnt_tmp, error))
|
||||
return FALSE;
|
||||
|
||||
const char *rofiles_mntpath = bwrap->rofiles_mnt.path;
|
||||
const char *rofiles_mntpath = local_mnt_tmp.path;
|
||||
rofiles_argv[3] = rofiles_mntpath;
|
||||
|
||||
int estatus;
|
||||
@ -208,17 +188,55 @@ setup_rofiles_usr (RpmOstreeBwrap *bwrap,
|
||||
if (!g_spawn_check_exit_status (estatus, error))
|
||||
return FALSE;
|
||||
|
||||
rpmostree_bwrap_bind_readwrite (bwrap, rofiles_mntpath, "/usr");
|
||||
rpmostree_bwrap_bind_readwrite (bwrap, rofiles_mntpath, path);
|
||||
|
||||
/* also mount /etc from the rofiles mount to allow RPM scripts to change defaults, while
|
||||
* still being protected; note we use bind to ensure symlinks work, see:
|
||||
* https://github.com/projectatomic/rpm-ostree/pull/640 */
|
||||
const char *rofiles_etc_mntpath = glnx_strjoina (rofiles_mntpath, "/etc");
|
||||
rpmostree_bwrap_bind_readwrite (bwrap, rofiles_etc_mntpath, "/etc");
|
||||
/* And transfer ownership of the tmpdir */
|
||||
*mnt_tmp = local_mnt_tmp;
|
||||
local_mnt_tmp.initialized = FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
teardown_rofiles (GLnxTmpDir *mnt_tmp)
|
||||
{
|
||||
g_assert (mnt_tmp);
|
||||
if (!mnt_tmp->initialized)
|
||||
return;
|
||||
|
||||
g_autoptr(GError) tmp_error = NULL;
|
||||
const char *fusermount_argv[] = { "fusermount", "-u", mnt_tmp->path, NULL};
|
||||
int estatus;
|
||||
|
||||
GLNX_AUTO_PREFIX_ERROR ("rofiles teardown", &tmp_error);
|
||||
|
||||
if (!g_spawn_sync (NULL, (char**)fusermount_argv, NULL, G_SPAWN_SEARCH_PATH,
|
||||
NULL, NULL, NULL, NULL, &estatus, &tmp_error))
|
||||
{
|
||||
g_prefix_error (&tmp_error, "Executing fusermount: ");
|
||||
goto out;
|
||||
}
|
||||
if (!g_spawn_check_exit_status (estatus, &tmp_error))
|
||||
{
|
||||
g_prefix_error (&tmp_error, "Executing fusermount: ");
|
||||
goto out;
|
||||
}
|
||||
|
||||
(void)glnx_tmpdir_delete (mnt_tmp, NULL, NULL);
|
||||
|
||||
out:
|
||||
/* We don't want a failure to unmount to be fatal, so all we do here
|
||||
* is log. Though in practice what we *really* want is for the
|
||||
* fusermount to be in the bwrap namespace, and hence tied by the
|
||||
* kernel to the lifecycle of the container. This would require
|
||||
* special casing for somehow doing FUSE mounts in bwrap. Which
|
||||
* would be hard because NO_NEW_PRIVS turns off the setuid bits for
|
||||
* fuse.
|
||||
*/
|
||||
if (tmp_error)
|
||||
sd_journal_print (LOG_WARNING, "%s", tmp_error->message);
|
||||
}
|
||||
|
||||
/* nspawn by default doesn't give us CAP_NET_ADMIN; see
|
||||
* https://pagure.io/releng/issue/6602#comment-71214
|
||||
* https://pagure.io/koji/pull-request/344#comment-21060
|
||||
@ -348,13 +366,19 @@ rpmostree_bwrap_new (int rootfs_fd,
|
||||
{
|
||||
case RPMOSTREE_BWRAP_IMMUTABLE:
|
||||
rpmostree_bwrap_bind_read (ret, "usr", "/usr");
|
||||
rpmostree_bwrap_bind_read (ret, "etc", "/etc");
|
||||
break;
|
||||
case RPMOSTREE_BWRAP_MUTATE_ROFILES:
|
||||
if (!setup_rofiles_usr (ret, error))
|
||||
if (!setup_rofiles (ret, "/usr",
|
||||
&ret->rofiles_mnt_usr, error))
|
||||
return NULL;
|
||||
if (!setup_rofiles (ret, "/etc",
|
||||
&ret->rofiles_mnt_etc, error))
|
||||
return NULL;
|
||||
break;
|
||||
case RPMOSTREE_BWRAP_MUTATE_FREELY:
|
||||
rpmostree_bwrap_bind_readwrite (ret, "usr", "/usr");
|
||||
rpmostree_bwrap_bind_readwrite (ret, "etc", "/etc");
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -3301,8 +3301,10 @@ apply_rpmfi_overrides (RpmOstreeContext *self,
|
||||
continue;
|
||||
else if (g_str_has_prefix (fn, "etc/"))
|
||||
{
|
||||
/* The tree uses usr/etc */
|
||||
fn = modified_fn = g_strconcat ("usr/", fn, NULL);
|
||||
/* Changing /etc is OK; note "normally" we maintain
|
||||
* usr/etc but this runs right after %pre, where
|
||||
* we're in the middle of running scripts.
|
||||
*/
|
||||
}
|
||||
else if (!g_str_has_prefix (fn, "usr/"))
|
||||
{
|
||||
@ -3809,6 +3811,21 @@ rpmostree_context_assemble (RpmOstreeContext *self,
|
||||
gboolean skip_sanity_check = FALSE;
|
||||
g_variant_dict_lookup (self->spec->dict, "skip-sanity-check", "b", &skip_sanity_check);
|
||||
|
||||
if (!glnx_fstatat_allow_noent (tmprootfs_dfd, "usr/etc", NULL, 0, error))
|
||||
return FALSE;
|
||||
gboolean renamed_etc = (errno == 0);
|
||||
if (renamed_etc)
|
||||
{
|
||||
/* In general now, we place contents in /etc when running scripts */
|
||||
if (!glnx_renameat (tmprootfs_dfd, "usr/etc", tmprootfs_dfd, "etc", error))
|
||||
return FALSE;
|
||||
/* But leave a compat symlink, as we used to bind mount, so scripts
|
||||
* could still use that too.
|
||||
*/
|
||||
if (symlinkat ("../etc", tmprootfs_dfd, "usr/etc") < 0)
|
||||
return glnx_throw_errno_prefix (error, "symlinkat");
|
||||
}
|
||||
|
||||
/* NB: we're not running scripts right now for removals, so this is only for overlays and
|
||||
* replacements */
|
||||
if (overlays->len > 0 || overrides_replace->len > 0)
|
||||
@ -3900,10 +3917,10 @@ rpmostree_context_assemble (RpmOstreeContext *self,
|
||||
}
|
||||
rpmostree_output_task_end ("%u done", n_pre_scripts_run);
|
||||
|
||||
if (faccessat (tmprootfs_dfd, "usr/etc/passwd", F_OK, 0) == 0)
|
||||
if (faccessat (tmprootfs_dfd, "etc/passwd", F_OK, 0) == 0)
|
||||
{
|
||||
g_autofree char *contents =
|
||||
glnx_file_get_contents_utf8_at (tmprootfs_dfd, "usr/etc/passwd",
|
||||
glnx_file_get_contents_utf8_at (tmprootfs_dfd, "etc/passwd",
|
||||
NULL, cancellable, error);
|
||||
if (!contents)
|
||||
return FALSE;
|
||||
@ -3916,10 +3933,10 @@ rpmostree_context_assemble (RpmOstreeContext *self,
|
||||
}
|
||||
}
|
||||
|
||||
if (faccessat (tmprootfs_dfd, "usr/etc/group", F_OK, 0) == 0)
|
||||
if (faccessat (tmprootfs_dfd, "etc/group", F_OK, 0) == 0)
|
||||
{
|
||||
g_autofree char *contents =
|
||||
glnx_file_get_contents_utf8_at (tmprootfs_dfd, "usr/etc/group",
|
||||
glnx_file_get_contents_utf8_at (tmprootfs_dfd, "etc/group",
|
||||
NULL, cancellable, error);
|
||||
if (!contents)
|
||||
return FALSE;
|
||||
@ -4008,6 +4025,16 @@ rpmostree_context_assemble (RpmOstreeContext *self,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Undo the /etc move above */
|
||||
if (renamed_etc)
|
||||
{
|
||||
/* Remove the symlink and swap back */
|
||||
if (!glnx_unlinkat (tmprootfs_dfd, "usr/etc", 0, error))
|
||||
return FALSE;
|
||||
if (!glnx_renameat (tmprootfs_dfd, "etc", tmprootfs_dfd, "usr/etc", error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* And clean up var/tmp, we don't want it in commits */
|
||||
if (!glnx_shutil_rm_rf_at (tmprootfs_dfd, "var/tmp", cancellable, error))
|
||||
return FALSE;
|
||||
|
@ -1200,7 +1200,7 @@ rpmostree_passwd_prepare_rpm_layering (int rootfs_dfd,
|
||||
for (guint i = 0; i < G_N_ELEMENTS (pwgrp_shadow_files); i++)
|
||||
{
|
||||
const char *file = pwgrp_shadow_files[i];
|
||||
g_autofree char *src = g_strconcat ("usr/etc/", file, NULL);
|
||||
g_autofree char *src = g_strconcat ("etc/", file, NULL);
|
||||
|
||||
if (!glnx_fstatat_allow_noent (rootfs_dfd, src, NULL, AT_SYMLINK_NOFOLLOW, error))
|
||||
return FALSE;
|
||||
@ -1221,7 +1221,7 @@ rpmostree_passwd_prepare_rpm_layering (int rootfs_dfd,
|
||||
{
|
||||
const char *file = usrlib_pwgrp_files[i];
|
||||
g_autofree char *usrlibfile = g_strconcat ("usr/lib/", file, NULL);
|
||||
g_autofree char *usretcfile = g_strconcat ("usr/etc/", file, NULL);
|
||||
g_autofree char *usretcfile = g_strconcat ("etc/", file, NULL);
|
||||
g_autofree char *usrlibfiletmp = g_strconcat ("usr/lib/", file, ".tmp", NULL);
|
||||
|
||||
/* Retain the current copies in /etc as backups */
|
||||
@ -1229,7 +1229,7 @@ rpmostree_passwd_prepare_rpm_layering (int rootfs_dfd,
|
||||
glnx_strjoina (usretcfile, ".rpmostreesave"), error))
|
||||
return FALSE;
|
||||
|
||||
/* Copy /usr/lib/{passwd,group} -> /usr/etc (breaking hardlinks) */
|
||||
/* Copy /usr/lib/{passwd,group} -> /etc (breaking hardlinks) */
|
||||
if (!glnx_file_copy_at (rootfs_dfd, usrlibfile, NULL,
|
||||
rootfs_dfd, usretcfile,
|
||||
GLNX_FILE_COPY_NOXATTRS,
|
||||
@ -1261,13 +1261,13 @@ rpmostree_passwd_complete_rpm_layering (int rootfs_dfd,
|
||||
for (guint i = 0; i < G_N_ELEMENTS (usrlib_pwgrp_files); i++)
|
||||
{
|
||||
const char *file = usrlib_pwgrp_files[i];
|
||||
/* And now the inverse: /usr/etc/passwd -> /usr/lib/passwd */
|
||||
if (!glnx_renameat (rootfs_dfd, glnx_strjoina ("usr/etc/", file),
|
||||
/* And now the inverse: /etc/passwd -> /usr/lib/passwd */
|
||||
if (!glnx_renameat (rootfs_dfd, glnx_strjoina ("etc/", file),
|
||||
rootfs_dfd, glnx_strjoina ("usr/lib/", file), error))
|
||||
return FALSE;
|
||||
/* /usr/etc/passwd.rpmostreesave -> /usr/etc/passwd */
|
||||
if (!glnx_renameat (rootfs_dfd, glnx_strjoina ("usr/etc/", file, ".rpmostreesave"),
|
||||
rootfs_dfd, glnx_strjoina ("usr/etc/", file), error))
|
||||
/* /etc/passwd.rpmostreesave -> /etc/passwd */
|
||||
if (!glnx_renameat (rootfs_dfd, glnx_strjoina ("etc/", file, ".rpmostreesave"),
|
||||
rootfs_dfd, glnx_strjoina ("etc/", file), error))
|
||||
return FALSE;
|
||||
}
|
||||
/* However, we leave the (potentially modified) shadow files in place.
|
||||
|
@ -63,6 +63,15 @@ run_bwrap_mutably (int rootfs_fd,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
/* For scripts, it's /etc, not /usr/etc */
|
||||
if (!glnx_renameat (rootfs_fd, "usr/etc", rootfs_fd, "etc", error))
|
||||
return FALSE;
|
||||
/* But leave a compat symlink, as we used to bind mount, so scripts
|
||||
* could still use that too.
|
||||
*/
|
||||
if (symlinkat ("../etc", rootfs_fd, "usr/etc") < 0)
|
||||
return glnx_throw_errno_prefix (error, "symlinkat");
|
||||
|
||||
RpmOstreeBwrapMutability mut =
|
||||
unified_core_mode ? RPMOSTREE_BWRAP_MUTATE_ROFILES : RPMOSTREE_BWRAP_MUTATE_FREELY;
|
||||
g_autoptr(RpmOstreeBwrap) bwrap = rpmostree_bwrap_new (rootfs_fd, mut, error);
|
||||
@ -70,14 +79,9 @@ run_bwrap_mutably (int rootfs_fd,
|
||||
return FALSE;
|
||||
|
||||
if (unified_core_mode)
|
||||
{
|
||||
rpmostree_bwrap_bind_read (bwrap, "./var", "/var");
|
||||
}
|
||||
rpmostree_bwrap_bind_read (bwrap, "var", "/var");
|
||||
else
|
||||
{
|
||||
rpmostree_bwrap_bind_readwrite (bwrap, "var", "/var");
|
||||
rpmostree_bwrap_bind_readwrite (bwrap, "usr/etc", "/etc");
|
||||
}
|
||||
rpmostree_bwrap_bind_readwrite (bwrap, "var", "/var");
|
||||
|
||||
rpmostree_bwrap_append_child_argv (bwrap, binpath, NULL);
|
||||
|
||||
@ -95,6 +99,12 @@ run_bwrap_mutably (int rootfs_fd,
|
||||
if (!rpmostree_bwrap_run (bwrap, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
/* Remove the symlink and swap back */
|
||||
if (!glnx_unlinkat (rootfs_fd, "usr/etc", 0, error))
|
||||
return FALSE;
|
||||
if (!glnx_renameat (rootfs_fd, "etc", rootfs_fd, "usr/etc", error))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -1629,6 +1639,10 @@ rpmostree_treefile_postprocessing (int rootfs_fd,
|
||||
if (symlinkat ("../../" RPMOSTREE_RPMDB_LOCATION, rootfs_fd, "var/lib/rpm") < 0)
|
||||
return glnx_throw_errno_prefix (error, "symlinkat(%s)", "var/lib/rpm");
|
||||
|
||||
/* Take care of /etc for these bits */
|
||||
if (!rename_if_exists (rootfs_fd, "usr/etc", rootfs_fd, "etc", error))
|
||||
return FALSE;
|
||||
|
||||
if (json_object_has_member (treefile, "remove-from-packages"))
|
||||
{
|
||||
remove = json_object_get_array_member (treefile, "remove-from-packages");
|
||||
@ -1639,10 +1653,6 @@ rpmostree_treefile_postprocessing (int rootfs_fd,
|
||||
if (!refsack)
|
||||
return glnx_prefix_error (error, "Reading package set");
|
||||
|
||||
/* Backwards compatibility */
|
||||
if (!rename_if_exists (rootfs_fd, "usr/etc", rootfs_fd, "etc", error))
|
||||
return FALSE;
|
||||
|
||||
for (guint i = 0; i < len; i++)
|
||||
{
|
||||
JsonArray *elt = json_array_get_array_element (remove, i);
|
||||
@ -1650,9 +1660,6 @@ rpmostree_treefile_postprocessing (int rootfs_fd,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Backwards compatibility */
|
||||
if (!rename_if_exists (rootfs_fd, "etc", rootfs_fd, "usr/etc", error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
{
|
||||
@ -1682,7 +1689,6 @@ rpmostree_treefile_postprocessing (int rootfs_fd,
|
||||
return FALSE;
|
||||
|
||||
/* map back to /etc so relative symlinks work */
|
||||
rpmostree_bwrap_bind_readwrite (bwrap, "usr/etc", "/etc");
|
||||
rpmostree_bwrap_append_child_argv (bwrap, "realpath", "-z", "/etc/os-release", NULL);
|
||||
|
||||
g_autoptr(GBytes) out = NULL;
|
||||
@ -1698,16 +1704,11 @@ rpmostree_treefile_postprocessing (int rootfs_fd,
|
||||
pathbuf[len-1] = '\0';
|
||||
|
||||
path = pathbuf+1; /* skip initial '/' */
|
||||
if (g_str_has_prefix (path, "etc/"))
|
||||
{
|
||||
g_autofree char *old_pathbuf = pathbuf;
|
||||
path = pathbuf = g_strdup_printf ("usr/%s", path);
|
||||
}
|
||||
}
|
||||
|
||||
/* fallback on just overwriting etc/os-release */
|
||||
if (!path)
|
||||
path = "usr/etc/os-release";
|
||||
path = "etc/os-release";
|
||||
|
||||
g_print ("Mutating /%s\n", path);
|
||||
|
||||
@ -1728,6 +1729,10 @@ rpmostree_treefile_postprocessing (int rootfs_fd,
|
||||
}
|
||||
}
|
||||
|
||||
/* Undo etc move again */
|
||||
if (!rename_if_exists (rootfs_fd, "etc", rootfs_fd, "usr/etc", error))
|
||||
return FALSE;
|
||||
|
||||
/* Copy in additional files before postprocessing */
|
||||
if (!copy_additional_files (rootfs_fd, treefile_rs, treefile, cancellable, error))
|
||||
return FALSE;
|
||||
|
@ -953,7 +953,6 @@ rpmostree_deployment_sanitycheck_true (int rootfs_fd,
|
||||
|
||||
if (!bwrap)
|
||||
return FALSE;
|
||||
rpmostree_bwrap_bind_read (bwrap, "./usr/etc", "/etc");
|
||||
rpmostree_bwrap_append_child_argv (bwrap, "/usr/bin/true", NULL);
|
||||
if (!rpmostree_bwrap_run (bwrap, cancellable, error))
|
||||
return FALSE;
|
||||
|
Loading…
x
Reference in New Issue
Block a user