From 915fe7ff58daf368cd65f9f319aac79e5557cfd3 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 28 Feb 2017 17:16:48 -0500 Subject: [PATCH] WIP: livefs But I've been playing with this interactively, and it's at a useful place already. The primary target here is supporting live addition of new packages, while *also* allowing people to do completely arbitrary replacement if that's what they want. Depends: https://github.com/GNOME/libglnx/pull/36 https://github.com/ostreedev/ostree/pull/714 Closes: https://github.com/projectatomic/rpm-ostree/issues/639 --- Makefile-daemon.am | 2 + Makefile-rpm-ostree.am | 1 + Makefile-tests.am | 1 + configure.ac | 3 + src/app/rpmostree-builtin-ex.c | 2 + src/app/rpmostree-builtin-livefs.c | 96 +++ src/app/rpmostree-builtin-status.c | 69 +- src/app/rpmostree-ex-builtins.h | 9 +- src/daemon/org.projectatomic.rpmostree1.xml | 5 + src/daemon/rpmostree-sysroot-core.c | 106 ++- src/daemon/rpmostree-sysroot-core.h | 21 +- src/daemon/rpmostree-sysroot-upgrader.c | 4 +- src/daemon/rpmostreed-deployment-utils.c | 14 + src/daemon/rpmostreed-os-experimental.c | 82 +++ src/daemon/rpmostreed-os.c | 3 +- src/daemon/rpmostreed-transaction-livefs.c | 688 ++++++++++++++++++ src/daemon/rpmostreed-transaction-types.c | 3 + src/daemon/rpmostreed-transaction-types.h | 12 + src/daemon/rpmostreed-utils.c | 2 + .../compose/yum/test-livefs-with-etc.spec | 33 + tests/vmcheck/test-livefs.sh | 61 ++ 21 files changed, 1205 insertions(+), 12 deletions(-) create mode 100644 src/app/rpmostree-builtin-livefs.c create mode 100644 src/daemon/rpmostreed-transaction-livefs.c create mode 100644 tests/common/compose/yum/test-livefs-with-etc.spec create mode 100755 tests/vmcheck/test-livefs.sh diff --git a/Makefile-daemon.am b/Makefile-daemon.am index 9dbfa6f1d9..6a5f24b04f 100644 --- a/Makefile-daemon.am +++ b/Makefile-daemon.am @@ -44,6 +44,7 @@ librpmostreed_la_SOURCES = \ src/daemon/rpmostreed-transaction-monitor.c \ src/daemon/rpmostreed-transaction-types.h \ src/daemon/rpmostreed-transaction-types.c \ + src/daemon/rpmostreed-transaction-livefs.c \ src/daemon/rpmostree-package-variants.h \ src/daemon/rpmostree-package-variants.c \ src/daemon/rpmostreed-os.h \ @@ -56,6 +57,7 @@ librpmostreed_la_CFLAGS = \ $(AM_CFLAGS) \ $(PKGDEP_RPMOSTREE_CFLAGS) \ -DG_LOG_DOMAIN=\"rpm-ostreed\" \ + -D_RPMOSTREE_EXTERN= \ -I$(srcdir)/src/daemon \ -I$(srcdir)/src/lib \ -I$(srcdir)/src/libpriv \ diff --git a/Makefile-rpm-ostree.am b/Makefile-rpm-ostree.am index 10228d16f1..1ecb6d2215 100644 --- a/Makefile-rpm-ostree.am +++ b/Makefile-rpm-ostree.am @@ -30,6 +30,7 @@ rpm_ostree_SOURCES = src/app/main.c \ src/app/rpmostree-builtin-rebase.c \ src/app/rpmostree-builtin-cleanup.c \ src/app/rpmostree-builtin-initramfs.c \ + src/app/rpmostree-builtin-livefs.c \ src/app/rpmostree-pkg-builtins.c \ src/app/rpmostree-builtin-status.c \ src/app/rpmostree-builtin-ex.c \ diff --git a/Makefile-tests.am b/Makefile-tests.am index 6a22a37d24..2465897736 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -30,6 +30,7 @@ testpackages = \ tests/common/compose/yum/repo/packages/x86_64/nonrootcap-1.0-1.x86_64.rpm \ tests/common/compose/yum/repo/packages/x86_64/test-post-rofiles-violation-1.0-1.x86_64.rpm \ tests/common/compose/yum/repo/packages/x86_64/test-opt-1.0-1.x86_64.rpm \ + tests/common/compose/yum/repo/packages/x86_64/test-livefs-with-etc-1.0-1.x86_64.rpm \ $(NULL) # Create a rule for each testpkg with their respective spec file as dep. diff --git a/configure.ac b/configure.ac index 586e29a2c7..371e4d1a1a 100644 --- a/configure.ac +++ b/configure.ac @@ -87,6 +87,9 @@ PKG_CHECK_MODULES(PKGDEP_RPMOSTREE, [gio-unix-2.0 >= 2.40.0 json-glib-1.0 dnl bundled libdnf PKGDEP_RPMOSTREE_CFLAGS="-I $(pwd)/libdnf -I $(pwd)/libdnf-build $PKGDEP_RPMOSTREE_CFLAGS" PKGDEP_RPMOSTREE_LIBS="-L$(pwd)/libdnf-build/libdnf -ldnf $PKGDEP_RPMOSTREE_LIBS" +AC_SEARCH_LIBS([ostree_sysroot_merge_etc], [ostree-1], + AC_DEFINE([BUILDOPT_HAVE_OSTREE_SYSROOT_MERGE_ETC], 1, [x]), + AC_DEFINE([BUILDOPT_HAVE_OSTREE_SYSROOT_MERGE_ETC], 0, [x])) AC_PATH_PROG([XSLTPROC], [xsltproc]) diff --git a/src/app/rpmostree-builtin-ex.c b/src/app/rpmostree-builtin-ex.c index 83b1030cc8..7cf0af6a9b 100644 --- a/src/app/rpmostree-builtin-ex.c +++ b/src/app/rpmostree-builtin-ex.c @@ -25,6 +25,8 @@ #include "rpmostree-rpm-util.h" static RpmOstreeCommand ex_subcommands[] = { + { "livefs", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT, + rpmostree_ex_builtin_livefs }, { "unpack", RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD, rpmostree_ex_builtin_unpack }, { "container", RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD, diff --git a/src/app/rpmostree-builtin-livefs.c b/src/app/rpmostree-builtin-livefs.c new file mode 100644 index 0000000000..06ec42e4eb --- /dev/null +++ b/src/app/rpmostree-builtin-livefs.c @@ -0,0 +1,96 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2017 Colin Walters + * + * 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 gboolean opt_dry_run; +static gboolean opt_replace; + +static GOptionEntry option_entries[] = { + { "dry-run", 'n', 0, G_OPTION_ARG_NONE, &opt_dry_run, "Only perform analysis, do not make changes", NULL }, + { "replace", 0, 0, G_OPTION_ARG_NONE, &opt_replace, "Completely replace all files in /usr", NULL }, + { NULL } +}; + +static GVariant * +get_args_variant (void) +{ + GVariantDict dict; + + g_variant_dict_init (&dict, NULL); + g_variant_dict_insert (&dict, "dry-run", "b", opt_dry_run); + g_variant_dict_insert (&dict, "replace", "b", opt_replace); + + return g_variant_dict_end (&dict); +} + +int +rpmostree_ex_builtin_livefs (int argc, + char **argv, + RpmOstreeCommandInvocation *invocation, + GCancellable *cancellable, + GError **error) +{ + _cleanup_peer_ GPid peer_pid = 0; + glnx_unref_object RPMOSTreeSysroot *sysroot_proxy = NULL; + g_autoptr(GOptionContext) context = g_option_context_new ("- Apply pending deployment changes to booted deployment"); + if (!rpmostree_option_context_parse (context, + option_entries, + &argc, &argv, + invocation, + cancellable, + NULL, NULL, + &sysroot_proxy, + &peer_pid, + error)) + return EXIT_FAILURE; + + glnx_unref_object RPMOSTreeOS *os_proxy = NULL; + glnx_unref_object RPMOSTreeOSExperimental *osexperimental_proxy = NULL; + if (!rpmostree_load_os_proxies (sysroot_proxy, NULL, + cancellable, &os_proxy, + &osexperimental_proxy, error)) + return EXIT_FAILURE; + + g_autofree char *transaction_address = NULL; + if (!rpmostree_osexperimental_call_live_fs_sync (osexperimental_proxy, + get_args_variant (), + &transaction_address, + cancellable, + error)) + return EXIT_FAILURE; + + if (!rpmostree_transaction_get_response_sync (sysroot_proxy, + transaction_address, + cancellable, + error)) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/src/app/rpmostree-builtin-status.c b/src/app/rpmostree-builtin-status.c index ad3933d04e..27f7d079b8 100644 --- a/src/app/rpmostree-builtin-status.c +++ b/src/app/rpmostree-builtin-status.c @@ -153,6 +153,29 @@ status_generic (RPMOSTreeSysroot *sysroot_proxy, const char *red_suffix = is_tty ? "\x1b[22m" : ""; GVariant* txn = get_active_txn (sysroot_proxy); + /* First, gather global state */ + gboolean have_any_live_overlay; + g_variant_iter_init (&iter, deployments); + while (TRUE) + { + g_autoptr(GVariant) child = g_variant_iter_next_value (&iter); + + if (!child) + break; + + g_autoptr(GVariantDict) dict = g_variant_dict_new (child); + + const gchar *live_inprogress; + if (!g_variant_dict_lookup (dict, "live-inprogress", "&s", &live_inprogress)) + live_inprogress = NULL; + const gchar *live_replaced; + if (!g_variant_dict_lookup (dict, "live-replaced", "&s", &live_replaced)) + live_replaced = NULL; + const gboolean have_live_changes = live_inprogress || live_replaced; + + have_any_live_overlay = have_any_live_overlay || have_live_changes; + } + if (txn) { const char *method, *sender, *path; @@ -179,13 +202,16 @@ status_generic (RPMOSTreeSysroot *sysroot_proxy, const gchar *checksum; const gchar *version_string; const gchar *unlocked; + const gchar *live_inprogress; + const gchar *live_replaced; gboolean gpg_enabled; gboolean regenerate_initramfs; guint64 t = 0; int serial; gboolean is_booted; const gboolean was_first = first; - const guint max_key_len = strlen ("PendingBaseVersion"); + /* Add the long keys here */ + const guint max_key_len = MAX (strlen ("PendingBaseVersion"), strlen ("InterruptedLiveCommit")); g_autoptr(GVariant) signatures = NULL; g_autofree char *timestamp_string = NULL; @@ -267,10 +293,45 @@ status_generic (RPMOSTreeSysroot *sysroot_proxy, print_kv ("Timestamp", max_key_len, timestamp_string); } + if (!g_variant_dict_lookup (dict, "live-inprogress", "&s", &live_inprogress)) + live_inprogress = NULL; + if (!g_variant_dict_lookup (dict, "live-replaced", "&s", &live_replaced)) + live_replaced = NULL; + const gboolean have_live_changes = live_inprogress || live_replaced; + if (is_locally_assembled) - print_kv ("BaseCommit", max_key_len, base_checksum); - if (opt_verbose || !is_locally_assembled) - print_kv ("Commit", max_key_len, checksum); + { + if (have_live_changes) + print_kv ("BootedBaseCommit", max_key_len, base_checksum); + else + print_kv ("BaseCommit", max_key_len, base_checksum); + if (opt_verbose || have_any_live_overlay) + print_kv ("Commit", max_key_len, checksum); + } + else + { + if (have_live_changes) + print_kv ("BootedCommit", max_key_len, checksum); + if (!have_live_changes || opt_verbose) + print_kv ("Commit", max_key_len, checksum); + } + + if (live_inprogress) + { + if (is_booted) + g_print ("%s%s", red_prefix, bold_prefix); + print_kv ("InterruptedLiveCommit", max_key_len, live_inprogress); + if (is_booted) + g_print ("%s%s", bold_suffix, red_suffix); + } + if (live_replaced) + { + if (is_booted) + g_print ("%s%s", red_prefix, bold_prefix); + print_kv ("LiveCommit", max_key_len, live_replaced); + if (is_booted) + g_print ("%s%s", bold_suffix, red_suffix); + } /* Show any difference between the baseref vs head, but only for the booted commit, and only if there isn't a pending deployment. Otherwise diff --git a/src/app/rpmostree-ex-builtins.h b/src/app/rpmostree-ex-builtins.h index 9b57006529..792ff613c5 100644 --- a/src/app/rpmostree-ex-builtins.h +++ b/src/app/rpmostree-ex-builtins.h @@ -26,7 +26,14 @@ G_BEGIN_DECLS -gboolean rpmostree_ex_builtin_unpack (int argc, char **argv, RpmOstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error); +#define BUILTINPROTO(name) gboolean rpmostree_ex_builtin_ ## name (int argc, char **argv, \ + RpmOstreeCommandInvocation *invocation, \ + GCancellable *cancellable, GError **error) + +BUILTINPROTO(unpack); +BUILTINPROTO(livefs); + +#undef BUILTINPROTO G_END_DECLS diff --git a/src/daemon/org.projectatomic.rpmostree1.xml b/src/daemon/org.projectatomic.rpmostree1.xml index 235e3a51fe..0a4a915c64 100644 --- a/src/daemon/org.projectatomic.rpmostree1.xml +++ b/src/daemon/org.projectatomic.rpmostree1.xml @@ -264,6 +264,11 @@ + + + + + diff --git a/src/daemon/rpmostree-sysroot-core.c b/src/daemon/rpmostree-sysroot-core.c index 7350723763..f3e8c52f7c 100644 --- a/src/daemon/rpmostree-sysroot-core.c +++ b/src/daemon/rpmostree-sysroot-core.c @@ -26,6 +26,7 @@ #include "rpmostree-util.h" #include "rpmostree-sysroot-upgrader.h" +#include "rpmostree-sysroot-core.h" #include "rpmostree-core.h" #include "rpmostree-origin.h" #include "rpmostree-kernel.h" @@ -333,7 +334,8 @@ GPtrArray * rpmostree_syscore_add_deployment (OstreeSysroot *sysroot, OstreeDeployment *new_deployment, OstreeDeployment *merge_deployment, - gboolean pushing_rollback) + gboolean pushing_rollback, + GError **error) { OstreeDeployment *booted_deployment = NULL; g_autoptr(GPtrArray) deployments = NULL; @@ -343,6 +345,7 @@ rpmostree_syscore_add_deployment (OstreeSysroot *sysroot, gboolean added_new = FALSE; /* Keep track of whether we're looking at a deployment before or after the booted */ gboolean before_booted = TRUE; + gboolean booted_is_live = FALSE; deployments = ostree_sysroot_get_deployments (sysroot); booted_deployment = ostree_sysroot_get_booted_deployment (sysroot); @@ -363,14 +366,23 @@ rpmostree_syscore_add_deployment (OstreeSysroot *sysroot, const gboolean is_last = (i == (deployments->len - 1)); if (is_booted) - before_booted = FALSE; + { + before_booted = FALSE; + if (!rpmostree_syscore_deployment_is_live (sysroot, deployment, -1, + &booted_is_live, error)) + return NULL; + } /* Retain deployment if: * - The deployment is for another osname * - We're pushing a rollback and this is a pending deployment * - It's the merge or booted deployment + * - The booted deployment is live, this is a rollback */ - if (!osname_matches || (pushing_rollback && before_booted) || is_merge_or_booted) + if (!osname_matches + || (pushing_rollback && before_booted) + || is_merge_or_booted + || (!before_booted && booted_is_live)) g_ptr_array_add (new_deployments, g_object_ref (deployment)); /* Insert new rollback right after the booted */ @@ -468,3 +480,91 @@ rpmostree_syscore_write_deployments (OstreeSysroot *sysroot, return TRUE; } + +/* Load the value of an xattr which we expect to be a SHA256 string. + */ +static gboolean +get_checksum_xattr (int fd, const char *xattr, + char **out_value, + GError **error) +{ + char xattr_csum[OSTREE_SHA256_STRING_LEN+1]; + ssize_t len; + + *out_value = NULL; + + if ((len = fgetxattr (fd, xattr, xattr_csum, sizeof (xattr_csum))) < 0) + { + /* Did something happen other than not finding the xattr? */ + if (errno != ENODATA) + return glnx_throw_errno (error); + } + else if (len == OSTREE_SHA256_STRING_LEN) + { + *out_value = g_strndup (xattr_csum, len); + } + else + sd_journal_print (LOG_WARNING, "Unexpected length %" PRIu64 " for %s", + len, xattr); + + return TRUE; +} + +/* Load the checksums that describe the "livefs" state of the given + * deployment. + */ +gboolean +rpmostree_syscore_deployment_get_live (OstreeSysroot *sysroot, + OstreeDeployment *deployment, + int deployment_dfd, + char **out_inprogress_checksum, + char **out_livereplaced_checksum, + GError **error) +{ + g_autofree char *ret_inprogress_checksum = NULL; + g_autofree char *ret_livereplaced_checksum = NULL; + glnx_fd_close int owned_dfd = -1; + + /* Allow providing -1 if the caller doesn't have a fd already */ + if (deployment_dfd == -1) + { + g_autofree char *deployment_path = NULL; + deployment_path = ostree_sysroot_get_deployment_dirpath (sysroot, deployment); + if (!glnx_opendirat (ostree_sysroot_get_fd (sysroot), deployment_path, TRUE, + &owned_dfd, error)) + return FALSE; + deployment_dfd = owned_dfd; + } + + if (!get_checksum_xattr (deployment_dfd, RPMOSTREE_LIVE_INPROGRESS_XATTR, + &ret_inprogress_checksum, error)) + return FALSE; + + if (!get_checksum_xattr (deployment_dfd, RPMOSTREE_LIVE_REPLACED_XATTR, + &ret_livereplaced_checksum, error)) + return FALSE; + + *out_inprogress_checksum = g_steal_pointer (&ret_inprogress_checksum); + *out_livereplaced_checksum = g_steal_pointer (&ret_livereplaced_checksum); + return TRUE; +} + +/* Set @out_is_live to %TRUE if the deployment is live-modified */ +gboolean +rpmostree_syscore_deployment_is_live (OstreeSysroot *sysroot, + OstreeDeployment *deployment, + int deployment_dfd, + gboolean *out_is_live, + GError **error) +{ + g_autofree char *inprogress_checksum = NULL; + g_autofree char *livereplaced_checksum = NULL; + + if (!rpmostree_syscore_deployment_get_live (sysroot, deployment, deployment_dfd, + &inprogress_checksum, &livereplaced_checksum, + error)) + return FALSE; + + *out_is_live = (inprogress_checksum != NULL || livereplaced_checksum != NULL); + return TRUE; +} diff --git a/src/daemon/rpmostree-sysroot-core.h b/src/daemon/rpmostree-sysroot-core.h index 407c9c3100..9649b37cd9 100644 --- a/src/daemon/rpmostree-sysroot-core.h +++ b/src/daemon/rpmostree-sysroot-core.h @@ -37,10 +37,29 @@ OstreeDeployment *rpmostree_syscore_get_origin_merge_deployment (OstreeSysroot * gboolean rpmostree_syscore_bump_mtime (OstreeSysroot *self, GError **error); + + +#define RPMOSTREE_LIVE_INPROGRESS_XATTR "user.rpmostree-live-inprogress" +#define RPMOSTREE_LIVE_REPLACED_XATTR "user.rpmostree-live-replaced" + +gboolean rpmostree_syscore_deployment_get_live (OstreeSysroot *sysroot, + OstreeDeployment *deployment, + int deployment_dfd, + char **out_inprogress_checksum, + char **out_livereplaced_checksum, + GError **error); + +gboolean rpmostree_syscore_deployment_is_live (OstreeSysroot *sysroot, + OstreeDeployment *deployment, + int deployment_dfd, + gboolean *out_is_live, + GError **error); + GPtrArray *rpmostree_syscore_add_deployment (OstreeSysroot *sysroot, OstreeDeployment *new_deployment, OstreeDeployment *merge_deployment, - gboolean pushing_rollback); + gboolean pushing_rollback, + GError **error); GPtrArray *rpmostree_syscore_filter_deployments (OstreeSysroot *sysroot, const char *osname, diff --git a/src/daemon/rpmostree-sysroot-upgrader.c b/src/daemon/rpmostree-sysroot-upgrader.c index 9f26d63da9..4fe7d4fdcd 100644 --- a/src/daemon/rpmostree-sysroot-upgrader.c +++ b/src/daemon/rpmostree-sysroot-upgrader.c @@ -910,7 +910,9 @@ rpmostree_sysroot_upgrader_deploy (RpmOstreeSysrootUpgrader *self, g_autoptr(GPtrArray) new_deployments = rpmostree_syscore_add_deployment (self->sysroot, new_deployment, - self->cfg_merge_deployment, FALSE); + self->cfg_merge_deployment, FALSE, error); + if (!new_deployments) + return FALSE; if (!rpmostree_syscore_write_deployments (self->sysroot, self->repo, new_deployments, cancellable, error)) return FALSE; diff --git a/src/daemon/rpmostreed-deployment-utils.c b/src/daemon/rpmostreed-deployment-utils.c index f05ea3d429..a15455693b 100644 --- a/src/daemon/rpmostreed-deployment-utils.c +++ b/src/daemon/rpmostreed-deployment-utils.c @@ -21,6 +21,8 @@ #include "rpmostreed-deployment-utils.h" #include "rpmostree-origin.h" #include "rpmostree-util.h" +#include "rpmostree-sysroot-core.h" +#include "rpmostreed-utils.h" #include @@ -179,6 +181,8 @@ rpmostreed_deployment_generate_variant (OstreeSysroot *sysroot, gint serial = ostree_deployment_get_deployserial (deployment); gboolean gpg_enabled = FALSE; gboolean is_layered = FALSE; + g_autofree char *live_inprogress = NULL; + g_autofree char *live_replaced = NULL; g_auto(GStrv) layered_pkgs = NULL; if (!ostree_repo_load_variant (repo, @@ -248,6 +252,16 @@ rpmostreed_deployment_generate_variant (OstreeSysroot *sysroot, variant_add_commit_details (&dict, "pending-base-", pending_base_commit); } + if (!rpmostree_syscore_deployment_get_live (sysroot, deployment, -1, + &live_inprogress, &live_replaced, + error)) + return NULL; + + if (live_inprogress) + g_variant_dict_insert (&dict, "live-inprogress", "s", live_inprogress); + if (live_replaced) + g_variant_dict_insert (&dict, "live-replaced", "s", live_replaced); + g_variant_dict_insert (&dict, "origin", "s", refspec); g_autofree char **requested_pkgs = diff --git a/src/daemon/rpmostreed-os-experimental.c b/src/daemon/rpmostreed-os-experimental.c index f69798f5fa..52fa8877b6 100644 --- a/src/daemon/rpmostreed-os-experimental.c +++ b/src/daemon/rpmostreed-os-experimental.c @@ -57,6 +57,23 @@ G_DEFINE_TYPE_WITH_CODE (RpmostreedOSExperimental, rpmostreed_osexperimental_iface_init) ); +static RpmostreedTransaction * +merge_compatible_txn (RpmostreedOSExperimental *self, + GDBusMethodInvocation *invocation) +{ + glnx_unref_object RpmostreedTransaction *transaction = NULL; + + /* If a compatible transaction is in progress, share its bus address. */ + transaction = rpmostreed_transaction_monitor_ref_active_transaction (self->transaction_monitor); + if (transaction != NULL) + { + if (rpmostreed_transaction_is_compatible (transaction, invocation)) + return g_steal_pointer (&transaction); + } + + return NULL; +} + /* ---------------------------------------------------------------------------------------------------- */ static void @@ -119,11 +136,76 @@ osexperimental_handle_moo (RPMOSTreeOSExperimental *interface, rpmostree_osexperimental_complete_moo (interface, invocation, result); return TRUE; } +static RpmOstreeTransactionLiveFsFlags +livefs_flags_from_options (GVariant *options) +{ + RpmOstreeTransactionLiveFsFlags ret = 0; + GVariantDict options_dict; + gboolean opt = FALSE; + + g_variant_dict_init (&options_dict, options); + if (g_variant_dict_lookup (&options_dict, "dry-run", "b", &opt) && opt) + ret |= RPMOSTREE_TRANSACTION_LIVEFS_FLAG_DRY_RUN; + if (g_variant_dict_lookup (&options_dict, "replace", "b", &opt) && opt) + ret |= RPMOSTREE_TRANSACTION_LIVEFS_FLAG_REPLACE; + + g_variant_dict_clear (&options_dict); + + return ret; +} + +static gboolean +osexperimental_handle_live_fs (RPMOSTreeOSExperimental *interface, + GDBusMethodInvocation *invocation, + GVariant *arg_options) +{ + RpmostreedOSExperimental *self = RPMOSTREED_OSEXPERIMENTAL (interface); + glnx_unref_object RpmostreedTransaction *transaction = NULL; + glnx_unref_object OstreeSysroot *ot_sysroot = NULL; + g_autoptr(GCancellable) cancellable = g_cancellable_new (); + GError *local_error = NULL; + + transaction = merge_compatible_txn (self, invocation); + if (transaction) + goto out; + + if (!rpmostreed_sysroot_load_state (rpmostreed_sysroot_get (), + cancellable, + &ot_sysroot, + NULL, + &local_error)) + goto out; + + transaction = rpmostreed_transaction_new_livefs (invocation, + ot_sysroot, + livefs_flags_from_options (arg_options), + cancellable, + &local_error); + if (transaction == NULL) + goto out; + + rpmostreed_transaction_monitor_add (self->transaction_monitor, 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_osexperimental_complete_live_fs (interface, invocation, client_address); + } + + return TRUE; +} static void rpmostreed_osexperimental_iface_init (RPMOSTreeOSExperimentalIface *iface) { iface->handle_moo = osexperimental_handle_moo; + iface->handle_live_fs = osexperimental_handle_live_fs; } /* ---------------------------------------------------------------------------------------------------- */ diff --git a/src/daemon/rpmostreed-os.c b/src/daemon/rpmostreed-os.c index fa62bc86eb..49a40831ab 100644 --- a/src/daemon/rpmostreed-os.c +++ b/src/daemon/rpmostreed-os.c @@ -1222,8 +1222,7 @@ rpmostreed_os_load_internals (RpmostreedOS *self, GError **error) rollback_index = rpmostreed_rollback_deployment_index (name, ot_sysroot, NULL); if (rollback_index >= 0) { - rollback_variant = rpmostreed_deployment_generate_variant (ot_sysroot, - deployments->pdata[rollback_index], booted_id, + rollback_variant = rpmostreed_deployment_generate_variant (ot_sysroot, deployments->pdata[rollback_index], booted_id, ot_repo, error); if (!rollback_variant) return FALSE; diff --git a/src/daemon/rpmostreed-transaction-livefs.c b/src/daemon/rpmostreed-transaction-livefs.c new file mode 100644 index 0000000000..ab37e31b00 --- /dev/null +++ b/src/daemon/rpmostreed-transaction-livefs.c @@ -0,0 +1,688 @@ +/* + * Copyright (C) 2015,2017 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "ostree.h" + +#include +#include +#include +#include + +#include "rpmostreed-transaction-types.h" +#include "rpmostreed-transaction.h" +#include "rpmostreed-deployment-utils.h" +#include "rpmostreed-sysroot.h" +#include "rpmostree-sysroot-core.h" +#include "rpmostree-util.h" +#include "rpmostree-db.h" +#include "rpmostree-output.h" +#include "rpmostree-core.h" +#include "rpmostreed-utils.h" + +#define RPMOSTREE_MESSAGE_LIVEFS_BEGIN SD_ID128_MAKE(30,60,1f,0b,bb,fe,4c,bd,a7,87,23,53,a2,ed,75,81) +#define RPMOSTREE_MESSAGE_LIVEFS_END SD_ID128_MAKE(d6,8a,b4,d9,d1,32,4a,32,8f,f8,c6,24,1c,6e,b3,c3) + +typedef struct { + RpmostreedTransaction parent; + RpmOstreeTransactionLiveFsFlags flags; +} LiveFsTransaction; + +typedef RpmostreedTransactionClass LiveFsTransactionClass; + +GType livefs_transaction_get_type (void); + +G_DEFINE_TYPE (LiveFsTransaction, + livefs_transaction, + RPMOSTREED_TYPE_TRANSACTION) + +static void +livefs_transaction_finalize (GObject *object) +{ + G_GNUC_UNUSED LiveFsTransaction *self; + + self = (LiveFsTransaction *) object; + + G_OBJECT_CLASS (livefs_transaction_parent_class)->finalize (object); +} + +typedef enum { + COMMIT_DIFF_FLAGS_ETC = (1<< 0), /* Change in /usr/etc */ + COMMIT_DIFF_FLAGS_BOOT = (1<< 1), /* Change in /boot */ + COMMIT_DIFF_FLAGS_ROOTFS = (1 << 2), /* Change in / */ + COMMIT_DIFF_FLAGS_REPLACEMENT = (1 << 3) /* Files in /usr were replaced */ +} CommitDiffFlags; + +typedef struct { + guint refcount; + CommitDiffFlags flags; + guint n_usretc; + + char *from; + char *to; + + /* Files */ + GPtrArray *added; /* Set */ + GPtrArray *modified; /* Set */ + GPtrArray *removed; /* Set */ + + /* Package view */ + GPtrArray *removed_pkgs; + GPtrArray *added_pkgs; + GPtrArray *modified_pkgs_old; + GPtrArray *modified_pkgs_new; +} CommitDiff; + +static void +commit_diff_unref (CommitDiff *diff) +{ + diff->refcount--; + if (diff->refcount > 0) + return; + g_free (diff->from); + g_free (diff->to); + g_clear_pointer (&diff->added, g_ptr_array_unref); + g_clear_pointer (&diff->modified, g_ptr_array_unref); + g_clear_pointer (&diff->removed, g_ptr_array_unref); + g_clear_pointer (&diff->removed_pkgs, g_ptr_array_unref); + g_clear_pointer (&diff->added_pkgs, g_ptr_array_unref); + g_clear_pointer (&diff->modified_pkgs_old, g_ptr_array_unref); + g_clear_pointer (&diff->modified_pkgs_new, g_ptr_array_unref); +} +G_DEFINE_AUTOPTR_CLEANUP_FUNC(CommitDiff, commit_diff_unref); + +static gboolean +path_is_boot (const char *path) +{ + return g_str_has_prefix (path, "/boot/") || + g_str_has_prefix (path, "/usr/lib/ostree-boot/"); +} + +static gboolean +path_is_usretc (const char *path) +{ + return g_str_has_prefix (path, "/usr/etc/"); +} + +static gboolean +path_is_rpmdb (const char *path) +{ + return g_str_has_prefix (path, "/usr/share/rpm/"); +} + +static gboolean +path_is_rootfs (const char *path) +{ + return !g_str_has_prefix (path, "/usr/"); +} + +static gboolean +path_is_ignored_for_diff (const char *path) +{ + /* /proc SELinux labeling is broken, ignore it + * https://github.com/ostreedev/ostree/pull/768 + */ + return strcmp (path, "/proc") == 0; +} + +typedef enum { + FILE_DIFF_RESULT_KEEP, + FILE_DIFF_RESULT_OMIT, +} FileDiffResult; + +/* Given a file path, update @diff's global flags which track high level + * modifications, and return whether or not a change to this file should be + * ignored. + */ +static FileDiffResult +diff_one_path (CommitDiff *diff, + const char *path) +{ + if (path_is_ignored_for_diff (path) || + path_is_rpmdb (path)) + return FILE_DIFF_RESULT_OMIT; + else if (path_is_usretc (path)) + { + diff->flags |= COMMIT_DIFF_FLAGS_ETC; + diff->n_usretc++; + } + else if (path_is_boot (path)) + diff->flags |= COMMIT_DIFF_FLAGS_BOOT; + else if (path_is_rootfs (path)) + diff->flags |= COMMIT_DIFF_FLAGS_ROOTFS; + return FILE_DIFF_RESULT_KEEP; +} + +static gboolean +copy_new_config_files (OstreeRepo *repo, + OstreeDeployment *merge_deployment, + int new_deployment_dfd, + OstreeSePolicy *sepolicy, + CommitDiff *diff, + GCancellable *cancellable, + GError **error) +{ + /* Initialize checkout options; we want to make copies, and don't replace any + * existing files. + */ + OstreeRepoCheckoutAtOptions etc_co_opts = { .force_copy = TRUE, + .overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES }; + /* Use SELinux policy if it's initialized */ + if (ostree_sepolicy_get_name (sepolicy) != NULL) + etc_co_opts.sepolicy = sepolicy; + + guint n_added = 0; + for (guint i = 0; i < diff->added->len; i++) + { + GFile *added_f = diff->added->pdata[i]; + const char *path = gs_file_get_path_cached (added_f); + if (!g_str_has_prefix (path, "/usr/etc/")) + continue; + + etc_co_opts.subpath = path; + /* Strip off /usr for selinux labeling */ + etc_co_opts.sepolicy_prefix = path + strlen ("/usr"); + + if (!ostree_repo_checkout_at (repo, &etc_co_opts, + new_deployment_dfd, "etc", + ostree_deployment_get_csum (merge_deployment), + cancellable, error)) + return g_prefix_error (error, "Copying %s: ", path), FALSE; + n_added++; + } + g_print ("Copied %u new config files\n", n_added); + return TRUE; +} + +/* Generate a CommitDiff */ +static gboolean +analyze_commit_diff (OstreeRepo *repo, + const char *from_rev, + const char *to_rev, + CommitDiff **out_diff, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(CommitDiff) diff = g_new0(CommitDiff, 1); + diff->refcount = 1; + + diff->from = g_strdup (from_rev); + diff->to = g_strdup (to_rev); + + /* Read the "from" and "to" commits */ + glnx_unref_object GFile *from_tree = NULL; + if (!ostree_repo_read_commit (repo, from_rev, &from_tree, NULL, + cancellable, error)) + return FALSE; + glnx_unref_object GFile *to_tree = NULL; + if (!ostree_repo_read_commit (repo, to_rev, &to_tree, NULL, + cancellable, error)) + return FALSE; + + /* Diff the two commits at the filesystem level */ + g_autoptr(GPtrArray) modified = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_diff_item_unref); + g_autoptr(GPtrArray) removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + g_autoptr(GPtrArray) added = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + if (!ostree_diff_dirs (0, from_tree, to_tree, modified, removed, added, + cancellable, error)) + return FALSE; + + /* We'll filter these arrays below. */ + diff->modified = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + diff->removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + diff->added = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + + /* Analyze the differences */ + for (guint i = 0; i < modified->len; i++) + { + OstreeDiffItem *diffitem = modified->pdata[i]; + const char *path = gs_file_get_path_cached (diffitem->src); + + if (diff_one_path (diff, path) == FILE_DIFF_RESULT_KEEP) + g_ptr_array_add (diff->modified, g_object_ref (diffitem->src)); + } + + for (guint i = 0; i < removed->len; i++) + { + GFile *gfpath = removed->pdata[i]; + const char *path = gs_file_get_path_cached (gfpath); + + if (diff_one_path (diff, path) == FILE_DIFF_RESULT_KEEP) + g_ptr_array_add (diff->removed, g_object_ref (gfpath)); + } + + for (guint i = 0; i < added->len; i++) + { + GFile *added_f = added->pdata[i]; + const char *path = gs_file_get_path_cached (added_f); + + if (diff_one_path (diff, path) == FILE_DIFF_RESULT_KEEP) + g_ptr_array_add (diff->added, g_object_ref (added_f)); + } + + /* And gather the RPM level changes */ + if (!rpm_ostree_db_diff (repo, from_rev, to_rev, + &diff->removed_pkgs, &diff->added_pkgs, + &diff->modified_pkgs_old, &diff->modified_pkgs_new, + cancellable, error)) + return FALSE; + g_assert (diff->modified_pkgs_old->len == diff->modified_pkgs_new->len); + + *out_diff = g_steal_pointer (&diff); + return TRUE; +} + +static void +print_commit_diff (CommitDiff *diff) +{ + /* Print out the results of the two diffs */ + g_print ("Diff Analysis: %s => %s\n", diff->from, diff->to); + g_print ("Files:\n modified: %u\n removed: %u\n added: %u\n", + diff->modified->len, diff->removed->len, diff->added->len); + g_print ("Packages:\n modified: %u\n removed: %u\n added: %u\n", + diff->modified_pkgs_new->len, diff->removed_pkgs->len, diff->added_pkgs->len); + + if (diff->flags & COMMIT_DIFF_FLAGS_ETC) + { + g_print ("* Configuration changed in /etc\n"); + } + if (diff->flags & COMMIT_DIFF_FLAGS_ROOTFS) + { + g_print ("* Content outside of /usr and /etc is modified\n"); + } + if (diff->flags & COMMIT_DIFF_FLAGS_BOOT) + { + g_print ("* Kernel/initramfs changed\n"); + } + fflush (stdout); +} + +/* We want to ensure the rollback deployment matches our booted checksum. If it + * doesn't, we'll push a new one, and GC the previous one(s). + */ +static OstreeDeployment * +get_rollback_deployment (OstreeSysroot *self, + OstreeDeployment *booted) +{ + const char *osname = ostree_deployment_get_osname (booted); + int booted_idx = ostree_deployment_get_index (booted); + g_autoptr(GPtrArray) deployments = ostree_sysroot_get_deployments (self); + const char *booted_csum = ostree_deployment_get_csum (booted); + guint i; + + for (i = 0; i < deployments->len; i++) + { + OstreeDeployment *deployment = deployments->pdata[i]; + int idx = ostree_deployment_get_index (deployment); + + /* Is this for another osname? Skip it. */ + if (strcmp (ostree_deployment_get_osname (deployment), osname) != 0) + continue; + + /* Before or at booted? Skip. */ + if (idx <= booted_idx) + continue; + + /* Does it match our checksum? */ + const char *csum = ostree_deployment_get_csum (deployment); + if (strcmp (csum, booted_csum) == 0) + return g_object_ref (deployment); + } + + return NULL; +} + +static gboolean +prepare_rollback_deployment (OstreeSysroot *sysroot, + OstreeRepo *repo, + OstreeDeployment *booted_deployment, + GCancellable *cancellable, + GError **error) +{ + glnx_unref_object OstreeDeployment *new_deployment = NULL; + OstreeBootconfigParser *original_bootconfig = ostree_deployment_get_bootconfig (booted_deployment); + glnx_unref_object OstreeBootconfigParser *new_bootconfig = ostree_bootconfig_parser_clone (original_bootconfig); + + /* Ensure we have a clean slate */ + if (!ostree_sysroot_prepare_cleanup (sysroot, cancellable, error)) + return g_prefix_error (error, "Performing initial cleanup: "), FALSE; + + g_print ("Preparing new rollback matching currently booted deployment\n"); + + if (!ostree_sysroot_deploy_tree (sysroot, + ostree_deployment_get_osname (booted_deployment), + ostree_deployment_get_csum (booted_deployment), + ostree_deployment_get_origin (booted_deployment), + booted_deployment, + NULL, + &new_deployment, + cancellable, error)) + return FALSE; + + /* Inherit kernel arguments */ + ostree_deployment_set_bootconfig (new_deployment, new_bootconfig); + + g_autoptr(GPtrArray) new_deployments = + rpmostree_syscore_add_deployment (sysroot, new_deployment, booted_deployment, TRUE, error); + if (!new_deployments) + return FALSE; + if (!rpmostree_syscore_write_deployments (sysroot, repo, new_deployments, + cancellable, error)) + return FALSE; + + return TRUE; +} + +static gboolean +checkout_add_usr (OstreeRepo *repo, + int deployment_dfd, + CommitDiff *diff, + const char *target_csum, + GCancellable *cancellable, + GError **error) +{ + OstreeRepoCheckoutAtOptions usr_checkout_opts = { .mode = OSTREE_REPO_CHECKOUT_MODE_NONE, + .overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES, + .no_copy_fallback = TRUE, + .subpath = "/usr" }; + + if (!ostree_repo_checkout_at (repo, &usr_checkout_opts, deployment_dfd, "usr", + target_csum, cancellable, error)) + return FALSE; + + return TRUE; +} + +static gboolean +livefs_transaction_execute_inner (LiveFsTransaction *self, + OstreeSysroot *sysroot, + GCancellable *cancellable, + GError **error) +{ + static const char orig_rpmdb_path[] = "usr/share/rpm.rpmostree-orig"; + + /* Initial setup - load sysroot, repo, and booted deployment */ + glnx_unref_object OstreeRepo *repo = NULL; + if (!ostree_sysroot_get_repo (sysroot, &repo, cancellable, error)) + return FALSE; + OstreeDeployment *booted_deployment = ostree_sysroot_get_booted_deployment (sysroot); + if (!booted_deployment) + return glnx_throw (error, "Not currently booted into an OSTree system"); + + /* Overlayfs doesn't support mutation of the lowerdir. And broadly speaking, + * our medium term goal here is to obviate most of the unlock usage. + */ + OstreeDeploymentUnlockedState unlockstate = ostree_deployment_get_unlocked (booted_deployment); + if (unlockstate != OSTREE_DEPLOYMENT_UNLOCKED_NONE) + return glnx_throw (error, "livefs is incompatible with unlocked state"); + + /* Find the source for /etc - either booted or pending, but down below we + require pending */ + OstreeDeployment *origin_merge_deployment = + rpmostree_syscore_get_origin_merge_deployment (sysroot, + ostree_deployment_get_osname (booted_deployment)); + g_assert (origin_merge_deployment); + const char *booted_csum = ostree_deployment_get_csum (booted_deployment); + const char *target_csum = ostree_deployment_get_csum (origin_merge_deployment); + + /* Require a pending deployment to use as a source - perhaps in the future we + * handle direct live overlays. + */ + if (origin_merge_deployment == booted_deployment) + return glnx_throw (error, "No pending deployment"); + + /* Open a fd for the booted deployment */ + g_autofree char *deployment_path = ostree_sysroot_get_deployment_dirpath (sysroot, booted_deployment); + glnx_fd_close int deployment_dfd = -1; + if (!glnx_opendirat (ostree_sysroot_get_fd (sysroot), deployment_path, TRUE, + &deployment_dfd, error)) + return FALSE; + + /* Find out whether we already have a live overlay */ + g_autofree char *live_inprogress = NULL; + g_autofree char *live_replaced = NULL; + if (!rpmostree_syscore_deployment_get_live (sysroot, booted_deployment, -1, + &live_inprogress, &live_replaced, + error)) + return FALSE; + const char *resuming_overlay = NULL; + if (live_inprogress != NULL) + { + if (strcmp (live_inprogress, target_csum) == 0) + resuming_overlay = target_csum; + } + const char *replacing_overlay = NULL; + if (live_replaced != NULL) + { + if (strcmp (live_replaced, target_csum) == 0) + return glnx_throw (error, "Current overlay is already %s", target_csum); + replacing_overlay = live_replaced; + } + + if (resuming_overlay) + g_print ("Note: Resuming interrupted overlay of %s\n", target_csum); + if (replacing_overlay) + g_print ("Note: Previous overlay: %s\n", replacing_overlay); + + /* Look at the difference between the two commits - we could also walk the + * filesystem, but doing it at the ostree level is potentially faster, since + * we know when two directories are the same. + */ + g_autoptr(CommitDiff) diff = NULL; + if (!analyze_commit_diff (repo, booted_csum, target_csum, + &diff, cancellable, error)) + return FALSE; + + print_commit_diff (diff); + + const gboolean replacing = (self->flags & RPMOSTREE_TRANSACTION_LIVEFS_FLAG_REPLACE) > 0; + const gboolean requires_etc_merge = (diff->flags & COMMIT_DIFF_FLAGS_ETC) > 0; + const gboolean adds_packages = diff->added_pkgs->len > 0; + const gboolean modifies_packages = diff->removed_pkgs->len > 0 || diff->modified_pkgs_new->len > 0; + if (!adds_packages) + return glnx_throw (error, "No packages added; live updates not currently supported for modifications or deletions"); + /* Is this a dry run? */ + if ((self->flags & RPMOSTREE_TRANSACTION_LIVEFS_FLAG_DRY_RUN) > 0) + /* Note early return */ + return TRUE; + /* Error out in various cases if we're not doing a replacement */ + if (!replacing) + { + if (modifies_packages) + return glnx_throw (error, "livefs update modifies/replaces packages"); + else if ((diff->flags & COMMIT_DIFF_FLAGS_REPLACEMENT) > 0) + return glnx_throw (error, "livefs update would replace files in /usr, and replacement not enabled"); + else if ((diff->flags & COMMIT_DIFF_FLAGS_ROOTFS) > 0) + return glnx_throw (error, "livefs update would modify non-/usr content"); + } + + g_autoptr(GString) journal_msg = g_string_new (""); + g_string_append_printf (journal_msg, "Starting livefs for commit %s", target_csum); + if (resuming_overlay) + g_string_append (journal_msg, " (resuming)"); + if (!replacing) + g_string_append_printf (journal_msg, " addition; %u pkgs, %u files", + diff->added_pkgs->len, diff->added->len); + else + g_string_append_printf (journal_msg, " replacement; %u/%u/%u pkgs (added, removed, modified); %u/%u/%u files", + diff->added_pkgs->len, diff->removed_pkgs->len, diff->modified_pkgs_old->len, + diff->added->len, diff->removed->len, diff->modified->len); + if (replacing_overlay) + g_string_append_printf (journal_msg, "; replacing %s", replacing_overlay); + sd_journal_send ("MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(RPMOSTREE_MESSAGE_LIVEFS_BEGIN), + "MESSAGE=%s", journal_msg->str, + "RESUMING=%s", resuming_overlay ?: "", + "REPLACING=%s", replacing_overlay ?: "", + "BOOTED_COMMIT=%s", booted_csum, + "TARGET_COMMIT=%s", target_csum, + NULL); + g_string_truncate (journal_msg, 0); + + /* Ensure that we have a rollback deployment that matches our booted checksum, + * so that if something goes wrong, the user can get to it. If we have an + * older rollback, that gets GC'd. + */ + OstreeDeployment *rollback_deployment = get_rollback_deployment (sysroot, booted_deployment); + if (!rollback_deployment) + { + if (!prepare_rollback_deployment (sysroot, repo, booted_deployment, cancellable, error)) + return g_prefix_error (error, "Preparing rollback: "), FALSE; + } + + /* Reload this, the sysroot may have changed it */ + booted_deployment = ostree_sysroot_get_booted_deployment (sysroot); + + /* Load SELinux policy for making changes to /etc */ + g_autoptr(OstreeSePolicy) sepolicy = ostree_sepolicy_new_at (deployment_dfd, cancellable, error); + if (!sepolicy) + return FALSE; + + /* Crack open our booted root */ + if (!ostree_sysroot_deployment_set_mutable (sysroot, booted_deployment, TRUE, + cancellable, error)) + return g_prefix_error (error, "Setting deployment mutable: "), FALSE; + /* This xattr says which commit we're in the process of overlaying */ + if (fsetxattr (deployment_dfd, RPMOSTREE_LIVE_INPROGRESS_XATTR, target_csum, OSTREE_SHA256_STRING_LEN, 0) < 0) + return glnx_throw_errno_prefix (error, "Setting %s", RPMOSTREE_LIVE_INPROGRESS_XATTR); + + rpmostree_output_task_begin ("Overlaying /usr"); + + if (!checkout_add_usr (repo, deployment_dfd, diff, target_csum, cancellable, error)) + return FALSE; + + /* Start replacing the rpmdb. First, ensure the temporary dir for the new + version doesn't exist */ + if (!glnx_shutil_rm_rf_at (deployment_dfd, orig_rpmdb_path, cancellable, error)) + return FALSE; + /* Check out the new rpmdb */ + { OstreeRepoCheckoutAtOptions rpmdb_checkout_opts = { .mode = OSTREE_REPO_CHECKOUT_MODE_NONE, + .no_copy_fallback = TRUE, + .subpath = "/usr/share/rpm" }; + + + if (!ostree_repo_checkout_at (repo, &rpmdb_checkout_opts, deployment_dfd, orig_rpmdb_path, + target_csum, cancellable, error)) + return FALSE; + } + /* Now, RENAME_EXCHANGE the two */ + if (glnx_renameat2_exchange (deployment_dfd, "usr/share/rpm", deployment_dfd, orig_rpmdb_path) < 0) + return glnx_throw_errno_prefix (error, "%s", "rename(..., RENAME_EXCHANGE) for rpmdb"); + /* And nuke the old one */ + if (!glnx_shutil_rm_rf_at (deployment_dfd, orig_rpmdb_path, cancellable, error)) + return FALSE; + + if (requires_etc_merge) + { + if (!copy_new_config_files (repo, origin_merge_deployment, + deployment_dfd, sepolicy, diff, + cancellable, error)) + return FALSE; + } + + /* Mark the current deployment as having completed a live overlay */ + if (fsetxattr (deployment_dfd, RPMOSTREE_LIVE_REPLACED_XATTR, target_csum, OSTREE_SHA256_STRING_LEN, 0) < 0) + return glnx_throw_errno_prefix (error, "Setting %s", RPMOSTREE_LIVE_REPLACED_XATTR); + + /* And remove the in-progress xattr */ + if (fremovexattr (deployment_dfd, RPMOSTREE_LIVE_INPROGRESS_XATTR) < 0) + { + /* If this somehow happens...hm, well, let's ignore it */ + if (errno == ENODATA) + sd_journal_print (LOG_WARNING, "Got ENODATA removing xattr '%s'?", RPMOSTREE_LIVE_INPROGRESS_XATTR); + else + return glnx_throw_errno (error); + } + + /* Seal the root back up again */ + if (!ostree_sysroot_deployment_set_mutable (sysroot, booted_deployment, FALSE, + cancellable, error)) + return g_prefix_error (error, "Setting deployment mutable: "), FALSE; + + sd_journal_send ("MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(RPMOSTREE_MESSAGE_LIVEFS_END), + "MESSAGE=Completed livefs for commit %s", target_csum, + "BOOTED_COMMIT=%s", booted_csum, + "TARGET_COMMIT=%s", target_csum, + NULL); + + rpmostree_output_task_end ("done"); + + return TRUE; +} + +static gboolean +livefs_transaction_execute (RpmostreedTransaction *transaction, + GCancellable *cancellable, + GError **error) +{ + LiveFsTransaction *self = (LiveFsTransaction *) transaction; + OstreeSysroot *sysroot = rpmostreed_transaction_get_sysroot (transaction); + + /* Run the transaction */ + gboolean ret = livefs_transaction_execute_inner (self, sysroot, cancellable, error); + + /* We use this to notify ourselves of changes, which is a bit silly, but it + * keeps things consistent if `ostree admin` is invoked directly. Always + * invoke it, in case we error out, so that we correctly update for the + * partial state. + */ + (void) rpmostree_syscore_bump_mtime (sysroot, NULL); + return ret; +} + + +static void +livefs_transaction_class_init (LiveFsTransactionClass *class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (class); + object_class->finalize = livefs_transaction_finalize; + + class->execute = livefs_transaction_execute; +} + +static void +livefs_transaction_init (LiveFsTransaction *self) +{ +} + +RpmostreedTransaction * +rpmostreed_transaction_new_livefs (GDBusMethodInvocation *invocation, + OstreeSysroot *sysroot, + RpmOstreeTransactionLiveFsFlags flags, + GCancellable *cancellable, + GError **error) +{ + LiveFsTransaction *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 (livefs_transaction_get_type (), + cancellable, error, + "invocation", invocation, + "sysroot-path", gs_file_get_path_cached (ostree_sysroot_get_path (sysroot)), + NULL); + + if (self != NULL) + { + self->flags = flags; + } + + return (RpmostreedTransaction *) self; +} diff --git a/src/daemon/rpmostreed-transaction-types.c b/src/daemon/rpmostreed-transaction-types.c index d512529fec..ec2add0b22 100644 --- a/src/daemon/rpmostreed-transaction-types.c +++ b/src/daemon/rpmostreed-transaction-types.c @@ -28,6 +28,7 @@ #include "rpmostree-sysroot-upgrader.h" #include "rpmostree-sysroot-core.h" #include "rpmostree-util.h" +#include "rpmostree-output.h" #include "rpmostree-core.h" #include "rpmostree-unpacker.h" #include "rpmostreed-utils.h" @@ -1106,6 +1107,8 @@ cleanup_transaction_execute (RpmostreedTransaction *transaction, rpmostree_syscore_filter_deployments (sysroot, self->osname, cleanup_pending, cleanup_rollback); + return FALSE; + if (new_deployments) { OstreeSysrootWriteDeploymentsOpts write_opts = { .do_postclean = FALSE }; diff --git a/src/daemon/rpmostreed-transaction-types.h b/src/daemon/rpmostreed-transaction-types.h index c6822fc984..2da69c7e23 100644 --- a/src/daemon/rpmostreed-transaction-types.h +++ b/src/daemon/rpmostreed-transaction-types.h @@ -94,3 +94,15 @@ rpmostreed_transaction_new_cleanup (GDBusMethodInvocation *invocation, RpmOstreeTransactionCleanupFlags flags, GCancellable *cancellable, GError **error); + +typedef enum { + RPMOSTREE_TRANSACTION_LIVEFS_FLAG_DRY_RUN = (1 << 0), + RPMOSTREE_TRANSACTION_LIVEFS_FLAG_REPLACE = (1 << 1), +} RpmOstreeTransactionLiveFsFlags; + +RpmostreedTransaction * +rpmostreed_transaction_new_livefs (GDBusMethodInvocation *invocation, + OstreeSysroot *sysroot, + RpmOstreeTransactionLiveFsFlags flags, + GCancellable *cancellable, + GError **error); diff --git a/src/daemon/rpmostreed-utils.c b/src/daemon/rpmostreed-utils.c index ecd26935d4..2cf7006804 100644 --- a/src/daemon/rpmostreed-utils.c +++ b/src/daemon/rpmostreed-utils.c @@ -21,6 +21,8 @@ #include "rpmostreed-utils.h" #include "rpmostreed-errors.h" #include "libglnx.h" +#include +#include #include diff --git a/tests/common/compose/yum/test-livefs-with-etc.spec b/tests/common/compose/yum/test-livefs-with-etc.spec new file mode 100644 index 0000000000..fb22a4e0c9 --- /dev/null +++ b/tests/common/compose/yum/test-livefs-with-etc.spec @@ -0,0 +1,33 @@ +Name: test-livefs-with-etc +Summary: %{name} +Version: 1.0 +Release: 1 +License: GPL+ +Group: Development/Tools +URL: http://foo.bar.com +BuildArch: x86_64 + +%description +%{summary} + +%prep + +%build +cat > %{name} << EOF +#!/bin/sh +echo "livefs-with-etc" +EOF +chmod a+x %{name} +cat > %{name}.conf < livefs-analysis.txt +assert_file_has_content livefs-analysis.txt 'livefs would be safe' + +vm_rpmostree ex livefs +vm_cmd rpm -q foo > rpmq.txt +assert_file_has_content rpmq.txt foo-1.0-1 + +echo "ok livefs stage1" + +vm_rpmostree install /tmp/vmcheck/repo/packages/x86_64/test-livefs-with-etc-1.0-1.x86_64.rpm +vm_rpmostree ex livefs -n > livefs-analysis.txt +assert_file_has_content livefs-analysis.txt 'livefs would be safe' +vm_rpmostree ex livefs +vm_cmd rpm -q foo test-livefs-with-etc > rpmq.txt +assert_file_has_content rpmq.txt foo-1.0-1 test-livefs-with-etc-1.0-1 +vm_has_files /etc/foo.conf + +echo "ok livefs stage2" + +if vm_rpmostree cleanup -r 2>err.txt; then + assert_not_reached "cleaned up rollback?" +fi +assert_file_has_content err.txt 'refusing cleanup of rollback deployment' + +echo "ok livefs rollback"