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:
Colin Walters 2017-02-28 17:16:48 -05:00 committed by Atomic Bot
parent 376a2cc3f5
commit 95e9aa4284
22 changed files with 1286 additions and 10 deletions

View File

@ -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 \

View File

@ -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 \

View File

@ -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.

View File

@ -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,

View 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;
}

View File

@ -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

View File

@ -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

View File

@ -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">

View File

@ -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;
}

View File

@ -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,

View File

@ -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;

View File

@ -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 =

View File

@ -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;
}
/* ---------------------------------------------------------------------------------------------------- */

View 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;
}

View File

@ -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 };

View File

@ -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);

View File

@ -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>

View File

@ -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)

View File

@ -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);

View 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

View File

@ -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
View 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"