Introduce ex livefs
There are a few different use cases here. First, for layering new packages, there's no good reason for us to force a reboot. Second, we want some support for cherry-picking security updates and allowing admins to restart services. Finally, at some point we should offer support for entirely replacing the running tree if that's what the user wants. Until now we've been very conservative, but there's a spectrum here. In particular, this patch changes things so we push a rollback before we start doing anything live. I think in practice, many use cases would be totally fine with doing most changes live, and falling back to the rollback if something went wrong. This initial code drop *only* supports live layering of new packages. However, a lot of the base infrastructure is laid for future work. For now, this will be classified as an experimental feature, hence `ex livefs`. Part of: https://github.com/projectatomic/rpm-ostree/issues/639 Closes: #652 Approved by: jlebon
This commit is contained in:
parent
376a2cc3f5
commit
95e9aa4284
@ -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 \
|
||||
|
@ -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 \
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
96
src/app/rpmostree-builtin-livefs.c
Normal file
96
src/app/rpmostree-builtin-livefs.c
Normal file
@ -0,0 +1,96 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||
*
|
||||
* Copyright (C) 2017 Colin Walters <walters@verbum.org>
|
||||
*
|
||||
* 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 <string.h>
|
||||
#include <glib-unix.h>
|
||||
|
||||
#include "rpmostree-ex-builtins.h"
|
||||
#include "rpmostree-libbuiltin.h"
|
||||
#include "rpmostree-dbus-helpers.h"
|
||||
|
||||
#include <libglnx.h>
|
||||
|
||||
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;
|
||||
}
|
@ -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 = FALSE;
|
||||
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
|
||||
|
@ -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
|
||||
|
||||
|
@ -264,6 +264,11 @@
|
||||
<arg type="s" name="result" direction="out"/>
|
||||
</method>
|
||||
|
||||
<method name="LiveFs">
|
||||
<arg type="a{sv}" name="options" direction="in"/>
|
||||
<arg type="s" name="transaction_address" direction="out"/>
|
||||
</method>
|
||||
|
||||
</interface>
|
||||
|
||||
<interface name="org.projectatomic.rpmostree1.Transaction">
|
||||
|
@ -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 */
|
||||
@ -510,3 +522,41 @@ rpmostree_syscore_write_deployments (OstreeSysroot *sysroot,
|
||||
|
||||
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_autoptr(RpmOstreeOrigin) origin = rpmostree_origin_parse_deployment (deployment, error);
|
||||
if (!origin)
|
||||
return FALSE;
|
||||
rpmostree_origin_get_live_state (origin, out_inprogress_checksum, out_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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
void rpmostree_syscore_query_deployments (OstreeSysroot *sysroot,
|
||||
const char *osname,
|
||||
|
@ -106,6 +106,11 @@ parse_origin_deployment (RpmOstreeSysrootUpgrader *self,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* A bit hacky; here we clean out the live state which is deployment specific.
|
||||
* We don't expect users of the upgrader to want the live state.
|
||||
*/
|
||||
rpmostree_origin_set_live_state (self->origin, NULL, NULL);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -908,7 +913,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;
|
||||
|
@ -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 <libglnx.h>
|
||||
|
||||
@ -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 =
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
|
696
src/daemon/rpmostreed-transaction-livefs.c
Normal file
696
src/daemon/rpmostreed-transaction-livefs.c
Normal file
@ -0,0 +1,696 @@
|
||||
/*
|
||||
* 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 <sys/types.h>
|
||||
#include <sys/xattr.h>
|
||||
#include <systemd/sd-journal.h>
|
||||
#include <libglnx.h>
|
||||
|
||||
#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-origin.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<GFile> */
|
||||
GPtrArray *modified; /* Set<OstreeDiffItem> */
|
||||
GPtrArray *removed; /* Set<GFile> */
|
||||
|
||||
/* 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)
|
||||
{
|
||||
rpmostree_output_task_begin ("Copying new config files");
|
||||
|
||||
/* 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;
|
||||
|
||||
glnx_fd_close int deployment_etc_dfd = -1;
|
||||
if (!glnx_opendirat (new_deployment_dfd, "etc", TRUE, &deployment_etc_dfd, error))
|
||||
return FALSE;
|
||||
|
||||
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,
|
||||
deployment_etc_dfd, ".",
|
||||
ostree_deployment_get_csum (merge_deployment),
|
||||
cancellable, error))
|
||||
return g_prefix_error (error, "Copying %s: ", path), FALSE;
|
||||
n_added++;
|
||||
}
|
||||
rpmostree_output_task_end ("%u", 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 *sysroot,
|
||||
OstreeDeployment *booted)
|
||||
{
|
||||
const char *booted_csum = ostree_deployment_get_csum (booted);
|
||||
|
||||
g_autoptr(OstreeDeployment) rollback_deployment = NULL;
|
||||
rpmostree_syscore_query_deployments (sysroot, ostree_deployment_get_osname (booted),
|
||||
NULL, &rollback_deployment);
|
||||
/* If no rollback found, we're done */
|
||||
if (!rollback_deployment)
|
||||
return NULL;
|
||||
/* We found a rollback, but it needs to match our checksum */
|
||||
const char *csum = ostree_deployment_get_csum (rollback_deployment);
|
||||
if (strcmp (csum, booted_csum) == 0)
|
||||
return g_object_ref (rollback_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;
|
||||
}
|
||||
|
||||
/* Update the origin for @booted with new livefs state */
|
||||
static gboolean
|
||||
write_livefs_state (OstreeSysroot *sysroot,
|
||||
OstreeDeployment *booted,
|
||||
const char *live_inprogress,
|
||||
const char *live,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(RpmOstreeOrigin) new_origin = rpmostree_origin_parse_deployment (booted, error);
|
||||
if (!new_origin)
|
||||
return FALSE;
|
||||
rpmostree_origin_set_live_state (new_origin, live_inprogress, live);
|
||||
g_autoptr(GKeyFile) kf = rpmostree_origin_dup_keyfile (new_origin);
|
||||
if (!ostree_sysroot_write_origin_file (sysroot, booted, kf, NULL, 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? */
|
||||
/* 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");
|
||||
}
|
||||
else
|
||||
return glnx_throw (error, "Replacement mode not implemented yet");
|
||||
if ((self->flags & RPMOSTREE_TRANSACTION_LIVEFS_FLAG_DRY_RUN) > 0)
|
||||
{
|
||||
g_print ("livefs OK (dry run)\n");
|
||||
/* Note early return */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/* Note we inherit the previous value of `live_replaced` (which may be NULL) */
|
||||
if (!write_livefs_state (sysroot, booted_deployment, target_csum, live_replaced, error))
|
||||
return FALSE;
|
||||
|
||||
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;
|
||||
|
||||
rpmostree_output_task_end ("done");
|
||||
|
||||
if (requires_etc_merge)
|
||||
{
|
||||
if (!copy_new_config_files (repo, origin_merge_deployment,
|
||||
deployment_dfd, sepolicy, diff,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Write out the origin as having completed this */
|
||||
if (!write_livefs_state (sysroot, booted_deployment, NULL, target_csum, error))
|
||||
return FALSE;
|
||||
|
||||
/* 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);
|
||||
|
||||
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;
|
||||
}
|
@ -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"
|
||||
@ -982,6 +983,7 @@ cleanup_transaction_execute (RpmostreedTransaction *transaction,
|
||||
rpmostree_syscore_filter_deployments (sysroot, self->osname,
|
||||
cleanup_pending,
|
||||
cleanup_rollback);
|
||||
|
||||
if (new_deployments)
|
||||
{
|
||||
OstreeSysrootWriteDeploymentsOpts write_opts = { .do_postclean = FALSE };
|
||||
|
@ -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);
|
||||
|
@ -21,6 +21,8 @@
|
||||
#include "rpmostreed-utils.h"
|
||||
#include "rpmostreed-errors.h"
|
||||
#include "libglnx.h"
|
||||
#include <systemd/sd-journal.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <libglnx.h>
|
||||
|
||||
|
@ -156,6 +156,17 @@ rpmostree_origin_get_unconfigured_state (RpmOstreeOrigin *origin)
|
||||
return origin->cached_unconfigured_state;
|
||||
}
|
||||
|
||||
void
|
||||
rpmostree_origin_get_live_state (RpmOstreeOrigin *origin,
|
||||
char **out_inprogress,
|
||||
char **out_live)
|
||||
{
|
||||
if (out_inprogress)
|
||||
*out_inprogress = g_key_file_get_string (origin->kf, "rpmostree-ex-live", "inprogress", NULL);
|
||||
if (out_live)
|
||||
*out_live = g_key_file_get_string (origin->kf, "rpmostree-ex-live", "commit", NULL);
|
||||
}
|
||||
|
||||
GKeyFile *
|
||||
rpmostree_origin_dup_keyfile (RpmOstreeOrigin *origin)
|
||||
{
|
||||
@ -291,6 +302,33 @@ rpmostree_origin_set_rebase (RpmOstreeOrigin *origin,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Like g_key_file_set_string(), but remove the key if @value is NULL */
|
||||
static void
|
||||
set_or_unset_str (GKeyFile *kf,
|
||||
const char *group,
|
||||
const char *key,
|
||||
const char *value)
|
||||
{
|
||||
if (!value)
|
||||
(void) g_key_file_remove_key (kf, group, key, NULL);
|
||||
else
|
||||
(void) g_key_file_set_string (kf, group, key, value);
|
||||
}
|
||||
|
||||
void
|
||||
rpmostree_origin_set_live_state (RpmOstreeOrigin *origin,
|
||||
const char *inprogress,
|
||||
const char *live)
|
||||
{
|
||||
if (!inprogress && !live)
|
||||
(void) g_key_file_remove_group (origin->kf, "rpmostree-ex-live", NULL);
|
||||
else
|
||||
{
|
||||
set_or_unset_str (origin->kf, "rpmostree-ex-live", "inprogress", inprogress);
|
||||
set_or_unset_str (origin->kf, "rpmostree-ex-live", "commit", live);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
update_keyfile_pkgs_from_cache (RpmOstreeOrigin *origin,
|
||||
gboolean local)
|
||||
|
@ -72,6 +72,11 @@ rpmostree_origin_get_initramfs_args (RpmOstreeOrigin *origin);
|
||||
const char *
|
||||
rpmostree_origin_get_unconfigured_state (RpmOstreeOrigin *origin);
|
||||
|
||||
void
|
||||
rpmostree_origin_get_live_state (RpmOstreeOrigin *origin,
|
||||
char **out_inprogress,
|
||||
char **out_live);
|
||||
|
||||
char *
|
||||
rpmostree_origin_get_string (RpmOstreeOrigin *origin,
|
||||
const char *section,
|
||||
@ -107,3 +112,8 @@ rpmostree_origin_delete_packages (RpmOstreeOrigin *origin,
|
||||
char **packages,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
void
|
||||
rpmostree_origin_set_live_state (RpmOstreeOrigin *origin,
|
||||
const char *inprogress,
|
||||
const char *live);
|
||||
|
33
tests/common/compose/yum/test-livefs-with-etc.spec
Normal file
33
tests/common/compose/yum/test-livefs-with-etc.spec
Normal file
@ -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 <<EOF
|
||||
A config file for %{name}
|
||||
EOF
|
||||
|
||||
%install
|
||||
mkdir -p %{buildroot}/usr/bin
|
||||
install %{name} %{buildroot}/usr/bin
|
||||
mkdir -p %{buildroot}/etc
|
||||
install %{name}.conf %{buildroot}/etc
|
||||
|
||||
%files
|
||||
/usr/bin/%{name}
|
||||
/etc/%{name}.conf
|
@ -59,6 +59,16 @@ vm_cmd() {
|
||||
$SSH "$@"
|
||||
}
|
||||
|
||||
# Copy argument (usually shell script) to VM, execute it there
|
||||
vm_cmdfile() {
|
||||
bin=$1
|
||||
chmod a+x ${bin}
|
||||
bn=$(basename ${bin})
|
||||
$SCP $1 $VM:/root/${bn}
|
||||
$SSH /root/${bn}
|
||||
}
|
||||
|
||||
|
||||
# Delete anything which we might change between runs
|
||||
vm_clean_caches() {
|
||||
vm_cmd rm /ostree/repo/extensions/rpmostree/pkgcache/refs/heads/* -rf
|
||||
|
126
tests/vmcheck/test-livefs.sh
Executable file
126
tests/vmcheck/test-livefs.sh
Executable file
@ -0,0 +1,126 @@
|
||||
#!/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
|
||||
vm_assert_status_jq '.deployments|length == 2'
|
||||
echo "ok install foo locally"
|
||||
|
||||
if vm_cmd rpm -q foo; then
|
||||
assert_not_reached "have foo?"
|
||||
fi
|
||||
assert_livefs_ok() {
|
||||
vm_rpmostree ex livefs -n > livefs-analysis.txt
|
||||
assert_file_has_content livefs-analysis.txt 'livefs OK (dry run)'
|
||||
}
|
||||
assert_livefs_ok
|
||||
|
||||
vm_assert_status_jq '.deployments|length == 2' '.deployments[0]["live-replaced"]|not' \
|
||||
'.deployments[1]["live-replaced"]|not'
|
||||
vm_rpmostree ex livefs
|
||||
vm_cmd rpm -q foo > rpmq.txt
|
||||
assert_file_has_content rpmq.txt foo-1.0-1
|
||||
vm_assert_status_jq '.deployments|length == 3' '.deployments[0]["live-replaced"]|not' \
|
||||
'.deployments[1]["live-replaced"]'
|
||||
|
||||
echo "ok livefs stage1"
|
||||
|
||||
vm_rpmostree install /tmp/vmcheck/repo/packages/x86_64/test-livefs-with-etc-1.0-1.x86_64.rpm
|
||||
assert_livefs_ok
|
||||
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_cmd cat /etc/test-livefs-with-etc.conf > test-livefs-with-etc.conf
|
||||
assert_file_has_content test-livefs-with-etc.conf "A config file for test-livefs-with-etc"
|
||||
|
||||
echo "ok livefs stage2"
|
||||
|
||||
# Now, perform a further change in the pending
|
||||
vm_rpmostree uninstall test-livefs-with-etc-1.0-1.x86_64
|
||||
vm_assert_status_jq '.deployments|length == 3'
|
||||
echo "ok livefs preserved rollback"
|
||||
|
||||
# Reset to rollback, undeploy pending
|
||||
reset() {
|
||||
vm_rpmostree rollback
|
||||
vm_reboot
|
||||
vm_rpmostree cleanup -r
|
||||
vm_assert_status_jq '.deployments|length == 1' '.deployments[0]["live-replaced"]|not'
|
||||
}
|
||||
reset
|
||||
|
||||
# If the admin created a config file before, we need to keep it
|
||||
vm_rpmostree install /tmp/vmcheck/repo/packages/x86_64/test-livefs-with-etc-1.0-1.x86_64.rpm
|
||||
vm_cmd cat /etc/test-livefs-with-etc.conf || true
|
||||
vm_cmd echo custom \> /etc/test-livefs-with-etc.conf
|
||||
vm_cmd cat /etc/test-livefs-with-etc.conf
|
||||
vm_rpmostree ex livefs
|
||||
vm_cmd cat /etc/test-livefs-with-etc.conf > test-livefs-with-etc.conf
|
||||
assert_file_has_content test-livefs-with-etc.conf "custom"
|
||||
echo "ok livefs preserved modified config"
|
||||
|
||||
reset
|
||||
vm_rpmostree install /tmp/vmcheck/repo/packages/x86_64/foo-1.0-1.x86_64.rpm
|
||||
vm_rpmostree ex livefs
|
||||
generate_upgrade() {
|
||||
# Create a modified vmcheck commit
|
||||
cat >t.sh<<EOF
|
||||
#!/bin/bash
|
||||
set -xeuo pipefail
|
||||
cd /ostree/repo/tmp
|
||||
rm vmcheck -rf
|
||||
ostree checkout vmcheck vmcheck --fsync=0
|
||||
(cat vmcheck/usr/bin/ls; echo more stuff) > vmcheck/usr/bin/ls.new
|
||||
mv vmcheck/usr/bin/ls{.new,}
|
||||
ostree commit -b vmcheck --tree=dir=vmcheck --link-checkout-speedup
|
||||
rm vmcheck -rf
|
||||
EOF
|
||||
vm_cmdfile t.sh
|
||||
}
|
||||
generate_upgrade
|
||||
# And remove the pending deployment so that our origin is now the booted
|
||||
vm_rpmostree cleanup -p
|
||||
vm_rpmostree upgrade
|
||||
vm_assert_status_jq '.deployments|length == 3' '.deployments[0]["live-replaced"]|not' \
|
||||
'.deployments[1]["live-replaced"]'
|
||||
|
||||
echo "ok livefs not carried over across upgrades"
|
||||
|
||||
reset
|
||||
generate_upgrade
|
||||
vm_rpmostree upgrade
|
||||
vm_assert_status_jq '.deployments|length == 2' '.deployments[0]["live-replaced"]|not' \
|
||||
'.deployments[1]["live-replaced"]|not'
|
||||
if vm_rpmostree ex livefs -n &> livefs-analysis.txt; then
|
||||
assert_not_reached "livefs succeeded?"
|
||||
fi
|
||||
vm_assert_status_jq '.deployments|length == 2' '.deployments[0]["live-replaced"]|not' \
|
||||
'.deployments[1]["live-replaced"]|not'
|
||||
assert_file_has_content livefs-analysis.txt 'live updates not currently supported for modifications'
|
||||
echo "ok no modifications"
|
Loading…
Reference in New Issue
Block a user