From 3ad4e6c72bb5b25745252da4fd1cab6e00827f72 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Mon, 15 Aug 2016 11:58:03 -0400 Subject: [PATCH] bwrap/compose: Add a workaround for Fedora's use of rpm-ostree-in-mock Decided to test this on Sunday evening. Of course it was broken =( (Actually I tested mock-in-Docker but it should be the same) The core problem is that mock does `chroot()` without using `/` as a mount point. This breaks an assumption in bwrap that it is. Now, in theory we could move this same logic down into bwrap to work around this situation, but for now let's hack it here. Mock is old, legacy container code that doesn't really do anything in a modern way - in fact our goal should be to replace it with a combination of rpm-ostree and bwrap. So carrying this hack here to get us to that future should be OK for now. Closes: #431 Approved by: jlebon --- src/app/rpmostree-compose-builtin-tree.c | 4 ++ src/libpriv/rpmostree-bwrap.c | 81 ++++++++++++++++++++++++ src/libpriv/rpmostree-bwrap.h | 1 + 3 files changed, 86 insertions(+) diff --git a/src/app/rpmostree-compose-builtin-tree.c b/src/app/rpmostree-compose-builtin-tree.c index 5ea1cbe3..e49c5e49 100644 --- a/src/app/rpmostree-compose-builtin-tree.c +++ b/src/app/rpmostree-compose-builtin-tree.c @@ -630,6 +630,10 @@ rpmostree_compose_builtin_tree (int argc, "compose tree must presently be run as uid 0 (root)"); goto out; } + + /* Mock->bwrap bootstrap for Fedora */ + if (!rpmostree_bwrap_bootstrap_if_in_mock (error)) + goto out; /* Test whether or not bwrap is going to work - we will fail inside e.g. a Docker * container without --privileged or userns exposed. */ diff --git a/src/libpriv/rpmostree-bwrap.c b/src/libpriv/rpmostree-bwrap.c index bd7c7939..85e7837c 100644 --- a/src/libpriv/rpmostree-bwrap.c +++ b/src/libpriv/rpmostree-bwrap.c @@ -108,6 +108,87 @@ rpmostree_run_sync_fchdir_setup (char **argv_array, GSpawnFlags flags, return TRUE; } +/* mock doesn't actually use a mount namespace, and hence bwrap will + * fail to remount /. Work around this by doing it here. Fedora + * runs rpm-ostree inside of mock instead of Docker or something + * more modern. + */ +gboolean +rpmostree_bwrap_bootstrap_if_in_mock (GError **error) +{ + const char *env_ps1 = getenv ("PS1"); + static const char *mock_mounted_paths[] = { "/proc", "/sys" }; + static const char *findmnt_argv[] = { "findmnt", "/", NULL }; + g_autofree char *pwd = NULL; + int estatus; + + if (!(env_ps1 && strstr (env_ps1, ""))) + return TRUE; + + /* Okay, we detected we're inside mock. Let's double check now + * whether or not / is already a mount point. The simplest way to + * do this is to execute findmnt...maybe someday we'll link to libmount + * but this is legacy. + */ + if (!g_spawn_sync (NULL, (char**)findmnt_argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, + NULL, NULL, &estatus, error)) + { + g_prefix_error (error, "Executing findmnt: "); + return FALSE; + } + /* Did findmnt say / is a mount point? Okay, nothing to do here. */ + if (estatus == 0) + return TRUE; + + pwd = getcwd (NULL, 0); + + g_print ("Detected mock chroot without / as mount, enabling workaround.\n"); + if (unshare (CLONE_NEWNS) < 0) + { + glnx_set_prefix_error_from_errno (error, "%s", "unshare(CLONE_NEWNS)"); + return FALSE; + } + /* For reasons I don't fully understand, trying to bind mount / -> / + * doesn't work. We seem to hit a check in the kernel: + * + * static int do_change_type(struct path *path, int flag) + * { + * ... + * if (path->dentry != path->mnt->mnt_root) + * return -EINVAL; + */ + if (mount ("/", "/mnt", NULL, MS_MGC_VAL | MS_BIND, NULL) != 0) + { + glnx_set_prefix_error_from_errno (error, "%s", "mount(/ as bind)"); + return FALSE; + } + /* Now take the paths that mock mounted (that we need) and move them + * underneath the new rootfs mount. + */ + for (guint i = 0; i < G_N_ELEMENTS (mock_mounted_paths); i++) + { + const char *mockpath = mock_mounted_paths[i]; + g_autofree char *destpath = g_strconcat ("/mnt", mockpath, NULL); + if (mount (mockpath, destpath, NULL, MS_MGC_VAL | MS_MOVE, NULL) != 0) + { + glnx_set_prefix_error_from_errno (error, "%s", "mount(move)"); + return FALSE; + } + } + if (chroot ("/mnt") < 0) + { + glnx_set_error_from_errno (error); + return FALSE; + } + if (chdir (pwd) < 0) + { + glnx_set_error_from_errno (error); + return FALSE; + } + + return TRUE; +} + /* Execute /bin/true inside a bwrap container on the host */ gboolean rpmostree_bwrap_selftest (GError **error) diff --git a/src/libpriv/rpmostree-bwrap.h b/src/libpriv/rpmostree-bwrap.h index b4bb88c9..b4643558 100644 --- a/src/libpriv/rpmostree-bwrap.h +++ b/src/libpriv/rpmostree-bwrap.h @@ -31,4 +31,5 @@ void rpmostree_ptrarray_append_strdup (GPtrArray *argv_array, ...) G_GNUC_NULL_T gboolean rpmostree_run_sync_fchdir_setup (char **argv_array, GSpawnFlags flags, int rootfs_fd, GError **error); +gboolean rpmostree_bwrap_bootstrap_if_in_mock (GError **error); gboolean rpmostree_bwrap_selftest (GError **error);