postprocess: Use O_TMPFILE for dracut, merge reproducible bits

I was planning to do some further changes here, and I really don't like the
manual fork/exec stuff on in the --reproducible checks. Our subprocess code
should basically be all bwrap.  Synchronous code execution while not reading from the
pipe is a recipe for deadlocks.

What simplifies things a lot is to write to an `O_TMPFILE` fd (or a tempfile on
legacy kernels), and slightly extend our bwrap-executing code to support a child
setup function, so we can set the tmpfile fd to be stdout.

Now that we have a shell script wrapper we inject, it's trivial to reimplement
the "detect reproducibility" in shell script there, rather than C.

This doesn't matter much for treecompose today, but it will matter more when
we're supporting client side initramfs regeneration, since now the dracut
container can be fully immutable.

Closes: #560
Approved by: jlebon
This commit is contained in:
Colin Walters 2017-01-04 21:18:59 -05:00 committed by Atomic Bot
parent 7a421443f9
commit 50ab2983ab
3 changed files with 129 additions and 80 deletions

View File

@ -108,11 +108,34 @@ rpmostree_run_sync_fchdir_setup (char **argv_array, GSpawnFlags flags,
return TRUE; return TRUE;
} }
typedef struct {
int rootfs_fd;
GSpawnChildSetupFunc func;
gpointer data;
} ChildSetupData;
static void
bwrap_child_setup (gpointer data)
{
ChildSetupData *cdata = data;
if (fchdir (cdata->rootfs_fd) < 0)
err (1, "fchdir");
if (cdata->func)
cdata->func (cdata->data);
}
gboolean gboolean
rpmostree_run_bwrap_sync (char **argv_array, int rootfs_fd, GError **error) rpmostree_run_bwrap_sync_setup (char **argv_array,
int rootfs_fd,
GSpawnChildSetupFunc func,
gpointer data,
GError **error)
{ {
int estatus; int estatus;
const char *current_lang = getenv ("LANG"); const char *current_lang = getenv ("LANG");
ChildSetupData csetupdata = { rootfs_fd, func, data };
if (!current_lang) if (!current_lang)
current_lang = "C"; current_lang = "C";
@ -129,7 +152,7 @@ rpmostree_run_bwrap_sync (char **argv_array, int rootfs_fd, GError **error)
NULL}; NULL};
if (!g_spawn_sync (NULL, argv_array, (char**) bwrap_env, G_SPAWN_SEARCH_PATH, if (!g_spawn_sync (NULL, argv_array, (char**) bwrap_env, G_SPAWN_SEARCH_PATH,
child_setup_fchdir, GINT_TO_POINTER (rootfs_fd), bwrap_child_setup, &csetupdata,
NULL, NULL, &estatus, error)) NULL, NULL, &estatus, error))
return FALSE; return FALSE;
if (!g_spawn_check_exit_status (estatus, error)) if (!g_spawn_check_exit_status (estatus, error))
@ -139,6 +162,12 @@ rpmostree_run_bwrap_sync (char **argv_array, int rootfs_fd, GError **error)
return TRUE; return TRUE;
} }
gboolean
rpmostree_run_bwrap_sync (char **argv_array, int rootfs_fd, GError **error)
{
return rpmostree_run_bwrap_sync_setup (argv_array, rootfs_fd, NULL, NULL, error);
}
/* Execute /bin/true inside a bwrap container on the host */ /* Execute /bin/true inside a bwrap container on the host */
gboolean gboolean
rpmostree_bwrap_selftest (GError **error) rpmostree_bwrap_selftest (GError **error)

View File

@ -32,5 +32,9 @@ gboolean rpmostree_run_sync_fchdir_setup (char **argv_array, GSpawnFlags flags,
int rootfs_fd, GError **error); int rootfs_fd, GError **error);
gboolean rpmostree_run_bwrap_sync (char **argv_array, int rootfs_fd, GError **error); gboolean rpmostree_run_bwrap_sync (char **argv_array, int rootfs_fd, GError **error);
gboolean rpmostree_run_bwrap_sync_setup (char **argv_array, int rootfs_fd,
GSpawnChildSetupFunc func,
gpointer data,
GError **error);
gboolean rpmostree_bwrap_selftest (GError **error); gboolean rpmostree_bwrap_selftest (GError **error);

View File

@ -54,6 +54,8 @@ static gboolean
run_sync_in_root_at (int rootfs_fd, run_sync_in_root_at (int rootfs_fd,
const char *binpath, const char *binpath,
char **child_argv, char **child_argv,
GSpawnChildSetupFunc setup_func,
gpointer data,
GError **error) GError **error)
{ {
g_autoptr(GPtrArray) bwrap_argv = NULL; g_autoptr(GPtrArray) bwrap_argv = NULL;
@ -84,9 +86,10 @@ run_sync_in_root_at (int rootfs_fd,
} }
g_ptr_array_add (bwrap_argv, NULL); g_ptr_array_add (bwrap_argv, NULL);
if (!rpmostree_run_bwrap_sync ((char**)bwrap_argv->pdata, rootfs_fd, error)) if (!rpmostree_run_bwrap_sync_setup ((char**)bwrap_argv->pdata, rootfs_fd,
setup_func, data, error))
{ {
g_prefix_error (error, "Executing bwrap: "); g_prefix_error (error, "Executing bwrap(%s): ", child_argv[0]);
return FALSE; return FALSE;
} }
@ -256,64 +259,86 @@ find_ensure_one_subdirectory (int rootfs_dfd,
return TRUE; return TRUE;
} }
static gboolean static void
dracut_supports_reproducible (int rootfs_dfd, gboolean *supported, dracut_child_setup (gpointer data)
GCancellable *cancellable, GError **error)
{ {
int pid, stdout[2]; int fd = GPOINTER_TO_INT (data);
if (pipe (stdout) < 0)
/* Move the tempfile fd to 3 (and without the cloexec flag) */
if (dup2 (fd, 3) < 0)
err (1, "dup2");
}
static gboolean
run_dracut (int rootfs_dfd,
char **argv,
int *out_initramfs_tmpfd,
char **out_initramfs_tmppath,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
/* Shell wrapper around dracut to write to the O_TMPFILE fd;
* at some point in the future we should add --fd X instead of -f
* to dracut.
*/
static const char rpmostree_dracut_wrapper_path[] = "usr/bin/rpmostree-dracut-wrapper";
/* This also hardcodes a few arguments */
static const char rpmostree_dracut_wrapper[] =
"#!/usr/bin/bash\n"
"set -euo pipefail\n"
"extra_argv=; if (dracut --help; true) | grep -q -e --reproducible; then extra_argv=\"--reproducible --gzip\"; fi\n"
"dracut $extra_argv -v --add ostree --tmpdir=/tmp -f /tmp/initramfs.img \"$@\"\n"
"cat /tmp/initramfs.img >/proc/self/fd/3\n";
g_autoptr(GPtrArray) child_argv = g_ptr_array_new ();
glnx_fd_close int tmp_fd = -1;
g_autofree char *tmpfile_path = NULL;
/* First tempfile is just our shell script */
if (!glnx_open_tmpfile_linkable_at (rootfs_dfd, "usr/bin",
O_RDWR | O_CLOEXEC,
&tmp_fd, &tmpfile_path,
error))
goto out;
if (glnx_loop_write (tmp_fd, rpmostree_dracut_wrapper, sizeof (rpmostree_dracut_wrapper)) < 0
|| fchmod (tmp_fd, 0755) < 0)
{ {
glnx_set_error_from_errno (error); glnx_set_error_from_errno (error);
return FALSE; goto out;
} }
if (!glnx_link_tmpfile_at (rootfs_dfd, GLNX_LINK_TMPFILE_NOREPLACE,
tmp_fd, tmpfile_path, rootfs_dfd, rpmostree_dracut_wrapper_path,
error))
goto out;
/* We need to close the writable FD now to be able to exec it */
close (tmp_fd); tmp_fd = -1;
pid = fork (); /* Second tempfile is the initramfs contents */
if (pid < 0) if (!glnx_open_tmpfile_linkable_at (rootfs_dfd, "tmp",
{ O_WRONLY | O_CLOEXEC,
close (stdout[0]); &tmp_fd, &tmpfile_path,
close (stdout[1]); error))
glnx_set_error_from_errno (error); goto out;
return FALSE;
}
/* Check that --reproducible is present in the --help output. */ /* Set up argv and run */
if (pid == 0) g_ptr_array_add (child_argv, (char*)glnx_basename (rpmostree_dracut_wrapper_path));
{ for (char **iter = argv; iter && *iter; iter++)
int null; g_ptr_array_add (child_argv, *iter);
char *child_argv[] = { "dracut", "--help", NULL }; g_ptr_array_add (child_argv, NULL);
if (!run_sync_in_root_at (rootfs_dfd, (char*)child_argv->pdata[0], (char**)child_argv->pdata,
dracut_child_setup, GINT_TO_POINTER (tmp_fd),
error))
goto out;
null = open ("/dev/null", O_RDWR); ret = TRUE;
if (null < 0 *out_initramfs_tmpfd = tmp_fd; tmp_fd = -1;
|| close (stdout[0]) < 0 *out_initramfs_tmppath = g_steal_pointer (&tmpfile_path);
|| dup2 (stdout[1], 1) < 0 out:
|| dup2 (null, 0) < 0 if (tmpfile_path != NULL)
|| dup2 (null, 2) < 0) (void) unlink (tmpfile_path);
_exit (1); unlinkat (rootfs_dfd, rpmostree_dracut_wrapper_path, 0);
return ret;
run_sync_in_root_at (rootfs_dfd, "dracut", child_argv, NULL);
_exit (1);
}
else
{
gsize read = 0;
/* the dracut 0.43 --help output is about 8Kb, leave some room. */
const gsize buffer_size = 16384;
g_autofree gchar *buffer = g_new (gchar, buffer_size);
g_autoptr(GInputStream) in = g_unix_input_stream_new (stdout[0], TRUE);
if (close (stdout[1]) < 0)
{
glnx_set_error_from_errno (error);
return FALSE;
}
if (!g_input_stream_read_all (in, buffer, buffer_size, &read,
cancellable, error))
return FALSE;
*supported = g_strstr_len (buffer, read, "--reproducible") != NULL;
return TRUE;
}
} }
static gboolean static gboolean
@ -330,6 +355,8 @@ do_kernel_prep (int rootfs_dfd,
const char *kver = NULL; /* May point to kver_owned */ const char *kver = NULL; /* May point to kver_owned */
g_autofree char *kver_owned = NULL; g_autofree char *kver_owned = NULL;
g_autofree char *bootdir = g_strdup ("boot"); g_autofree char *bootdir = g_strdup ("boot");
glnx_fd_close int initramfs_tmp_fd = -1;
g_autofree char *initramfs_tmp_path = NULL;
if (!find_kernel_and_initramfs_in_bootdir (rootfs_dfd, bootdir, if (!find_kernel_and_initramfs_in_bootdir (rootfs_dfd, bootdir,
&kernel_path, &initramfs_path, &kernel_path, &initramfs_path,
@ -385,7 +412,8 @@ do_kernel_prep (int rootfs_dfd,
{ {
char *child_argv[] = { "depmod", (char*)kver, NULL }; char *child_argv[] = { "depmod", (char*)kver, NULL };
if (!run_sync_in_root_at (rootfs_dfd, "depmod", child_argv, error)) if (!run_sync_in_root_at (rootfs_dfd, "depmod", child_argv,
NULL, NULL, error))
goto out; goto out;
} }
@ -399,24 +427,8 @@ do_kernel_prep (int rootfs_dfd,
goto out; goto out;
{ {
gboolean reproducible;
g_autoptr(GPtrArray) dracut_argv = g_ptr_array_new (); g_autoptr(GPtrArray) dracut_argv = g_ptr_array_new ();
if (!dracut_supports_reproducible (rootfs_dfd, &reproducible, cancellable, error))
goto out;
g_ptr_array_add (dracut_argv, "dracut");
g_ptr_array_add (dracut_argv, "-v");
if (reproducible)
{
g_ptr_array_add (dracut_argv, "--reproducible");
g_ptr_array_add (dracut_argv, "--gzip");
}
g_ptr_array_add (dracut_argv, "--add");
g_ptr_array_add (dracut_argv, "ostree");
g_ptr_array_add (dracut_argv, "--tmpdir=/tmp");
g_ptr_array_add (dracut_argv, "-f");
g_ptr_array_add (dracut_argv, "/var/tmp/initramfs.img");
g_ptr_array_add (dracut_argv, (char*)kver); g_ptr_array_add (dracut_argv, (char*)kver);
if (json_object_has_member (treefile, "initramfs-args")) if (json_object_has_member (treefile, "initramfs-args"))
@ -435,20 +447,21 @@ do_kernel_prep (int rootfs_dfd,
g_ptr_array_add (dracut_argv, (char*)arg); g_ptr_array_add (dracut_argv, (char*)arg);
} }
} }
g_ptr_array_add (dracut_argv, NULL); g_ptr_array_add (dracut_argv, NULL);
if (!run_sync_in_root_at (rootfs_dfd, "dracut", (char**)dracut_argv->pdata, error)) if (!run_dracut (rootfs_dfd, (char**)dracut_argv->pdata,
&initramfs_tmp_fd, &initramfs_tmp_path,
cancellable, error))
goto out; goto out;
} }
{ g_autofree char *initramfs_dest = g_strconcat (bootdir, "/initramfs-", kver, ".img", NULL); { g_autofree char *initramfs_dest = g_strconcat (bootdir, "/initramfs-", kver, ".img", NULL);
if (renameat (rootfs_dfd, "var/tmp/initramfs.img", rootfs_dfd, initramfs_dest) < 0) if (!glnx_link_tmpfile_at (rootfs_dfd, GLNX_LINK_TMPFILE_NOREPLACE,
{ initramfs_tmp_fd, initramfs_tmp_path,
glnx_set_prefix_error_from_errno (error, "Processing initramfs %s", initramfs_path); rootfs_dfd, initramfs_dest,
goto out; error))
} goto out;
g_free (initramfs_path); g_free (initramfs_path);
initramfs_path = g_steal_pointer (&initramfs_dest); initramfs_path = g_steal_pointer (&initramfs_dest);
@ -484,6 +497,8 @@ do_kernel_prep (int rootfs_dfd,
ret = TRUE; ret = TRUE;
out: out:
if (initramfs_tmp_path != NULL)
(void) unlinkat (rootfs_dfd, initramfs_tmp_path, 0);
return ret; return ret;
} }
@ -1828,7 +1843,8 @@ rpmostree_treefile_postprocessing (int rootfs_fd,
{ {
char *child_argv[] = { binpath, NULL }; char *child_argv[] = { binpath, NULL };
if (!run_sync_in_root_at (rootfs_fd, binpath, child_argv, error)) if (!run_sync_in_root_at (rootfs_fd, binpath, child_argv,
NULL, NULL, error))
{ {
g_prefix_error (error, "While executing postprocessing script '%s': ", bn); g_prefix_error (error, "While executing postprocessing script '%s': ", bn);
goto out; goto out;