From 3018e3d4c2333533709a17bb533256c42eaf48d4 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-decls.am | 3 + Makefile-rpm-ostree.am | 1 + Makefile-tests.am | 2 - libglnx | 2 +- src/app/rpmostree-builtin-ex.c | 2 + src/app/rpmostree-builtin-livefs.c | 101 ++++ src/app/rpmostree-builtin-status.c | 31 +- src/app/rpmostree-ex-builtins.h | 9 +- src/daemon/org.projectatomic.rpmostree1.xml | 5 + src/daemon/rpmostree-sysroot-upgrader.c | 53 +- src/daemon/rpmostreed-deployment-utils.c | 16 +- src/daemon/rpmostreed-deployment-utils.h | 5 +- src/daemon/rpmostreed-os.c | 77 ++- src/daemon/rpmostreed-sysroot.c | 2 +- src/daemon/rpmostreed-transaction-livefs.c | 525 ++++++++++++++++++++ src/daemon/rpmostreed-transaction-types.c | 4 + src/daemon/rpmostreed-transaction-types.h | 13 + src/daemon/rpmostreed-utils.c | 179 +++++++ src/daemon/rpmostreed-utils.h | 23 + tests/vmcheck/test-livefs.sh | 44 ++ 21 files changed, 1043 insertions(+), 56 deletions(-) create mode 100644 src/app/rpmostree-builtin-livefs.c create mode 100644 src/daemon/rpmostreed-transaction-livefs.c create mode 100755 tests/vmcheck/test-livefs.sh diff --git a/Makefile-daemon.am b/Makefile-daemon.am index 2047bf87f7..4da36d5554 100644 --- a/Makefile-daemon.am +++ b/Makefile-daemon.am @@ -39,6 +39,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 \ @@ -49,6 +50,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-decls.am b/Makefile-decls.am index d449d18baf..ddfb17b38b 100644 --- a/Makefile-decls.am +++ b/Makefile-decls.am @@ -47,6 +47,9 @@ gsettings_SCHEMAS = # git.mk GITIGNOREFILES = +# This initializes some more variables +include $(top_srcdir)/buildutil/glib-tap.mk + # This is a special facility to chain together hooks easily INSTALL_DATA_HOOKS = install-data-hook: $(INSTALL_DATA_HOOKS) 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 b17387791b..9784ac6771 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -1,5 +1,3 @@ -include $(top_srcdir)/buildutil/glib-tap.mk - BASE_TESTS_ENVIRONMENT = \ builddir=$(abs_builddir) \ topsrcdir=$(abs_top_srcdir) \ diff --git a/libglnx b/libglnx index abd37a4790..c83ec7f213 160000 --- a/libglnx +++ b/libglnx @@ -1 +1 @@ -Subproject commit abd37a4790f86f53bfb442e6d80e1710f50bff92 +Subproject commit c83ec7f213bd2e435043a435906e46aa9c0a2b6a diff --git a/src/app/rpmostree-builtin-ex.c b/src/app/rpmostree-builtin-ex.c index a2ec0477a4..310e2a576d 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..fd0c7e4a7a --- /dev/null +++ b/src/app/rpmostree-builtin-livefs.c @@ -0,0 +1,101 @@ +/* -*- 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_allow_replace; +static gboolean opt_accept_partial; + +static GOptionEntry option_entries[] = { + { "dry-run", 'n', 0, G_OPTION_ARG_NONE, &opt_dry_run, "Only perform analysis, do not make changes", NULL }, + { "allow-replace", 0, 0, G_OPTION_ARG_NONE, &opt_allow_replace, "Allow replacing existing files", NULL }, + { "accept-partial", 0, 0, G_OPTION_ARG_NONE, &opt_accept_partial, "Continue even on partial updates", 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_allow_replace); + g_variant_dict_insert (&dict, "partial", "b", opt_accept_partial); + + return g_variant_dict_end (&dict); +} + +int +rpmostree_ex_builtin_livefs (int argc, + char **argv, + RpmOstreeCommandInvocation *invocation, + GCancellable *cancellable, + GError **error) +{ + int exit_status = EXIT_FAILURE; + g_autoptr(GOptionContext) context = g_option_context_new ("- Apply pending deployment changes to booted deployment"); + glnx_unref_object RPMOSTreeOS *os_proxy = NULL; + glnx_unref_object RPMOSTreeSysroot *sysroot_proxy = NULL; + g_autofree char *transaction_address = NULL; + + if (!rpmostree_option_context_parse (context, + option_entries, + &argc, &argv, + invocation, + cancellable, + &sysroot_proxy, + error)) + goto out; + + if (!rpmostree_load_os_proxy (sysroot_proxy, NULL, + cancellable, &os_proxy, error)) + goto out; + + if (!rpmostree_os_call_live_fs_sync (os_proxy, + get_args_variant (), + &transaction_address, + cancellable, + error)) + goto out; + + if (!rpmostree_transaction_get_response_sync (sysroot_proxy, + transaction_address, + cancellable, + error)) + goto out; + + exit_status = EXIT_SUCCESS; +out: + /* Does nothing if using the message bus. */ + rpmostree_cleanup_peer (); + + return exit_status; +} diff --git a/src/app/rpmostree-builtin-status.c b/src/app/rpmostree-builtin-status.c index ca6b26a234..ac3993d694 100644 --- a/src/app/rpmostree-builtin-status.c +++ b/src/app/rpmostree-builtin-status.c @@ -177,13 +177,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; @@ -263,7 +266,31 @@ status_generic (RPMOSTreeSysroot *sysroot_proxy, print_kv ("BaseCommit", max_key_len, base_checksum); is_locally_assembled = TRUE; } - print_kv ("Commit", max_key_len, checksum); + 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; + if (!(live_inprogress || live_replaced)) + { + print_kv ("Commit", max_key_len, checksum); + } + else + { + print_kv ("BootedCommit", max_key_len, checksum); + + if (live_inprogress) + { + g_print ("%s%s", red_prefix, bold_prefix); + print_kv ("InterruptedLiveCommit", max_key_len, live_inprogress); + g_print ("%s%s", bold_suffix, red_suffix); + } + if (live_replaced) + { + g_print ("%s%s", red_prefix, bold_prefix); + print_kv ("LiveCommit", max_key_len, live_replaced); + 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 7935447016..253f6970e5 100644 --- a/src/daemon/org.projectatomic.rpmostree1.xml +++ b/src/daemon/org.projectatomic.rpmostree1.xml @@ -215,6 +215,11 @@ + + + + + diff --git a/src/daemon/rpmostree-sysroot-upgrader.c b/src/daemon/rpmostree-sysroot-upgrader.c index a82319dc31..2a3f46fe77 100644 --- a/src/daemon/rpmostree-sysroot-upgrader.c +++ b/src/daemon/rpmostree-sysroot-upgrader.c @@ -109,31 +109,6 @@ parse_origin_deployment (RpmOstreeSysrootUpgrader *self, return TRUE; } -/* This is like ostree_sysroot_get_merge_deployment() except we explicitly - * ignore the magical "booted" behavior. For rpm-ostree we're trying something - * different now where we are a bit more stateful and pick up changes from the - * pending root. This allows users to chain operations together naturally. - */ -static OstreeDeployment * -get_origin_merge_deployment (OstreeSysroot *self, - const char *osname) -{ - g_autoptr(GPtrArray) deployments = ostree_sysroot_get_deployments (self); - guint i; - - for (i = 0; i < deployments->len; i++) - { - OstreeDeployment *deployment = deployments->pdata[i]; - - if (strcmp (ostree_deployment_get_osname (deployment), osname) != 0) - continue; - - return g_object_ref (deployment); - } - - return NULL; -} - static gboolean rpmostree_sysroot_upgrader_initable_init (GInitable *initable, GCancellable *cancellable, @@ -169,7 +144,7 @@ rpmostree_sysroot_upgrader_initable_init (GInitable *initable, goto out; self->cfg_merge_deployment = ostree_sysroot_get_merge_deployment (self->sysroot, self->osname); - self->origin_merge_deployment = get_origin_merge_deployment (self->sysroot, self->osname); + self->origin_merge_deployment = rpmostreed_get_origin_merge_deployment (self->sysroot, self->osname); if (self->cfg_merge_deployment == NULL || self->origin_merge_deployment == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, @@ -1130,7 +1105,6 @@ rpmostree_sysroot_upgrader_deploy (RpmOstreeSysrootUpgrader *self, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; glnx_unref_object OstreeDeployment *new_deployment = NULL; const char *target_revision; g_autoptr(GKeyFile) origin = NULL; @@ -1141,7 +1115,7 @@ rpmostree_sysroot_upgrader_deploy (RpmOstreeSysrootUpgrader *self, g_hash_table_size (rpmostree_origin_get_local_packages (self->origin)) > 0) { if (!do_local_assembly (self, cancellable, error)) - goto out; + return FALSE; } else g_clear_pointer (&self->final_revision, g_free); @@ -1149,8 +1123,7 @@ rpmostree_sysroot_upgrader_deploy (RpmOstreeSysrootUpgrader *self, if (self->flags & RPMOSTREE_SYSROOT_UPGRADER_FLAGS_PKGOVERLAY_DRY_RUN) { /* we already printed the transaction in do_final_local_assembly() */ - ret = TRUE; - goto out; + return TRUE; } /* make sure we have a known target to deploy */ @@ -1164,7 +1137,7 @@ rpmostree_sysroot_upgrader_deploy (RpmOstreeSysrootUpgrader *self, NULL, &new_deployment, cancellable, error)) - goto out; + return FALSE; if (self->final_revision) { @@ -1174,22 +1147,18 @@ rpmostree_sysroot_upgrader_deploy (RpmOstreeSysrootUpgrader *self, if (!ostree_repo_set_ref_immediate (self->repo, NULL, RPMOSTREE_TMP_BASE_REF, self->base_revision, cancellable, error)) - goto out; + return FALSE; } - if (!ostree_sysroot_simple_write_deployment (self->sysroot, self->osname, - new_deployment, - self->cfg_merge_deployment, - OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN, - cancellable, error)) - goto out; + if (!rpmostree_write_deployment (self->sysroot, new_deployment, + self->cfg_merge_deployment, FALSE, + cancellable, error)) + return FALSE; if (!rpmostree_sysroot_upgrader_cleanup (self->sysroot, self->repo, cancellable, error)) - goto out; + return FALSE; - ret = TRUE; - out: - return ret; + return TRUE; } GType diff --git a/src/daemon/rpmostreed-deployment-utils.c b/src/daemon/rpmostreed-deployment-utils.c index 95fa5b5384..c4b2721bab 100644 --- a/src/daemon/rpmostreed-deployment-utils.c +++ b/src/daemon/rpmostreed-deployment-utils.c @@ -21,6 +21,7 @@ #include "rpmostreed-deployment-utils.h" #include "rpmostree-origin.h" #include "rpmostree-util.h" +#include "rpmostreed-utils.h" #include @@ -157,7 +158,8 @@ variant_add_commit_details (GVariantDict *dict, } GVariant * -rpmostreed_deployment_generate_variant (OstreeDeployment *deployment, +rpmostreed_deployment_generate_variant (OstreeSysroot *sysroot, + OstreeDeployment *deployment, const char *booted_id, OstreeRepo *repo, GError **error) @@ -178,6 +180,8 @@ rpmostreed_deployment_generate_variant (OstreeDeployment *deployment, 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, @@ -235,6 +239,16 @@ rpmostreed_deployment_generate_variant (OstreeDeployment *deployment, variant_add_commit_details (&dict, "pending-base-", pending_base_commit); } + if (!rpmostreed_deployment_get_live_status (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-deployment-utils.h b/src/daemon/rpmostreed-deployment-utils.h index da004650f3..ee2cab0eee 100644 --- a/src/daemon/rpmostreed-deployment-utils.h +++ b/src/daemon/rpmostreed-deployment-utils.h @@ -30,10 +30,11 @@ OstreeDeployment * GVariant * rpmostreed_deployment_generate_blank_variant (void); -GVariant * rpmostreed_deployment_generate_variant (OstreeDeployment *deployment, +GVariant * rpmostreed_deployment_generate_variant (OstreeSysroot *sysroot, + OstreeDeployment *deployment, const char *booted_id, OstreeRepo *repo, - GError **error); + GError **error); GVariant * rpmostreed_commit_generate_cached_details_variant (OstreeDeployment *deployment, OstreeRepo *repo, diff --git a/src/daemon/rpmostreed-os.c b/src/daemon/rpmostreed-os.c index ccc8869e21..749f529431 100644 --- a/src/daemon/rpmostreed-os.c +++ b/src/daemon/rpmostreed-os.c @@ -878,6 +878,73 @@ os_handle_cleanup (RPMOSTreeOS *interface, 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_ALLOW_REPLACE; + if (g_variant_dict_lookup (&options_dict, "partial", "b", &opt) && opt) + ret |= RPMOSTREE_TRANSACTION_LIVEFS_FLAG_IGNORE_NON_USR; + + g_variant_dict_clear (&options_dict); + + return ret; +} + +static gboolean +os_handle_live_fs (RPMOSTreeOS *interface, + GDBusMethodInvocation *invocation, + GVariant *arg_options) +{ + RpmostreedOS *self = RPMOSTREED_OS (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_os_complete_pkg_change (interface, invocation, client_address); + } + + return TRUE; +} + static gboolean os_handle_get_cached_rebase_rpm_diff (RPMOSTreeOS *interface, GDBusMethodInvocation *invocation, @@ -1176,7 +1243,7 @@ rpmostreed_os_load_internals (RpmostreedOS *self, GError **error) booted = ostree_sysroot_get_booted_deployment (ot_sysroot); if (booted && g_strcmp0 (ostree_deployment_get_osname (booted), name) == 0) { - booted_variant = rpmostreed_deployment_generate_variant (booted, booted_id, ot_repo, error); + booted_variant = rpmostreed_deployment_generate_variant (ot_sysroot, booted, booted_id, ot_repo, error); if (!booted_variant) return FALSE; booted_id = rpmostreed_deployment_generate_id (booted); @@ -1187,7 +1254,8 @@ rpmostreed_os_load_internals (RpmostreedOS *self, GError **error) { if (g_strcmp0 (ostree_deployment_get_osname (deployments->pdata[i]), name) == 0) { - default_variant = rpmostreed_deployment_generate_variant (deployments->pdata[i], + default_variant = rpmostreed_deployment_generate_variant (ot_sysroot, + deployments->pdata[i], booted_id, ot_repo, error); if (default_variant == NULL) @@ -1201,8 +1269,8 @@ 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 (deployments->pdata[rollback_index], booted_id, - ot_repo, error); + rollback_variant = rpmostreed_deployment_generate_variant (ot_sysroot, deployments->pdata[rollback_index], booted_id, + ot_repo, error); if (!rollback_variant) return FALSE; } @@ -1264,6 +1332,7 @@ rpmostreed_os_iface_init (RPMOSTreeOSIface *iface) iface->handle_pkg_change = os_handle_pkg_change; iface->handle_set_initramfs_state = os_handle_set_initramfs_state; iface->handle_cleanup = os_handle_cleanup; + iface->handle_live_fs = os_handle_live_fs; iface->handle_get_cached_rebase_rpm_diff = os_handle_get_cached_rebase_rpm_diff; iface->handle_download_rebase_rpm_diff = os_handle_download_rebase_rpm_diff; iface->handle_get_cached_deploy_rpm_diff = os_handle_get_cached_deploy_rpm_diff; diff --git a/src/daemon/rpmostreed-sysroot.c b/src/daemon/rpmostreed-sysroot.c index b55338e390..057b63c28f 100644 --- a/src/daemon/rpmostreed-sysroot.c +++ b/src/daemon/rpmostreed-sysroot.c @@ -493,7 +493,7 @@ sysroot_populate_deployments_unlocked (RpmostreedSysroot *self, OstreeDeployment *deployment = deployments->pdata[i]; const char *deployment_os; - variant = rpmostreed_deployment_generate_variant (deployment, booted_id, self->repo, error); + variant = rpmostreed_deployment_generate_variant (self->ot_sysroot, deployment, booted_id, self->repo, error); if (!variant) goto out; g_variant_builder_add_value (&builder, variant); diff --git a/src/daemon/rpmostreed-transaction-livefs.c b/src/daemon/rpmostreed-transaction-livefs.c new file mode 100644 index 0000000000..a7086a3595 --- /dev/null +++ b/src/daemon/rpmostreed-transaction-livefs.c @@ -0,0 +1,525 @@ +/* + * 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-upgrader.h" +#include "rpmostree-util.h" +#include "rpmostree-db.h" +#include "rpmostree-output.h" +#include "rpmostree-core.h" +#include "rpmostreed-utils.h" + +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; + +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 void +update_diff_for_path (const char *path, + gboolean is_addition, + CommitDiffFlags *inout_flags, + guint *inout_changed) +{ + /* We expect the rpmdb to change */ + if (path_is_rpmdb (path)) + ; + else if (path_is_usretc (path)) + (*inout_flags) |= COMMIT_DIFF_FLAGS_ETC; + else if (path_is_boot (path)) + (*inout_flags) |= COMMIT_DIFF_FLAGS_BOOT; + else if (path_is_rootfs (path)) + (*inout_flags) |= COMMIT_DIFF_FLAGS_ROOTFS; + else if (inout_changed) + (*inout_changed)++; +} + +static gboolean +analyze_commit_diff (OstreeRepo *repo, + const char *from_rev, + const char *to_rev, + CommitDiffFlags *out_flags, + guint *out_n_replaced, + GCancellable *cancellable, + GError **error) +{ + glnx_unref_object GFile *from_tree = NULL; + glnx_unref_object GFile *to_tree = NULL; + g_autoptr(GPtrArray) modified = NULL; + g_autoptr(GPtrArray) removed = NULL; + g_autoptr(GPtrArray) added = NULL; + CommitDiffFlags ret_flags = 0; + guint ret_replaced = 0; + + /* Shouldn't happen, but might as well fast path it in case */ + if (strcmp (from_rev, to_rev) == 0) + goto done; + + if (!ostree_repo_read_commit (repo, from_rev, &from_tree, NULL, + cancellable, error)) + return FALSE; + if (!ostree_repo_read_commit (repo, to_rev, &to_tree, NULL, + cancellable, error)) + return FALSE; + + + modified = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_diff_item_unref); + removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + 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; + + for (guint i = 0; i < removed->len; i++) + { + GFile *gfpath = removed->pdata[i]; + const char *path = gs_file_get_path_cached (gfpath); + + update_diff_for_path (path, FALSE, &ret_flags, &ret_replaced); + } + + for (guint i = 0; i < modified->len; i++) + { + OstreeDiffItem *diffitem = modified->pdata[i]; + const char *path = gs_file_get_path_cached (diffitem->src); + + update_diff_for_path (path, FALSE, &ret_flags, &ret_replaced); + } + + for (guint i = 0; i < added->len; i++) + { + GFile *added_f = added->pdata[i]; + const char *path = gs_file_get_path_cached (added_f); + + update_diff_for_path (path, TRUE, &ret_flags, NULL); + } + + done: + *out_flags = ret_flags; + *out_n_replaced = ret_replaced; + return TRUE; +} + +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); + guint i; + + for (i = 0; i < deployments->len; i++) + { + OstreeDeployment *deployment = deployments->pdata[i]; + int idx = ostree_deployment_get_index (deployment); + + if (strcmp (ostree_deployment_get_osname (deployment), osname) != 0) + continue; + + if (idx <= booted_idx) + continue; + + return g_object_ref (deployment); + } + + return NULL; +} + +static gboolean +prepare_rollback_deployment (OstreeSysroot *sysroot, + 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)) + { + g_prefix_error (error, "Performing initial cleanup: "); + return FALSE; + } + + g_print ("Preparing rollback 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); + + if (!rpmostree_write_deployment (sysroot, new_deployment, booted_deployment, + TRUE, cancellable, error)) + return FALSE; + + return TRUE; +} + +static gboolean +livefs_transaction_execute (RpmostreedTransaction *transaction, + GCancellable *cancellable, + GError **error) +{ + LiveFsTransaction *self = (LiveFsTransaction *) transaction; + OstreeSysroot *sysroot; + OstreeDeployment *booted_deployment; + OstreeDeployment *origin_merge_deployment; + OstreeDeployment *rollback_deployment; + glnx_unref_object OstreeRepo *repo = NULL; + g_autofree char *deployment_path = NULL; + glnx_fd_close int deployment_dfd = -1; + const char *booted_csum; + const char *target_csum; + g_autoptr(GPtrArray) removed_pkgs = NULL; + g_autoptr(GPtrArray) added_pkgs = NULL; + g_autoptr(GPtrArray) modified_pkgs_old = NULL; + g_autoptr(GPtrArray) modified_pkgs_new = NULL; + CommitDiffFlags diff_analysis_flags = 0; + guint n_replaced; + g_autofree char *live_inprogress = NULL; + g_autofree char *live_replaced = NULL; + static const char orig_rpmdb_path[] = "usr/share/rpm.rpmostree-orig"; + + sysroot = rpmostreed_transaction_get_sysroot (transaction); + if (!ostree_sysroot_get_repo (sysroot, &repo, cancellable, error)) + return FALSE; + + booted_deployment = ostree_sysroot_get_booted_deployment (sysroot); + if (!booted_deployment) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Not currently booted into an OSTree system"); + return FALSE; + } + + origin_merge_deployment = rpmostreed_get_origin_merge_deployment (sysroot, + ostree_deployment_get_osname (booted_deployment)); + /* We have to have at least the booted one */ + g_assert (origin_merge_deployment); + if (origin_merge_deployment == booted_deployment) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "No pending deployment"); + return FALSE; + } + + deployment_path = ostree_sysroot_get_deployment_dirpath (sysroot, booted_deployment); + if (!glnx_opendirat (ostree_sysroot_get_fd (sysroot), deployment_path, TRUE, + &deployment_dfd, error)) + return FALSE; + + booted_csum = ostree_deployment_get_csum (booted_deployment); + target_csum = ostree_deployment_get_csum (origin_merge_deployment); + + if (!rpmostreed_deployment_get_live_status (sysroot, booted_deployment, -1, + &live_inprogress, &live_replaced, + error)) + return FALSE; + + if (live_inprogress != NULL) + { + if (strcmp (live_inprogress, target_csum) == 0) + { + g_print ("Note: Resuming interrupted overlay of %s\n", target_csum); + } + } + if (live_replaced != NULL) + { + if (strcmp (live_replaced, booted_csum) == 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Current overlay is already %s", booted_csum); + return FALSE; + } + g_print ("Note: Adding to previous overlay: %s\n", live_replaced); + } + + if (!analyze_commit_diff (repo, booted_csum, + target_csum, + &diff_analysis_flags, + &n_replaced, + cancellable, error)) + return FALSE; + + if (!rpm_ostree_db_diff (repo, booted_csum, target_csum, + &removed_pkgs, &added_pkgs, &modified_pkgs_old, &modified_pkgs_new, + cancellable, error)) + return FALSE; + g_assert (modified_pkgs_old->len == modified_pkgs_new->len); + + g_print ("Packages:\n modified: %u\n removed: %u\n added: %u\n", + modified_pkgs_new->len, removed_pkgs->len, added_pkgs->len); + + if (diff_analysis_flags == 0) + { + if ((self->flags & RPMOSTREE_TRANSACTION_LIVEFS_FLAG_DRY_RUN) > 0) + { + g_print ("livefs would be safe!\n"); + return TRUE; + } + } + else + { + if (diff_analysis_flags & COMMIT_DIFF_FLAGS_ETC) + { + g_print ("warning: livefs would change configuration in /etc\n"); + } + if (diff_analysis_flags & COMMIT_DIFF_FLAGS_REPLACEMENT) + { + g_print ("warning: livefs would replace %u files\n", n_replaced); + } + if (diff_analysis_flags & COMMIT_DIFF_FLAGS_ROOTFS) + { + g_print ("warning: livefs would add or replace content in /\n"); + } + if (diff_analysis_flags & COMMIT_DIFF_FLAGS_BOOT) + { + g_print ("warning: livefs would add or replace kernel/initramfs\n"); + } + + /* Ensure these messages are out so they don't mix with an error from above */ + fflush (stdout); + + if ((self->flags & RPMOSTREE_TRANSACTION_LIVEFS_FLAG_DRY_RUN) > 0) + return TRUE; + else if ((diff_analysis_flags & COMMIT_DIFF_FLAGS_REPLACEMENT) > 0 + && (self->flags & RPMOSTREE_TRANSACTION_LIVEFS_FLAG_ALLOW_REPLACE) == 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "livefs update would replace files in /usr, and replacement not enabled"); + return FALSE; + } + else if ((diff_analysis_flags & ~COMMIT_DIFF_FLAGS_REPLACEMENT) > 0 + && (self->flags & RPMOSTREE_TRANSACTION_LIVEFS_FLAG_IGNORE_NON_USR) == 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "livefs update would perform changes beyond new files in /usr, and partial updates not enabled"); + return FALSE; + } + } + + rollback_deployment = get_rollback_deployment (sysroot, booted_deployment); + if (!rollback_deployment) + { + if (!prepare_rollback_deployment (sysroot, booted_deployment, cancellable, error)) + { + g_prefix_error (error, "Preparing rollback: "); + return FALSE; + } + } + + /* Reload this, the sysroot may have changed it */ + booted_deployment = ostree_sysroot_get_booted_deployment (sysroot); + + if (!ostree_sysroot_deployment_set_mutable (sysroot, booted_deployment, TRUE, + cancellable, error)) + { + g_prefix_error (error, "Setting deployment mutable: "); + return 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) + { + glnx_set_prefix_error_from_errno (error, "Setting %s", RPMOSTREE_LIVE_INPROGRESS_XATTR); + return FALSE; + } + + rpmostree_output_task_begin ("Overlaying /usr"); + + /* Note we only overlay /usr. Note this should implicitly skip the rpmdb since + * we won't overlay new files right now, but we should *explicitly* skip it + * which would require some sort of skip support in checkout. + */ + { 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; + } + + /* Now, replace 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, + .overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES, + .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) + { + glnx_set_prefix_error_from_errno (error, "%s", "rename(..., RENAME_EXCHANGE) for rpmdb"); + return FALSE; + } + /* And nuke the old one */ + if (!glnx_shutil_rm_rf_at (deployment_dfd, orig_rpmdb_path, cancellable, error)) + return FALSE; + + if (fsetxattr (deployment_dfd, RPMOSTREE_LIVE_REPLACED_XATTR, target_csum, OSTREE_SHA256_STRING_LEN, 0) < 0) + { + glnx_set_prefix_error_from_errno (error, "Setting %s", RPMOSTREE_LIVE_REPLACED_XATTR); + return FALSE; + } + + 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 + { + glnx_set_error_from_errno (error); + return FALSE; + } + } + + if (!ostree_sysroot_deployment_set_mutable (sysroot, booted_deployment, FALSE, + cancellable, error)) + { + g_prefix_error (error, "Setting deployment mutable: "); + return FALSE; + } + + rpmostree_output_task_end ("done"); + + if (!rpmostree_sysroot_bump_mtime (sysroot, error)) + return FALSE; + + return TRUE; +} + +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 a2e3f9fd7f..b3531dbcd5 100644 --- a/src/daemon/rpmostreed-transaction-types.c +++ b/src/daemon/rpmostreed-transaction-types.c @@ -19,6 +19,9 @@ #include "config.h" #include "ostree.h" +#include +#include +#include #include #include "rpmostreed-transaction-types.h" @@ -27,6 +30,7 @@ #include "rpmostreed-sysroot.h" #include "rpmostree-sysroot-upgrader.h" #include "rpmostree-util.h" +#include "rpmostree-output.h" #include "rpmostree-core.h" #include "rpmostree-unpacker.h" #include "rpmostreed-utils.h" diff --git a/src/daemon/rpmostreed-transaction-types.h b/src/daemon/rpmostreed-transaction-types.h index 6bba76870b..6322adfbc3 100644 --- a/src/daemon/rpmostreed-transaction-types.h +++ b/src/daemon/rpmostreed-transaction-types.h @@ -91,3 +91,16 @@ 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_ALLOW_REPLACE = (1 << 1), + RPMOSTREE_TRANSACTION_LIVEFS_FLAG_IGNORE_NON_USR = (1 << 2) +} 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..2289d8173f 100644 --- a/src/daemon/rpmostreed-utils.c +++ b/src/daemon/rpmostreed-utils.c @@ -23,6 +23,9 @@ #include "libglnx.h" #include +#include +#include +#include static void append_to_object_path (GString *str, @@ -214,6 +217,117 @@ rpmostreed_refspec_parse_partial (const gchar *new_provided_refspec, return ret; } +/* This is like ostree_sysroot_get_merge_deployment() except we explicitly + * ignore the magical "booted" behavior. For rpm-ostree we're trying something + * different now where we are a bit more stateful and pick up changes from the + * pending root. This allows users to chain operations together naturally. + */ +OstreeDeployment * +rpmostreed_get_origin_merge_deployment (OstreeSysroot *self, const char *osname) +{ + g_autoptr(GPtrArray) deployments = ostree_sysroot_get_deployments (self); + guint i; + + for (i = 0; i < deployments->len; i++) + { + OstreeDeployment *deployment = deployments->pdata[i]; + + if (strcmp (ostree_deployment_get_osname (deployment), osname) != 0) + continue; + + return g_object_ref (deployment); + } + + return NULL; +} + + +/* Copy of currently private _ostree_sysroot_bump_mtime() + * until we decide to either formalize that, or have a method + * to notify of changes to e.g. live replaced xattrs. + */ +gboolean +rpmostree_sysroot_bump_mtime (OstreeSysroot *sysroot, + GError **error) +{ + if (utimensat (ostree_sysroot_get_fd (sysroot), "ostree/deploy", NULL, 0) < 0) + { + glnx_set_prefix_error_from_errno (error, "%s", "futimens"); + return FALSE; + } + return TRUE; +} + +/* A version of ostree_sysroot_simple_write_deployment() but with + * a few changes: + * + * - There's just @pushing_rollback, which if true makes the deployment not-default + * as well as retaining the pending deployment + * - osname logic is based on new deployment + * - Fix insertion of deployment to be after booted (patch pending for ostree upstream) + */ +gboolean +rpmostree_write_deployment (OstreeSysroot *sysroot, + OstreeDeployment *new_deployment, + OstreeDeployment *merge_deployment, + gboolean pushing_rollback, + GCancellable *cancellable, + GError **error) +{ + OstreeDeployment *booted_deployment = NULL; + OstreeSysrootWriteDeploymentsOpts write_opts = { .do_postclean = FALSE }; + g_autoptr(GPtrArray) deployments = NULL; + g_autoptr(GPtrArray) new_deployments = g_ptr_array_new_with_free_func (g_object_unref); + const char *osname = ostree_deployment_get_osname (new_deployment); + /* Whether or not we added @new_deployment to the list yet */ + gboolean added_new = FALSE; + /* Keep track of whether we're looking at a deployment before or after the booted */ + gboolean before_booted = TRUE; + + deployments = ostree_sysroot_get_deployments (sysroot); + booted_deployment = ostree_sysroot_get_booted_deployment (sysroot); + + if (!pushing_rollback) + { + g_ptr_array_add (new_deployments, g_object_ref (new_deployment)); + added_new = TRUE; + } + + for (guint i = 0; i < deployments->len; i++) + { + OstreeDeployment *deployment = deployments->pdata[i]; + const gboolean osname_matches = (strcmp (ostree_deployment_get_osname (deployment), osname) == 0); + const gboolean is_booted = ostree_deployment_equal (deployment, booted_deployment); + const gboolean is_merge_or_booted = is_booted || + ostree_deployment_equal (deployment, merge_deployment); + const gboolean is_last = i == (deployments->len - 1); + + if (is_booted) + before_booted = FALSE; + + /* 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 + */ + if (!osname_matches || (pushing_rollback && before_booted) || is_merge_or_booted) + g_ptr_array_add (new_deployments, g_object_ref (deployment)); + + /* Insert new rollback right after the booted */ + if (!added_new && (!before_booted || is_last)) + { + g_ptr_array_add (new_deployments, g_object_ref (new_deployment)); + added_new = TRUE; + } + } + + if (!ostree_sysroot_write_deployments_with_options (sysroot, new_deployments, + &write_opts, cancellable, error)) + return FALSE; + + return TRUE; +} + void rpmostreed_reboot (GCancellable *cancellable, GError **error) { @@ -632,3 +746,68 @@ rpmostreed_parse_revision (const char *revision, out: return ret; } + +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) + { + if (errno != ENODATA) + { + glnx_set_error_from_errno (error); + return FALSE; + } + } + 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; +} + +gboolean +rpmostreed_deployment_get_live_status (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; +} diff --git a/src/daemon/rpmostreed-utils.h b/src/daemon/rpmostreed-utils.h index 8a679fe388..507d0f606e 100644 --- a/src/daemon/rpmostreed-utils.h +++ b/src/daemon/rpmostreed-utils.h @@ -34,6 +34,18 @@ gboolean rpmostreed_refspec_parse_partial (const gchar *new_provided_refspec, const gchar *base_refspec, gchar **out_refspec, GError **error); + +OstreeDeployment *rpmostreed_get_origin_merge_deployment (OstreeSysroot *self, const char *osname); + +gboolean rpmostree_sysroot_bump_mtime (OstreeSysroot *self, GError **error); + +gboolean rpmostree_write_deployment (OstreeSysroot *sysroot, + OstreeDeployment *new_deployment, + OstreeDeployment *merge_deployment, + gboolean make_default, + GCancellable *cancellable, + GError **error); + void rpmostreed_reboot (GCancellable *cancellable, GError **error); @@ -82,3 +94,14 @@ gboolean rpmostreed_parse_revision (const char *revision, char **out_checksum, char **out_version, GError **error); + + +#define RPMOSTREE_LIVE_INPROGRESS_XATTR "user.rpmostree-live-inprogress" +#define RPMOSTREE_LIVE_REPLACED_XATTR "user.rpmostree-live-replaced" + +gboolean rpmostreed_deployment_get_live_status (OstreeSysroot *sysroot, + OstreeDeployment *deployment, + int deployment_dfd, + char **out_inprogress_checksum, + char **out_livereplaced_checksum, + GError **error); diff --git a/tests/vmcheck/test-livefs.sh b/tests/vmcheck/test-livefs.sh new file mode 100755 index 0000000000..791a1ca6a8 --- /dev/null +++ b/tests/vmcheck/test-livefs.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# +# Copyright (C) 2017 Red Hat Inc. +# +# This library 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 License, 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. + +set -e + +. ${commondir}/libtest.sh +. ${commondir}/libvm.sh + +set -x + +vm_send_test_repo + +vm_assert_layered_pkg foo absent + +vm_rpmostree install /tmp/vmcheck/repo/packages/x86_64/foo-1.0-1.x86_64.rpm +echo "ok install foo locally" + +if vm_cmd rpm -q foo; then + assert_not_reached "have foo?" +fi +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 > foo-rpmq.txt +assert_file_has_content foo-rpmq.txt foo-1.0-1 + +echo "ok livefs"