diff --git a/Makefile-rpm-ostree.am b/Makefile-rpm-ostree.am
index 12505fa2b5..85134041b6 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 a9f91d6f8b..d9d322920b 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 fef120d59f..ac9cde5df6 100644
--- a/src/app/rpmostree-builtin-ex.c
+++ b/src/app/rpmostree-builtin-ex.c
@@ -36,6 +36,8 @@ static RpmOstreeCommand ex_subcommands[] = {
#endif
{ "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 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 ae666c4120..6908571823 100644
--- a/src/app/rpmostree-ex-builtins.h
+++ b/src/app/rpmostree-ex-builtins.h
@@ -37,6 +37,7 @@ BUILTINPROTO(commit2rojig);
BUILTINPROTO(rojig2commit);
#endif
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 c793a5b39b..7b160e456c 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"
@@ -947,6 +948,15 @@ 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) &&
+ g_hash_table_size (rpmostree_origin_get_initramfs_etc_files (self->origin)) > 0)
+ 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;
@@ -1039,6 +1049,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,
@@ -1406,6 +1596,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 */
@@ -1425,22 +1637,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 455f1751f6..25bd5d3354 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 37581f1c08..4df9d99507 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 86260f60e4..d133f09482 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);
@@ -122,6 +125,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,
diff --git a/tests/kolainst/Makefile b/tests/kolainst/Makefile
index 267f7193e8..1a993df461 100644
--- a/tests/kolainst/Makefile
+++ b/tests/kolainst/Makefile
@@ -10,4 +10,6 @@ all:
install:
install -d -m 0755 $(KOLA_TESTDIR)
rsync -prlv ./nondestructive $(KOLA_TESTDIR)/
- rsync -prlv ../common/ $(KOLA_TESTDIR)/nondestructive/data
+ rsync -prlv ./destructive $(KOLA_TESTDIR)/
+ rsync -prlv ../common/*.sh $(KOLA_TESTDIR)/nondestructive/data/
+ rsync -prlv ../common/*.sh $(KOLA_TESTDIR)/destructive/data/
diff --git a/tests/kolainst/destructive/initramfs-etc b/tests/kolainst/destructive/initramfs-etc
new file mode 100755
index 0000000000..82bda07481
--- /dev/null
+++ b/tests/kolainst/destructive/initramfs-etc
@@ -0,0 +1,79 @@
+#!/bin/bash
+set -euo pipefail
+
+. ${KOLA_EXT_DATA}/libtest.sh
+cd $(mktemp -d)
+
+# From https://github.com/ostreedev/ostree/blob/95a6d1514/tests/kolainst/destructive/overlay-initrds.sh#L23
+check_for_dracut_karg() {
+ local karg=$1; shift
+ # https://github.com/dracutdevs/dracut/blob/38ea7e821b/modules.d/98dracut-systemd/dracut-cmdline.sh#L17
+ journalctl -b 0 -t dracut-cmdline \
+ --grep "Using kernel command line parameters:.* ${karg} "
+}
+
+case "${AUTOPKGTEST_REBOOT_MARK:-}" in
+ "")
+ mkdir -p /etc/cmdline.d
+ echo 'foobar' > /etc/cmdline.d/foobar.conf
+
+ rpm-ostree ex initramfs-etc --track /etc/cmdline.d/foobar.conf
+ rpm-ostree status > status.txt
+ assert_file_has_content_literal status.txt "InitramfsEtc: /etc/cmdline.d/foobar.conf"
+ rpm-ostree status --json > status.json
+ assert_jq status.json \
+ '.deployments[0]["initramfs-etc"]|length == 1' \
+ '.deployments[0]["initramfs-etc"][0] == "/etc/cmdline.d/foobar.conf"'
+
+ /tmp/autopkgtest-reboot 1
+ ;;
+ 1)
+ check_for_dracut_karg foobar
+ rpm-ostree ex initramfs-etc --track /etc/cmdline.d/foobar.conf > out.txt
+ assert_file_has_content_literal out.txt "No changes."
+
+ # right now we don't rechecksum all the files so changing the file alone
+ # isn't noticed, but we could in the future
+ echo 'barbaz' > /etc/cmdline.d/foobar.conf
+ rpm-ostree ex initramfs-etc --track /etc/cmdline.d/foobar.conf > out.txt
+ assert_file_has_content_literal out.txt "No changes."
+
+ # but --force-sync should also plow through
+ rpm-ostree ex initramfs-etc --force-sync > out.txt
+ assert_file_has_content_literal out.txt "Staging deployment"
+
+ /tmp/autopkgtest-reboot 2
+ ;;
+ 2)
+ check_for_dracut_karg barbaz
+ if check_for_dracut_karg foobar; then
+ assert_not_reached "Found karg foobar; expected barbaz"
+ fi
+
+ # let's try tracking a whole directory instead
+ echo 'bazboo' > /etc/cmdline.d/bazboo.conf
+ # and for fun, let's use the the locked finalization flow
+ rpm-ostree ex initramfs-etc --lock-finalization \
+ --untrack /etc/cmdline.d/foobar.conf \
+ --track /etc/cmdline.d
+ rpm-ostree status > status.txt
+ assert_file_has_content_literal status.txt "InitramfsEtc: /etc/cmdline.d"
+ rpm-ostree status --json > status.json
+ assert_jq status.json \
+ '.deployments[0]["initramfs-etc"]|length == 1' \
+ '.deployments[0]["initramfs-etc"][0] == "/etc/cmdline.d"'
+
+ /tmp/autopkgtest-reboot-prepare 3
+ rpm-ostree finalize-deployment --allow-missing-checksum
+ ;;
+ 3)
+ check_for_dracut_karg barbaz
+ check_for_dracut_karg bazboo
+
+ # finally, check that passing no args prints the tracked files
+ rpm-ostree ex initramfs-etc > out.txt
+ assert_file_has_content_literal out.txt "Tracked files:"
+ assert_file_has_content_literal out.txt "/etc/cmdline.d"
+ ;;
+ *) echo "unexpected mark: ${AUTOPKGTEST_REBOOT_MARK}"; exit 1;;
+esac