From 4a37d246b0fac43e535f601cef9b2f24089c7d90 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Fri, 24 Jul 2020 17:35:01 -0400 Subject: [PATCH] Add new `ex initramfs-etc` command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This command allows users to cheaply inject configuration files in the initramfs stage without having to regenerate the whole initramfs (or even a new OSTree commit). This will be useful for configuring services involved in bringing up the root block device. ``` $ echo 'hello world' > /etc/foobar $ rpm-ostree ex initramfs-etc --track /etc/foobar Staging deployment... done Run "systemctl reboot" to start a reboot $ rpm-ostree status State: idle Deployments: ostree://fedora:fedora/x86_64/coreos/testing-devel Version: 32.20200716.dev.1 (2020-07-16T02:47:29Z) Commit: 9a817d75bef81b955179be6e602d1e6ae350645b6323231a62ba2ee6e5b9644b GPGSignature: (unsigned) InitramfsEtc: /etc/foobar ● ostree://fedora:fedora/x86_64/coreos/testing-devel Version: 32.20200716.dev.1 (2020-07-16T02:47:29Z) Commit: 9a817d75bef81b955179be6e602d1e6ae350645b6323231a62ba2ee6e5b9644b GPGSignature: (unsigned) $ reboot (boot into rd.break) sh-5.0# cat /etc/foobar hello world ``` See the libostree side of this at: https://github.com/ostreedev/ostree/pull/2155 Lots more discussions in: https://github.com/coreos/fedora-coreos-tracker/issues/94 --- Makefile-rpm-ostree.am | 1 + configure.ac | 2 +- man/rpm-ostree.xml | 30 +++ packaging/rpm-ostree.spec.in | 2 +- src/app/rpmostree-builtin-ex.c | 2 + src/app/rpmostree-builtin-initramfs-etc.c | 156 +++++++++++++ src/app/rpmostree-builtin-reset.c | 2 +- src/app/rpmostree-builtin-status.c | 7 + src/app/rpmostree-ex-builtins.h | 1 + src/daemon/org.projectatomic.rpmostree1.xml | 9 + src/daemon/rpmostree-sysroot-upgrader.c | 233 +++++++++++++++++++- src/daemon/rpmostreed-deployment-utils.c | 3 + src/daemon/rpmostreed-os.c | 65 +++++- src/daemon/rpmostreed-transaction-types.c | 161 +++++++++++++- src/daemon/rpmostreed-transaction-types.h | 12 + src/libpriv/rpmostree-origin.c | 61 +++++ src/libpriv/rpmostree-origin.h | 17 ++ 17 files changed, 746 insertions(+), 18 deletions(-) create mode 100644 src/app/rpmostree-builtin-initramfs-etc.c diff --git a/Makefile-rpm-ostree.am b/Makefile-rpm-ostree.am index b1cad83481..f25d58227e 100644 --- a/Makefile-rpm-ostree.am +++ b/Makefile-rpm-ostree.am @@ -32,6 +32,7 @@ rpm_ostree_SOURCES = src/app/main.c \ src/app/rpmostree-builtin-cliwrap.c \ src/app/rpmostree-builtin-cleanup.c \ src/app/rpmostree-builtin-initramfs.c \ + src/app/rpmostree-builtin-initramfs-etc.c \ src/app/rpmostree-builtin-livefs.c \ src/app/rpmostree-builtin-usroverlay.c \ src/app/rpmostree-builtin-override.c \ diff --git a/configure.ac b/configure.ac index 4bd611a504..f6542aa084 100644 --- a/configure.ac +++ b/configure.ac @@ -94,7 +94,7 @@ LIBS="$save_LIBS" # Remember to update AM_CPPFLAGS in Makefile.am when bumping GIO req. PKG_CHECK_MODULES(PKGDEP_GIO_UNIX, [gio-unix-2.0]) PKG_CHECK_MODULES(PKGDEP_RPMOSTREE, [gio-unix-2.0 >= 2.50.0 json-glib-1.0 - ostree-1 >= 2020.1 + ostree-1 >= 2020.7 libsystemd polkit-gobject-1 rpm librepo libsolv diff --git a/man/rpm-ostree.xml b/man/rpm-ostree.xml index 10c9088b13..67dd71bf97 100644 --- a/man/rpm-ostree.xml +++ b/man/rpm-ostree.xml @@ -662,6 +662,36 @@ Boston, MA 02111-1307, USA. + + ex initramfs-etc + + + + Experimental feature; subject to change. + + + + Add configuration (/etc) files into the initramfs without + regenerating the entire initramfs. This is useful to be able to configure + services backing the root block device as well as early-boot services like + systemd and journald. + + + + Use --track to start tracking a specific file. Can be + specified multiple times. A new deployment will be generated. Use + --untrack or --untrack-all to stop + tracking files. + + + + When there are tracked files, any future created deployment (e.g. when doing an + upgrade) will ensure that they are synced. You can additionally use + --force-sync to simply generate a new deployment with the + latest versions of tracked files without upgrading. + + + diff --git a/packaging/rpm-ostree.spec.in b/packaging/rpm-ostree.spec.in index a71b46b0cf..ee5dd78759 100644 --- a/packaging/rpm-ostree.spec.in +++ b/packaging/rpm-ostree.spec.in @@ -42,7 +42,7 @@ BuildRequires: gnome-common BuildRequires: /usr/bin/g-ir-scanner # Core requirements # One way to check this: `objdump -p /path/to/rpm-ostree | grep LIBOSTREE` and pick the highest (though that might miss e.g. new struct members) -BuildRequires: pkgconfig(ostree-1) >= 2019.2 +BuildRequires: pkgconfig(ostree-1) >= 2020.7 BuildRequires: pkgconfig(polkit-gobject-1) BuildRequires: pkgconfig(json-glib-1.0) BuildRequires: pkgconfig(rpm) diff --git a/src/app/rpmostree-builtin-ex.c b/src/app/rpmostree-builtin-ex.c index 878697b0b8..af9760af6f 100644 --- a/src/app/rpmostree-builtin-ex.c +++ b/src/app/rpmostree-builtin-ex.c @@ -34,6 +34,8 @@ static RpmOstreeCommand ex_subcommands[] = { "Convert an rpm-ostree rojig into an OSTree commit", rpmostree_ex_builtin_rojig2commit }, { "history", RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD, "Inspect rpm-ostree history of the system", rpmostree_ex_builtin_history }, + { "initramfs-etc", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT, + "Track/untrack initramfs configuration files", rpmostree_ex_builtin_initramfs_etc }, { NULL, 0, NULL, NULL } }; diff --git a/src/app/rpmostree-builtin-initramfs-etc.c b/src/app/rpmostree-builtin-initramfs-etc.c new file mode 100644 index 0000000000..420c454947 --- /dev/null +++ b/src/app/rpmostree-builtin-initramfs-etc.c @@ -0,0 +1,156 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2020 Jonathan Lebon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2 of the licence or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include "rpmostree-ex-builtins.h" +#include "rpmostree-libbuiltin.h" +#include "rpmostree-dbus-helpers.h" + +#include + +static char *opt_osname; +static gboolean opt_force_sync; +static char **opt_track; +static char **opt_untrack; +static gboolean opt_untrack_all; +static gboolean opt_reboot; +static gboolean opt_lock_finalization; +static gboolean opt_unchanged_exit_77; + +static GOptionEntry option_entries[] = { + { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Operate on provided OSNAME", "OSNAME" }, + { "force-sync", 0, 0, G_OPTION_ARG_NONE, &opt_force_sync, "Deploy a new tree with the latest tracked /etc files", NULL }, + { "track", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_track, "Track root /etc file", "FILE" }, + { "untrack", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_untrack, "Untrack root /etc file", "FILE" }, + { "untrack-all", 0, 0, G_OPTION_ARG_NONE, &opt_untrack_all, "Untrack all root /etc files", NULL }, + { "reboot", 'r', 0, G_OPTION_ARG_NONE, &opt_reboot, "Initiate a reboot after operation is complete", NULL }, + { "lock-finalization", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_lock_finalization, "Prevent automatic deployment finalization on shutdown", NULL }, + { "unchanged-exit-77", 0, 0, G_OPTION_ARG_NONE, &opt_unchanged_exit_77, "If no new deployment made, exit 77", NULL }, + + { NULL } +}; + +gboolean +rpmostree_ex_builtin_initramfs_etc (int argc, + char **argv, + RpmOstreeCommandInvocation *invocation, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GOptionContext) context = g_option_context_new (""); + + _cleanup_peer_ GPid peer_pid = 0; + glnx_unref_object RPMOSTreeSysroot *sysroot_proxy = NULL; + if (!rpmostree_option_context_parse (context, + option_entries, + &argc, &argv, + invocation, + cancellable, + NULL, NULL, + &sysroot_proxy, + &peer_pid, + NULL, + error)) + return FALSE; + + glnx_unref_object RPMOSTreeOS *os_proxy = NULL; + if (!rpmostree_load_os_proxy (sysroot_proxy, opt_osname, + cancellable, &os_proxy, error)) + return FALSE; + + g_autoptr(GVariant) previous_deployment = rpmostree_os_dup_default_deployment (os_proxy); + + if (!(opt_track || opt_untrack || opt_untrack_all || opt_force_sync)) + { + if (opt_reboot) + return glnx_throw (error, "Cannot use ---reboot without --track, --untrack, --untrack-all, or --force-sync"); + + g_autofree char **files = NULL; + g_autoptr(GVariant) deployments = rpmostree_sysroot_dup_deployments (sysroot_proxy); + if (g_variant_n_children (deployments) > 0) + { + g_autoptr(GVariant) pending = g_variant_get_child_value (deployments, 0); + g_auto(GVariantDict) dict; + g_variant_dict_init (&dict, pending); + + g_variant_dict_lookup (&dict, "initramfs-etc", "^a&s", &files); + } + + if (!files || !*files) + g_print ("No tracked files.\n"); + else + { + g_print ("Tracked files:\n"); + for (char **it = files; it && *it; it++) + g_print (" %s\n", *it); + } + + return TRUE; /* note early return */ + } + + char *empty_strv[] = {NULL}; + if (!opt_track) + opt_track = empty_strv; + if (!opt_untrack) + opt_untrack = empty_strv; + + GVariantDict dict; + g_variant_dict_init (&dict, NULL); + g_variant_dict_insert (&dict, "reboot", "b", opt_reboot); + g_variant_dict_insert (&dict, "initiating-command-line", "s", invocation->command_line); + g_variant_dict_insert (&dict, "lock-finalization", "b", opt_lock_finalization); + g_autoptr(GVariant) options = g_variant_ref_sink (g_variant_dict_end (&dict)); + + g_autofree char *transaction_address = NULL; + if (!rpmostree_os_call_initramfs_etc_sync (os_proxy, + (const char *const*)opt_track, + (const char *const*)opt_untrack, + opt_untrack_all, + opt_force_sync, + options, + &transaction_address, + cancellable, + error)) + return FALSE; + + if (!rpmostree_transaction_get_response_sync (sysroot_proxy, + transaction_address, + cancellable, + error)) + return FALSE; + + if (!opt_reboot) + { + if (!rpmostree_has_new_default_deployment (os_proxy, previous_deployment)) + { + if (opt_unchanged_exit_77) + invocation->exit_code = RPM_OSTREE_EXIT_UNCHANGED; + return TRUE; + } + + g_print ("Run \"systemctl reboot\" to start a reboot\n"); + } + + return TRUE; +} diff --git a/src/app/rpmostree-builtin-reset.c b/src/app/rpmostree-builtin-reset.c index d962083115..337eeefbee 100644 --- a/src/app/rpmostree-builtin-reset.c +++ b/src/app/rpmostree-builtin-reset.c @@ -42,7 +42,7 @@ static GOptionEntry option_entries[] = { { "reboot", 'r', 0, G_OPTION_ARG_NONE, &opt_reboot, "Initiate a reboot after transaction is complete", NULL }, { "overlays", 'l', 0, G_OPTION_ARG_NONE, &opt_overlays, "Remove all overlayed packages", NULL }, { "overrides", 'o', 0, G_OPTION_ARG_NONE, &opt_overrides, "Remove all overrides", NULL }, - { "initramfs", 'i', 0, G_OPTION_ARG_NONE, &opt_initramfs, "Stop regenerating initramfs", NULL }, + { "initramfs", 'i', 0, G_OPTION_ARG_NONE, &opt_initramfs, "Stop regenerating initramfs or tracking files", NULL }, { "lock-finalization", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_lock_finalization, "Prevent automatic deployment finalization on shutdown", NULL }, { NULL } }; diff --git a/src/app/rpmostree-builtin-status.c b/src/app/rpmostree-builtin-status.c index ce0851a562..c68423dc20 100644 --- a/src/app/rpmostree-builtin-status.c +++ b/src/app/rpmostree-builtin-status.c @@ -870,6 +870,13 @@ print_one_deployment (RPMOSTreeSysroot *sysroot_proxy, g_string_append (buf, "regenerate"); rpmostree_print_kv ("Initramfs", max_key_len, buf->str); } + + g_autofree char **initramfs_etc_files = NULL; + g_variant_dict_lookup (dict, "initramfs-etc", "^a&s", &initramfs_etc_files); + if (initramfs_etc_files && *initramfs_etc_files) + /* XXX: not really packages but it works... should just rename that function */ + print_packages ("InitramfsEtc", max_key_len, (const char**)initramfs_etc_files, NULL); + gboolean pinned = FALSE; g_variant_dict_lookup (dict, "pinned", "b", &pinned); if (pinned) diff --git a/src/app/rpmostree-ex-builtins.h b/src/app/rpmostree-ex-builtins.h index da6271eb12..7e7192a506 100644 --- a/src/app/rpmostree-ex-builtins.h +++ b/src/app/rpmostree-ex-builtins.h @@ -35,6 +35,7 @@ BUILTINPROTO(livefs); BUILTINPROTO(commit2rojig); BUILTINPROTO(rojig2commit); BUILTINPROTO(history); +BUILTINPROTO(initramfs_etc); #undef BUILTINPROTO diff --git a/src/daemon/org.projectatomic.rpmostree1.xml b/src/daemon/org.projectatomic.rpmostree1.xml index 13077d8655..b97eae9a66 100644 --- a/src/daemon/org.projectatomic.rpmostree1.xml +++ b/src/daemon/org.projectatomic.rpmostree1.xml @@ -253,6 +253,15 @@ + + + + + + + + + diff --git a/src/daemon/rpmostree-sysroot-upgrader.c b/src/daemon/rpmostree-sysroot-upgrader.c index 8df2327f5c..7dc47a4221 100644 --- a/src/daemon/rpmostree-sysroot-upgrader.c +++ b/src/daemon/rpmostree-sysroot-upgrader.c @@ -21,6 +21,7 @@ #include "config.h" #include +#include #include #include "rpmostreed-utils.h" #include "rpmostree-util.h" @@ -944,6 +945,14 @@ prep_local_assembly (RpmOstreeSysrootUpgrader *self, { g_assert (!self->ctx); + /* before doing any serious work; do some basic sanity checks that the origin is valid */ + + /* If initramfs regeneration is enabled, it's silly to support /etc overlays on top of + * that. Just point users at dracut's -I instead. I guess we could auto-convert + * ourselves? */ + if (rpmostree_origin_get_regenerate_initramfs (self->origin)) + return glnx_throw (error, "initramfs regeneration and /etc overlay not compatible; use dracut arg -I instead"); + if (!checkout_base_tree (self, cancellable, error)) return FALSE; @@ -1036,6 +1045,186 @@ prep_local_assembly (RpmOstreeSysrootUpgrader *self, return TRUE; } +static gboolean +add_etc_files_recurse (const char *path, + GPtrArray *filelist, + GCancellable *cancellable, + GError **error) +{ + struct stat stbuf; + if (!glnx_fstatat (AT_FDCWD, path, &stbuf, AT_SYMLINK_NOFOLLOW, error)) + return FALSE; + + if (!(S_ISDIR (stbuf.st_mode) || S_ISREG (stbuf.st_mode) || S_ISLNK (stbuf.st_mode))) + return glnx_throw (error, "Invalid file type for %s", path); + + g_ptr_array_add (filelist, g_strdup (path)); + + /* cpio doesn't automatically recurse into directories, so we need to do it */ + if (S_ISDIR (stbuf.st_mode)) + { + g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; + if (!glnx_dirfd_iterator_init_at (AT_FDCWD, path, TRUE, &dfd_iter, error)) + return FALSE; + + while (TRUE) + { + struct dirent *dent = NULL; + if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error)) + return FALSE; + if (!dent) + break; + + g_autofree char *subpath = g_build_filename (path, dent->d_name, NULL); + if (!add_etc_files_recurse (subpath, filelist, cancellable, error)) + return FALSE; + } + } + + return TRUE; +} + +typedef struct { + guint counter; + GMainLoop *loop; + GError **error; +} InitRamfsOverlayData; + +static void +splice_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + InitRamfsOverlayData *data = user_data; + g_autoptr(GError) local_error = NULL; + + (void)g_output_stream_splice_finish ((GOutputStream*)object, result, &local_error); + + /* just keep the first error */ + if (local_error && !*data->error) + *data->error = g_steal_pointer (&local_error); + + data->counter--; + if (data->counter == 0) + g_main_loop_quit (data->loop); +} + +static int +compare_strings (gconstpointer a, + gconstpointer b) +{ + const gchar *sa = *(const gchar **) a; + const gchar *sb = *(const gchar **) b; + return strcmp (sa, sb); +} + +static gboolean +generate_initramfs_overlay (GHashTable *initramfs_etc_files, + GLnxTmpfile *out_tmpf, + GCancellable *cancellable, + GError **error) +{ + + g_autoptr(GMainLoop) loop = g_main_loop_new (g_main_context_get_thread_default (), TRUE); + g_autoptr(GError) local_error = NULL; + InitRamfsOverlayData data = { .loop = loop, .error = &local_error }; + + g_auto(GLnxTmpfile) tmpf = { 0, }; + if (!glnx_open_anonymous_tmpfile (O_RDWR | O_CLOEXEC, &tmpf, error)) + return glnx_prefix_error (error, "Creating tmpfile"); + + g_autoptr(GOutputStream) tmpf_stream = g_unix_output_stream_new (tmpf.fd, FALSE); + + g_autoptr(GPtrArray) filelist = g_ptr_array_new_with_free_func (g_free); + + /* could do this async too... though it really shouldn't be that many files */ + GLNX_HASH_TABLE_FOREACH (initramfs_etc_files, const char*, path) + { + /* should've been checked already */ + g_assert (g_str_has_prefix (path, "/etc/")); + if (!add_etc_files_recurse (path, filelist, cancellable, error)) + return FALSE; + } + + /* feed into the input stream, but sort first to avoid needlessly changing the checksum */ + g_ptr_array_sort (filelist, compare_strings); + g_autoptr(GInputStream) filelist_input = g_memory_input_stream_new (); + + /* keep a hash of the previous path so we don't archive the same file multiple times */ + guint last_path_hash = 0; + for (guint i = 0; i < filelist->len; i++) + { + g_autofree char *path = g_steal_pointer (&filelist->pdata[i]); /* steal in-place */ + guint path_hash = g_str_hash (path); + if (path_hash == last_path_hash) + continue; + g_memory_input_stream_add_data (G_MEMORY_INPUT_STREAM (filelist_input), + g_steal_pointer (&path), (strlen (path))+1, g_free); + last_path_hash = path_hash; + } + + const char *cpio_argv[] = {"cpio", "--create", "--format", "newc", "--quiet", + "--reproducible", "--null", NULL}; + g_autoptr(GSubprocess) cpio = g_subprocess_newv ((const char *const*)cpio_argv, + G_SUBPROCESS_FLAGS_STDIN_PIPE | + G_SUBPROCESS_FLAGS_STDOUT_PIPE, + error); + if (!cpio) + return FALSE; + + const char *gzip_argv[] = {"gzip", "-1", NULL}; + g_autoptr(GSubprocess) gzip = g_subprocess_newv ((const char *const*)gzip_argv, + G_SUBPROCESS_FLAGS_STDIN_PIPE | + G_SUBPROCESS_FLAGS_STDOUT_PIPE, + error); + if (!gzip) + return FALSE; + + GOutputStream *cpio_input = g_subprocess_get_stdin_pipe (cpio); + g_output_stream_splice_async (cpio_input, filelist_input, + G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | + G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, + G_PRIORITY_DEFAULT, cancellable, splice_cb, &data); + data.counter++; + + GInputStream *cpio_output = g_subprocess_get_stdout_pipe (cpio); + GOutputStream *gzip_input = g_subprocess_get_stdin_pipe (gzip); + g_output_stream_splice_async (gzip_input, cpio_output, + G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | + G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, + G_PRIORITY_DEFAULT, cancellable, splice_cb, &data); + data.counter++; + + GInputStream *gzip_output = g_subprocess_get_stdout_pipe (gzip); + g_output_stream_splice_async (tmpf_stream, gzip_output, + G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | + G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, + G_PRIORITY_DEFAULT, cancellable, splice_cb, &data); + data.counter++; + + g_main_loop_run (data.loop); + + const gboolean cpio_success = g_subprocess_wait_check (cpio, cancellable, error); + const gboolean gzip_success = g_subprocess_wait_check (gzip, cancellable, error); + if (local_error) + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + /* stderr is inherited; so it's in the journal */ + if (!cpio_success) + return glnx_throw (error, "cpio failed; check daemon output in journal"); + if (!gzip_success) + return glnx_throw (error, "gzip failed; check daemon output in journal"); + + if (lseek (tmpf.fd, 0, SEEK_SET) < 0) + return glnx_throw_errno_prefix (error, "lseek"); + + *out_tmpf = tmpf; tmpf.initialized = FALSE; /* Transfer */ + + return TRUE; +} + /* Overlay pkgs, run scripts, and commit final rootfs to ostree */ static gboolean perform_local_assembly (RpmOstreeSysrootUpgrader *self, @@ -1403,6 +1592,28 @@ rpmostree_sysroot_upgrader_deploy (RpmOstreeSysrootUpgrader *self, g_autoptr(GKeyFile) origin = rpmostree_origin_dup_keyfile (self->origin); g_autoptr(OstreeDeployment) new_deployment = NULL; + g_autofree char *overlay_initrd_checksum = NULL; + const char *overlay_v[] = { NULL, NULL }; + if (g_hash_table_size (rpmostree_origin_get_initramfs_etc_files (self->origin)) > 0) + { + g_auto(GLnxTmpfile) tmpf = { 0, }; + if (!generate_initramfs_overlay (rpmostree_origin_get_initramfs_etc_files (self->origin), + &tmpf, cancellable, error)) + return glnx_prefix_error (error, "Generating initramfs overlay"); + + if (!ostree_sysroot_stage_overlay_initrd (self->sysroot, tmpf.fd, + &overlay_initrd_checksum, + cancellable, error)) + return glnx_prefix_error (error, "Staging initramfs overlay"); + + overlay_v[0] = overlay_initrd_checksum; + } + + OstreeSysrootDeployTreeOpts opts = { + .override_kernel_argv = self->kargs_strv, + .overlay_initrds = (char**)overlay_v, + }; + if (use_staging) { /* touch file *before* we stage to avoid races */ @@ -1422,22 +1633,20 @@ rpmostree_sysroot_upgrader_deploy (RpmOstreeSysrootUpgrader *self, g_auto(RpmOstreeProgress) task = { 0, }; rpmostree_output_task_begin (&task, "Staging deployment"); - if (!ostree_sysroot_stage_tree (self->sysroot, self->osname, - target_revision, origin, - self->cfg_merge_deployment, - self->kargs_strv, - &new_deployment, - cancellable, error)) + if (!ostree_sysroot_stage_tree_with_options (self->sysroot, self->osname, + target_revision, origin, + self->cfg_merge_deployment, + &opts, &new_deployment, + cancellable, error)) return FALSE; } else { - if (!ostree_sysroot_deploy_tree (self->sysroot, self->osname, - target_revision, origin, - self->cfg_merge_deployment, - self->kargs_strv, - &new_deployment, - cancellable, error)) + if (!ostree_sysroot_deploy_tree_with_options (self->sysroot, self->osname, + target_revision, origin, + self->cfg_merge_deployment, + &opts, &new_deployment, + cancellable, error)) return FALSE; } diff --git a/src/daemon/rpmostreed-deployment-utils.c b/src/daemon/rpmostreed-deployment-utils.c index 1330b3a14a..def733a752 100644 --- a/src/daemon/rpmostreed-deployment-utils.c +++ b/src/daemon/rpmostreed-deployment-utils.c @@ -412,6 +412,9 @@ rpmostreed_deployment_generate_variant (OstreeSysroot *sysroot, g_variant_dict_insert (&dict, "initramfs-args", "^as", args); } + variant_add_from_hash_table (&dict, "initramfs-etc", + rpmostree_origin_get_initramfs_etc_files (origin)); + if (booted_id != NULL) g_variant_dict_insert (&dict, "booted", "b", g_strcmp0 (booted_id, id) == 0); diff --git a/src/daemon/rpmostreed-os.c b/src/daemon/rpmostreed-os.c index 3b7cd6507b..3666f15e49 100644 --- a/src/daemon/rpmostreed-os.c +++ b/src/daemon/rpmostreed-os.c @@ -137,7 +137,8 @@ os_authorize_method (GDBusInterfaceSkeleton *interface, return TRUE; } else if (g_strcmp0 (method_name, "SetInitramfsState") == 0 || - g_strcmp0 (method_name, "KernelArgs") == 0) + g_strcmp0 (method_name, "KernelArgs") == 0 || + g_strcmp0 (method_name, "InitramfsEtc") == 0) { g_ptr_array_add (actions, "org.projectatomic.rpmostree1.bootconfig"); } @@ -1078,6 +1079,67 @@ os_handle_clear_rollback_target (RPMOSTreeOS *interface, return TRUE; } +static gboolean +os_handle_initramfs_etc (RPMOSTreeOS *interface, + GDBusMethodInvocation *invocation, + const char *const* track, + const char *const* untrack, + gboolean untrack_all, + gboolean force_sync, + GVariant *options) +{ + glnx_unref_object OstreeSysroot *ot_sysroot = NULL; + g_autoptr(GCancellable) cancellable = g_cancellable_new (); + const char *osname; + GError *local_error = NULL; + + /* try to merge with an existing transaction, otherwise start a new one */ + glnx_unref_object RpmostreedTransaction *transaction = NULL; + RpmostreedSysroot *rsysroot = rpmostreed_sysroot_get (); + if (!rpmostreed_sysroot_prep_for_txn (rsysroot, invocation, &transaction, &local_error)) + goto out; + if (transaction) + goto out; + + if (!rpmostreed_sysroot_load_state (rpmostreed_sysroot_get (), + cancellable, + &ot_sysroot, + NULL, + &local_error)) + goto out; + + osname = rpmostree_os_get_name (interface); + + transaction = rpmostreed_transaction_new_initramfs_etc (invocation, + ot_sysroot, + osname, + (char**)track, + (char**)untrack, + untrack_all, + force_sync, + options, + cancellable, + &local_error); + if (transaction == NULL) + goto out; + + rpmostreed_sysroot_set_txn (rsysroot, transaction); + +out: + if (local_error != NULL) + { + g_dbus_method_invocation_take_error (invocation, local_error); + } + else + { + const char *client_address; + client_address = rpmostreed_transaction_get_client_address (transaction); + rpmostree_os_complete_initramfs_etc (interface, invocation, client_address); + } + + return TRUE; +} + static gboolean os_handle_set_initramfs_state (RPMOSTreeOS *interface, GDBusMethodInvocation *invocation, @@ -1764,6 +1826,7 @@ rpmostreed_os_iface_init (RPMOSTreeOSIface *iface) iface->handle_refresh_md = os_handle_refresh_md; iface->handle_modify_yum_repo = os_handle_modify_yum_repo; iface->handle_rollback = os_handle_rollback; + iface->handle_initramfs_etc = os_handle_initramfs_etc; iface->handle_set_initramfs_state = os_handle_set_initramfs_state; iface->handle_update_deployment = os_handle_update_deployment; iface->handle_finalize_deployment = os_handle_finalize_deployment; diff --git a/src/daemon/rpmostreed-transaction-types.c b/src/daemon/rpmostreed-transaction-types.c index 648743da53..20767ae455 100644 --- a/src/daemon/rpmostreed-transaction-types.c +++ b/src/daemon/rpmostreed-transaction-types.c @@ -1011,9 +1011,12 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, } gboolean changed = FALSE; - if (no_initramfs && rpmostree_origin_get_regenerate_initramfs (origin)) + GHashTable *initrd_etc_files = rpmostree_origin_get_initramfs_etc_files (origin); + if (no_initramfs && (rpmostree_origin_get_regenerate_initramfs (origin) || + g_hash_table_size (initrd_etc_files) > 0)) { rpmostree_origin_set_regenerate_initramfs (origin, FALSE, NULL); + rpmostree_origin_initramfs_etc_files_untrack_all (origin, NULL); changed = TRUE; } @@ -1694,7 +1697,161 @@ rpmostreed_transaction_new_deploy (GDBusMethodInvocation *invocation, return (RpmostreedTransaction *) g_steal_pointer (&self); } -/* ================================ InitramfsState ================================ */ +/* ================================ InitramfsEtc ================================ */ + +typedef struct { + RpmostreedTransaction parent; + char *osname; + char **track; + char **untrack; + gboolean untrack_all; + gboolean force_sync; + GVariantDict *options; +} InitramfsEtcTransaction; + +typedef RpmostreedTransactionClass InitramfsEtcTransactionClass; + +GType initramfs_etc_transaction_get_type (void); + +G_DEFINE_TYPE (InitramfsEtcTransaction, + initramfs_etc_transaction, + RPMOSTREED_TYPE_TRANSACTION) + +static void +initramfs_etc_transaction_finalize (GObject *object) +{ + InitramfsEtcTransaction *self; + + self = (InitramfsEtcTransaction *) object; + g_free (self->osname); + g_strfreev (self->track); + g_strfreev (self->untrack); + g_clear_pointer (&self->options, g_variant_dict_unref); + + G_OBJECT_CLASS (initramfs_etc_transaction_parent_class)->finalize (object); +} + +static gboolean +initramfs_etc_transaction_execute (RpmostreedTransaction *transaction, + GCancellable *cancellable, + GError **error) +{ + InitramfsEtcTransaction *self = (InitramfsEtcTransaction *) transaction; + OstreeSysroot *sysroot = rpmostreed_transaction_get_sysroot (transaction); + const char *command_line = + vardict_lookup_ptr (self->options, "initiating-command-line", "&s"); + + rpmostree_transaction_set_title ((RPMOSTreeTransaction*)self, command_line ?: "initramfs-etc"); + + RpmOstreeSysrootUpgraderFlags upgrader_flags = 0; + if (vardict_lookup_bool (self->options, "lock-finalization", FALSE)) + upgrader_flags |= RPMOSTREE_SYSROOT_UPGRADER_FLAGS_LOCK_FINALIZATION; + + g_autoptr(RpmOstreeSysrootUpgrader) upgrader = + rpmostree_sysroot_upgrader_new (sysroot, self->osname, upgrader_flags, cancellable, error); + if (upgrader == NULL) + return FALSE; + + g_autoptr(RpmOstreeOrigin) origin = rpmostree_sysroot_upgrader_dup_origin (upgrader); + + gboolean changed = FALSE; + if (self->untrack_all) + { + gboolean subchanged = FALSE; + rpmostree_origin_initramfs_etc_files_untrack_all (origin, &subchanged); + changed = changed || subchanged; + } + else if (self->untrack) + { + gboolean subchanged = FALSE; + rpmostree_origin_initramfs_etc_files_untrack (origin, self->untrack, &subchanged); + changed = changed || subchanged; + } + + if (self->track) + { + gboolean subchanged = FALSE; + rpmostree_origin_initramfs_etc_files_track (origin, self->track, &subchanged); + changed = changed || subchanged; + } + + if (!changed && !self->force_sync) + { + rpmostree_output_message ("No changes."); + return TRUE; /* Note early return */ + } + + GHashTable *files = rpmostree_origin_get_initramfs_etc_files (origin); + GLNX_HASH_TABLE_FOREACH (files, const char*, file) + { + if (!g_str_has_prefix (file, "/etc/")) + return glnx_throw (error, "Path outside /etc forbidden: %s", file); + /* could add more checks here in the future */ + } + + rpmostree_sysroot_upgrader_set_origin (upgrader, origin); + if (!rpmostree_sysroot_upgrader_deploy (upgrader, command_line, NULL, cancellable, error)) + return FALSE; + + if (vardict_lookup_bool (self->options, "reboot", FALSE)) + rpmostreed_reboot (cancellable, error); + + return TRUE; +} + +static void +initramfs_etc_transaction_class_init (InitramfsEtcTransactionClass *class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (class); + object_class->finalize = initramfs_etc_transaction_finalize; + + class->execute = initramfs_etc_transaction_execute; +} + +static void +initramfs_etc_transaction_init (InitramfsEtcTransaction *self) +{ +} + +RpmostreedTransaction * +rpmostreed_transaction_new_initramfs_etc (GDBusMethodInvocation *invocation, + OstreeSysroot *sysroot, + const char *osname, + char **track, + char **untrack, + gboolean untrack_all, + gboolean force_sync, + GVariant *options, + GCancellable *cancellable, + GError **error) +{ + InitramfsEtcTransaction *self; + + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL); + g_return_val_if_fail (OSTREE_IS_SYSROOT (sysroot), NULL); + + self = g_initable_new (initramfs_etc_transaction_get_type (), + cancellable, error, + "invocation", invocation, + "sysroot-path", gs_file_get_path_cached (ostree_sysroot_get_path (sysroot)), + NULL); + + if (self != NULL) + { + self->osname = g_strdup (osname); + self->track = g_strdupv (track); + self->untrack = g_strdupv (untrack); + self->untrack_all = untrack_all; + self->force_sync = force_sync; + self->options = g_variant_dict_new (options); + } + + return (RpmostreedTransaction *) self; +} + +/* ================================ SetInitramfsState ================================ */ typedef struct { RpmostreedTransaction parent; diff --git a/src/daemon/rpmostreed-transaction-types.h b/src/daemon/rpmostreed-transaction-types.h index ebc85feed6..4850831d3e 100644 --- a/src/daemon/rpmostreed-transaction-types.h +++ b/src/daemon/rpmostreed-transaction-types.h @@ -77,6 +77,18 @@ rpmostreed_transaction_new_finalize_deployment (GDBusMethodInvocation *invocatio GCancellable *cancellable, GError **error); +RpmostreedTransaction * +rpmostreed_transaction_new_initramfs_etc (GDBusMethodInvocation *invocation, + OstreeSysroot *sysroot, + const char *osname, + char **track, + char **untrack, + gboolean untrack_all, + gboolean force_sync, + GVariant *options, + GCancellable *cancellable, + GError **error); + RpmostreedTransaction * rpmostreed_transaction_new_initramfs_state (GDBusMethodInvocation *invocation, OstreeSysroot *sysroot, diff --git a/src/libpriv/rpmostree-origin.c b/src/libpriv/rpmostree-origin.c index 26c2e42b90..e2f52aa82b 100644 --- a/src/libpriv/rpmostree-origin.c +++ b/src/libpriv/rpmostree-origin.c @@ -46,6 +46,7 @@ struct RpmOstreeOrigin { char *cached_unconfigured_state; char **cached_initramfs_args; + GHashTable *cached_initramfs_etc_files; /* set of paths */ GHashTable *cached_packages; /* set of reldeps */ GHashTable *cached_local_packages; /* NEVRA --> header sha256 */ /* GHashTable *cached_overrides_replace; XXX: NOT IMPLEMENTED YET */ @@ -111,6 +112,8 @@ rpmostree_origin_parse_keyfile (GKeyFile *origin, g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); ret->cached_overrides_remove = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + ret->cached_initramfs_etc_files = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); ret->cached_unconfigured_state = g_key_file_get_string (ret->kf, "origin", "unconfigured-state", NULL); @@ -161,6 +164,11 @@ rpmostree_origin_parse_keyfile (GKeyFile *origin, ret->cached_overrides_local_replace, error)) return FALSE; + g_auto(GStrv) initramfs_etc_files = + g_key_file_get_string_list (ret->kf, "rpmostree", "initramfs-etc", NULL, NULL); + for (char **f = initramfs_etc_files; f && *f; f++) + g_hash_table_add (ret->cached_initramfs_etc_files, g_steal_pointer (f)); + ret->cached_initramfs_args = g_key_file_get_string_list (ret->kf, "rpmostree", "initramfs-args", NULL, NULL); @@ -314,6 +322,12 @@ rpmostree_origin_get_override_commit (RpmOstreeOrigin *origin) return origin->cached_override_commit; } +GHashTable * +rpmostree_origin_get_initramfs_etc_files (RpmOstreeOrigin *origin) +{ + return origin->cached_initramfs_etc_files; +} + gboolean rpmostree_origin_get_regenerate_initramfs (RpmOstreeOrigin *origin) { @@ -342,6 +356,7 @@ gboolean rpmostree_origin_may_require_local_assembly (RpmOstreeOrigin *origin) { return rpmostree_origin_get_regenerate_initramfs (origin) || + (g_hash_table_size (origin->cached_initramfs_etc_files) > 0) || (g_hash_table_size (origin->cached_packages) > 0) || (g_hash_table_size (origin->cached_local_packages) > 0) || (g_hash_table_size (origin->cached_overrides_local_replace) > 0) || @@ -398,6 +413,7 @@ rpmostree_origin_unref (RpmOstreeOrigin *origin) g_clear_pointer (&origin->cached_local_packages, g_hash_table_unref); g_clear_pointer (&origin->cached_overrides_local_replace, g_hash_table_unref); g_clear_pointer (&origin->cached_overrides_remove, g_hash_table_unref); + g_clear_pointer (&origin->cached_initramfs_etc_files, g_hash_table_unref); g_free (origin); } @@ -411,6 +427,51 @@ update_string_list_from_hash_table (GKeyFile *kf, g_key_file_set_string_list (kf, group, key, (const char *const*)strv, g_strv_length (strv)); } +void +rpmostree_origin_initramfs_etc_files_track (RpmOstreeOrigin *origin, + char **paths, + gboolean *out_changed) +{ + gboolean changed = FALSE; + for (char **path = paths; path && *path; path++) + changed = (g_hash_table_add (origin->cached_initramfs_etc_files, g_strdup (*path)) || changed); + + if (changed) + update_string_list_from_hash_table (origin->kf, "rpmostree", "initramfs-etc", + origin->cached_initramfs_etc_files); + if (out_changed) + *out_changed = changed; +} + +void +rpmostree_origin_initramfs_etc_files_untrack (RpmOstreeOrigin *origin, + char **paths, + gboolean *out_changed) +{ + gboolean changed = FALSE; + for (char **path = paths; path && *path; path++) + changed = (g_hash_table_remove (origin->cached_initramfs_etc_files, *path) || changed); + + if (changed) + update_string_list_from_hash_table (origin->kf, "rpmostree", "initramfs-etc", + origin->cached_initramfs_etc_files); + if (out_changed) + *out_changed = changed; +} + +void +rpmostree_origin_initramfs_etc_files_untrack_all (RpmOstreeOrigin *origin, + gboolean *out_changed) +{ + const gboolean changed = (g_hash_table_size (origin->cached_initramfs_etc_files) > 0); + g_hash_table_remove_all (origin->cached_initramfs_etc_files); + if (changed) + update_string_list_from_hash_table (origin->kf, "rpmostree", "initramfs-etc", + origin->cached_initramfs_etc_files); + if (out_changed) + *out_changed = changed; +} + void rpmostree_origin_set_regenerate_initramfs (RpmOstreeOrigin *origin, gboolean regenerate, diff --git a/src/libpriv/rpmostree-origin.h b/src/libpriv/rpmostree-origin.h index 0513dd7ed9..7bfce503dc 100644 --- a/src/libpriv/rpmostree-origin.h +++ b/src/libpriv/rpmostree-origin.h @@ -95,6 +95,9 @@ rpmostree_origin_get_overrides_local_replace (RpmOstreeOrigin *origin); const char * rpmostree_origin_get_override_commit (RpmOstreeOrigin *origin); +GHashTable * +rpmostree_origin_get_initramfs_etc_files (RpmOstreeOrigin *origin); + gboolean rpmostree_origin_get_regenerate_initramfs (RpmOstreeOrigin *origin); @@ -120,6 +123,20 @@ rpmostree_origin_get_string (RpmOstreeOrigin *origin, GKeyFile * rpmostree_origin_dup_keyfile (RpmOstreeOrigin *origin); +void +rpmostree_origin_initramfs_etc_files_track (RpmOstreeOrigin *origin, + char **paths, + gboolean *out_changed); + +void +rpmostree_origin_initramfs_etc_files_untrack (RpmOstreeOrigin *origin, + char **paths, + gboolean *out_changed); + +void +rpmostree_origin_initramfs_etc_files_untrack_all (RpmOstreeOrigin *origin, + gboolean *out_changed); + void rpmostree_origin_set_regenerate_initramfs (RpmOstreeOrigin *origin, gboolean regenerate,