This commit is contained in:
Rafael F. 2024-12-20 14:44:04 -05:00 committed by GitHub
commit 4ea5e18d03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 148 additions and 28 deletions

View File

@ -17,6 +17,7 @@
#pragma once #pragma once
#include "ostree.h"
#include "ostree-deployment.h" #include "ostree-deployment.h"
G_BEGIN_DECLS G_BEGIN_DECLS
@ -54,9 +55,12 @@ struct _OstreeDeployment
gboolean finalization_locked; gboolean finalization_locked;
char **overlay_initrds; char **overlay_initrds;
char *overlay_initrds_id; char *overlay_initrds_id;
gchar *version;
gboolean version_is_cached;
}; };
void _ostree_deployment_set_bootcsum (OstreeDeployment *self, const char *bootcsum); void _ostree_deployment_set_bootcsum (OstreeDeployment *self, const char *bootcsum);
const char *_ostree_deployment_get_version (OstreeDeployment *self, OstreeRepo *repo);
void _ostree_deployment_set_overlay_initrds (OstreeDeployment *self, char **overlay_initrds); void _ostree_deployment_set_overlay_initrds (OstreeDeployment *self, char **overlay_initrds);

View File

@ -85,6 +85,31 @@ ostree_deployment_get_bootserial (OstreeDeployment *self)
return self->bootserial; return self->bootserial;
} }
const char *
_ostree_deployment_get_version (OstreeDeployment *self,
OstreeRepo *repo)
{
g_return_val_if_fail (repo != NULL, NULL);
if (!self->version_is_cached)
{
/* Try extracting a version for this deployment. */
const gchar *csum = ostree_deployment_get_csum (self);
g_autoptr(GVariant) variant = NULL;
if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, csum,
&variant, NULL))
return NULL;
g_autoptr(GVariant) metadata = g_variant_get_child_value (variant, 0);
g_variant_lookup (metadata, OSTREE_COMMIT_META_KEY_VERSION, "s", &self->version);
self->version_is_cached = TRUE;
}
return self->version;
}
/** /**
* ostree_deployment_get_bootconfig: * ostree_deployment_get_bootconfig:
* @self: Deployment * @self: Deployment
@ -255,6 +280,8 @@ ostree_deployment_clone (OstreeDeployment *self)
OstreeDeployment *ret = ostree_deployment_new ( OstreeDeployment *ret = ostree_deployment_new (
self->index, self->osname, self->csum, self->deployserial, self->bootcsum, self->bootserial); self->index, self->osname, self->csum, self->deployserial, self->bootcsum, self->bootserial);
ret->version = g_strdup (self->version);
new_bootconfig = ostree_bootconfig_parser_clone (self->bootconfig); new_bootconfig = ostree_bootconfig_parser_clone (self->bootconfig);
ostree_deployment_set_bootconfig (ret, new_bootconfig); ostree_deployment_set_bootconfig (ret, new_bootconfig);
@ -324,6 +351,7 @@ ostree_deployment_finalize (GObject *object)
g_free (self->osname); g_free (self->osname);
g_free (self->csum); g_free (self->csum);
g_free (self->bootcsum); g_free (self->bootcsum);
g_free (self->version);
g_clear_object (&self->bootconfig); g_clear_object (&self->bootconfig);
g_clear_pointer (&self->origin, g_key_file_unref); g_clear_pointer (&self->origin, g_key_file_unref);
g_strfreev (self->overlay_initrds); g_strfreev (self->overlay_initrds);

View File

@ -2036,24 +2036,9 @@ install_deployment_kernel (OstreeSysroot *sysroot, int new_bootversion,
if (val == NULL) if (val == NULL)
return glnx_throw (error, "No PRETTY_NAME or ID in /etc/os-release"); return glnx_throw (error, "No PRETTY_NAME or ID in /etc/os-release");
g_autofree char *deployment_version = NULL; const gchar *deployment_version = NULL;
if (repo) if (repo)
{ deployment_version = _ostree_deployment_get_version (deployment, repo);
/* Try extracting a version for this deployment. */
const char *csum = ostree_deployment_get_csum (deployment);
g_autoptr (GVariant) variant = NULL;
g_autoptr (GVariant) metadata = NULL;
/* XXX Copying ot_admin_checksum_version() + bits from
* ot-admin-builtin-status.c. Maybe this should be
* public API in libostree? */
if (ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, csum, &variant, NULL))
{
metadata = g_variant_get_child_value (variant, 0);
(void)g_variant_lookup (metadata, OSTREE_COMMIT_META_KEY_VERSION, "s",
&deployment_version);
}
}
/* XXX The SYSLINUX bootloader backend actually parses the title string /* XXX The SYSLINUX bootloader backend actually parses the title string
* (specifically, it looks for the substring "(ostree"), so further * (specifically, it looks for the substring "(ostree"), so further
@ -3840,6 +3825,11 @@ ostree_sysroot_stage_tree_with_options (OstreeSysroot *self, const char *osname,
g_variant_builder_add (builder, "{sv}", "overlay-initrds", g_variant_builder_add (builder, "{sv}", "overlay-initrds",
g_variant_new_strv ((const char *const *)opts->overlay_initrds, -1)); g_variant_new_strv ((const char *const *)opts->overlay_initrds, -1));
/* Proxy across any flags */
if (opts && opts->finalization_flags)
g_variant_builder_add (builder, "{sv}", "write-deployment-flags",
g_variant_new_uint32 ((guint32)opts->finalization_flags));
const char *parent = dirname (strdupa (_OSTREE_SYSROOT_RUNSTATE_STAGED)); const char *parent = dirname (strdupa (_OSTREE_SYSROOT_RUNSTATE_STAGED));
if (!glnx_shutil_mkdir_p_at (AT_FDCWD, parent, 0755, cancellable, error)) if (!glnx_shutil_mkdir_p_at (AT_FDCWD, parent, 0755, cancellable, error))
return FALSE; return FALSE;
@ -4033,14 +4023,14 @@ _ostree_sysroot_finalize_staged_inner (OstreeSysroot *self, GCancellable *cancel
staged->staged = FALSE; staged->staged = FALSE;
g_ptr_array_remove_index (self->deployments, 0); g_ptr_array_remove_index (self->deployments, 0);
/* TODO: Proxy across flags too? OstreeSysrootSimpleWriteDeploymentFlags flags = 0;
* g_variant_lookup (self->staged_deployment_data, "write-deployment-flags", "u", &flags);
* But note that we always use NO_CLEAN to avoid adding more latency at /*
* Note that we always use NO_CLEAN to avoid adding more latency at
* shutdown, and also because e.g. rpm-ostree wants to own the cleanup * shutdown, and also because e.g. rpm-ostree wants to own the cleanup
* process. * process.
*/ */
OstreeSysrootSimpleWriteDeploymentFlags flags flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN;
= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN;
if (!ostree_sysroot_simple_write_deployment (self, ostree_deployment_get_osname (staged), staged, if (!ostree_sysroot_simple_write_deployment (self, ostree_deployment_get_osname (staged), staged,
merge_deployment, flags, cancellable, error)) merge_deployment, flags, cancellable, error))
return FALSE; return FALSE;

View File

@ -1905,6 +1905,9 @@ ostree_sysroot_init_osname (OstreeSysroot *self, const char *osname, GCancellabl
* specified, then no cleanup will be performed after adding the * specified, then no cleanup will be performed after adding the
* deployment. Make sure to call ostree_sysroot_cleanup() sometime * deployment. Make sure to call ostree_sysroot_cleanup() sometime
* later, instead. * later, instead.
*
* If %OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PREVIOUS_VERSION is
* specified, then the previous version will not be garbage collected.
*/ */
gboolean gboolean
ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const char *osname, ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const char *osname,
@ -1920,6 +1923,8 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const char *osna
= (flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PENDING) > 0; = (flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PENDING) > 0;
const gboolean retain_rollback const gboolean retain_rollback
= (flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_ROLLBACK) > 0; = (flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_ROLLBACK) > 0;
const gboolean retain_previous
= (flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PREVIOUS_VERSION) > 0;
gboolean retain = (flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN) > 0; gboolean retain = (flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN) > 0;
g_autoptr (GPtrArray) deployments = ostree_sysroot_get_deployments (sysroot); g_autoptr (GPtrArray) deployments = ostree_sysroot_get_deployments (sysroot);
@ -1941,6 +1946,34 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const char *osna
if (!booted_deployment && !merge_deployment && (retain_pending || retain_rollback)) if (!booted_deployment && !merge_deployment && (retain_pending || retain_rollback))
retain = TRUE; retain = TRUE;
/* tracks current versioned deployment */
OstreeRepo *repo = ostree_sysroot_repo (sysroot);
const gchar *new_version = _ostree_deployment_get_version (new_deployment, repo);
gboolean retained_previous_version = FALSE;
/* we never prune booted and merge deployments, so if they exist and are of a
* different version from `new_version`, they already fulfill the criteria of
* retaining the previous version */
if (booted_deployment)
{
const gchar *booted_version =
_ostree_deployment_get_version (booted_deployment, repo);
retained_previous_version = (g_strcmp0 (booted_version, new_version) != 0);
}
/* checking also that booted and merge are not the same although that's not a
* big deal since we cache the version now (though this will still work in
* the NULL case)
*/
if (!retained_previous_version && merge_deployment &&
!ostree_deployment_equal (merge_deployment, booted_deployment))
{
const gchar *merge_version =
_ostree_deployment_get_version (merge_deployment, repo);
retained_previous_version = (g_strcmp0 (merge_version, new_version) != 0);
}
/* tracks when we come across the booted deployment */ /* tracks when we come across the booted deployment */
gboolean before_booted = TRUE; gboolean before_booted = TRUE;
gboolean before_merge = TRUE; gboolean before_merge = TRUE;
@ -1962,6 +1995,13 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const char *osna
* deployments, fall back on merge deployment */ * deployments, fall back on merge deployment */
const gboolean passed_crossover = booted_deployment ? !before_booted : !before_merge; const gboolean passed_crossover = booted_deployment ? !before_booted : !before_merge;
gboolean is_previous_version = FALSE;
if (passed_crossover && osname_matches && !retained_previous_version)
{
const gchar *version = _ostree_deployment_get_version (deployment, repo);
is_previous_version = (g_strcmp0 (version, new_version) != 0);
}
/* Retain deployment if: /* Retain deployment if:
* - we're explicitly asked to, or * - we're explicitly asked to, or
* - it's pinned * - it's pinned
@ -1974,6 +2014,15 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const char *osna
|| (retain_pending && !passed_crossover) || (is_booted || is_merge) || (retain_pending && !passed_crossover) || (is_booted || is_merge)
|| (retain_rollback && passed_crossover)) || (retain_rollback && passed_crossover))
g_ptr_array_add (new_deployments, g_object_ref (deployment)); g_ptr_array_add (new_deployments, g_object_ref (deployment));
/*
* - we're keeping the previous version deployment
*/
else if (retain_previous && !retained_previous_version && is_previous_version)
{
g_ptr_array_add (new_deployments, g_object_ref (deployment));
/* Just keep one previous version */
retained_previous_version = TRUE;
}
/* add right after booted/merge deployment */ /* add right after booted/merge deployment */
if (!added_new && passed_crossover) if (!added_new && passed_crossover)

View File

@ -176,13 +176,17 @@ _OSTREE_PUBLIC
gboolean ostree_sysroot_stage_overlay_initrd (OstreeSysroot *self, int fd, char **out_checksum, gboolean ostree_sysroot_stage_overlay_initrd (OstreeSysroot *self, int fd, char **out_checksum,
GCancellable *cancellable, GError **error); GCancellable *cancellable, GError **error);
/**
* finalization_flags: only used in the staging path
*/
typedef struct typedef struct
{ {
/* If set to true, then this deployment will be staged but "locked" and not automatically applied /* If set to true, then this deployment will be staged but "locked" and not automatically applied
* on reboot. */ * on reboot. */
gboolean locked; gboolean locked;
gboolean unused_bools[7]; gboolean unused_bools[7];
int unused_ints[8]; int finalization_flags;
int unused_ints[7];
char **override_kernel_argv; char **override_kernel_argv;
char **overlay_initrds; char **overlay_initrds;
gpointer unused_ptrs[6]; gpointer unused_ptrs[6];
@ -259,6 +263,7 @@ typedef enum
OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN = (1 << 2), OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN = (1 << 2),
OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PENDING = (1 << 3), OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PENDING = (1 << 3),
OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_ROLLBACK = (1 << 4), OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_ROLLBACK = (1 << 4),
OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PREVIOUS_VERSION = (1 << 5),
} OstreeSysrootSimpleWriteDeploymentFlags; } OstreeSysrootSimpleWriteDeploymentFlags;
_OSTREE_PUBLIC _OSTREE_PUBLIC

View File

@ -35,6 +35,7 @@ static gboolean opt_stage;
static gboolean opt_lock_finalization; static gboolean opt_lock_finalization;
static gboolean opt_retain_pending; static gboolean opt_retain_pending;
static gboolean opt_retain_rollback; static gboolean opt_retain_rollback;
static gboolean opt_retain_previous;
static gboolean opt_not_as_default; static gboolean opt_not_as_default;
static gboolean opt_no_prune; static gboolean opt_no_prune;
static gboolean opt_no_merge; static gboolean opt_no_merge;
@ -65,6 +66,8 @@ static GOptionEntry options[] = {
"Do not delete pending deployments", NULL }, "Do not delete pending deployments", NULL },
{ "retain-rollback", 0, 0, G_OPTION_ARG_NONE, &opt_retain_rollback, { "retain-rollback", 0, 0, G_OPTION_ARG_NONE, &opt_retain_rollback,
"Do not delete rollback deployments", NULL }, "Do not delete rollback deployments", NULL },
{ "retain-previous-version", 0, 0, G_OPTION_ARG_NONE, &opt_retain_previous,
"Do not delete previous deployment if version differs from new deployment", NULL },
{ "not-as-default", 0, 0, G_OPTION_ARG_NONE, &opt_not_as_default, { "not-as-default", 0, 0, G_OPTION_ARG_NONE, &opt_not_as_default,
"Append rather than prepend new deployment", NULL }, "Append rather than prepend new deployment", NULL },
{ "karg-proc-cmdline", 0, 0, G_OPTION_ARG_NONE, &opt_kernel_proc_cmdline, { "karg-proc-cmdline", 0, 0, G_OPTION_ARG_NONE, &opt_kernel_proc_cmdline,
@ -239,6 +242,7 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat
.locked = opt_lock_finalization, .locked = opt_lock_finalization,
.override_kernel_argv = kargs_strv, .override_kernel_argv = kargs_strv,
.overlay_initrds = overlay_initrd_chksums ? (char **)overlay_initrd_chksums->pdata : NULL, .overlay_initrds = overlay_initrd_chksums ? (char **)overlay_initrd_chksums->pdata : NULL,
.finalization_flags = opt_retain_previous ? OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PREVIOUS_VERSION : 0,
}; };
g_autoptr (OstreeDeployment) new_deployment = NULL; g_autoptr (OstreeDeployment) new_deployment = NULL;
@ -265,7 +269,7 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat
_OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED); _OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED);
} }
/* use old API if we can to exercise it in CI */ /* use old API if we can to exercise it in CI */
if (!(overlay_initrd_chksums || opt_lock_finalization)) if (!(overlay_initrd_chksums || opt_lock_finalization || opt_retain_previous))
{ {
if (!ostree_sysroot_stage_tree (sysroot, opt_osname, revision, origin, merge_deployment, if (!ostree_sysroot_stage_tree (sysroot, opt_osname, revision, origin, merge_deployment,
kargs_strv, &new_deployment, cancellable, error)) kargs_strv, &new_deployment, cancellable, error))
@ -308,6 +312,8 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PENDING; flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PENDING;
if (opt_retain_rollback) if (opt_retain_rollback)
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_ROLLBACK; flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_ROLLBACK;
if (opt_retain_previous)
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PREVIOUS_VERSION;
} }
if (opt_not_as_default) if (opt_not_as_default)

View File

@ -46,7 +46,7 @@ EOF
# xref https://github.com/coreos/coreos-assembler/pull/2814 # xref https://github.com/coreos/coreos-assembler/pull/2814
systemctl mask --now zincati systemctl mask --now zincati
# Create a synthetic commit for upgrade # Create a synthetic commit for upgrade
ostree commit --no-bindings --parent="${commit}" -b staged-deploy -I --consume t ostree commit --no-bindings --parent="${commit}" -b staged-deploy -I --consume t --add-metadata-string=version=foobar
newcommit=$(ostree rev-parse staged-deploy) newcommit=$(ostree rev-parse staged-deploy)
orig_mtime=$(stat -c '%.Y' /sysroot/ostree/deploy) orig_mtime=$(stat -c '%.Y' /sysroot/ostree/deploy)
systemctl show -p SubState ostree-finalize-staged.path | grep -q waiting systemctl show -p SubState ostree-finalize-staged.path | grep -q waiting
@ -169,8 +169,46 @@ EOF
ostree admin undeploy 1 ostree admin undeploy 1
echo "ok staged retained" echo "ok staged retained"
# Deploy a new version
commit=${host_commit}
ostree checkout -H ${commit} t
ostree commit --no-bindings --parent="${commit}" -b same-version -I --consume t --add-metadata-string=version=foobaz
ostree admin deploy same-version --retain-previous-version
# Cleanup refs # Cleanup refs
ostree refs --delete staged-deploy nonstaged-deploy ostree refs --delete staged-deploy nonstaged-deploy same-version
echo "ok cleanup refs"
/tmp/autopkgtest-reboot "3"
;;
"3")
# Check currently deployed versions
rpm-ostree status
# Make a new commit with the same version as the previous reboot
commit=$(rpmostree_query_json '.deployments[0].checksum')
cd /ostree/repo/tmp
ostree checkout -H ${commit} t
ostree commit --no-bindings --parent="${commit}" -b same-version-again -I --consume t --add-metadata-string=version=foobaz
ostree admin deploy same-version-again --retain-previous-version
# Check that previous version was kept
ostree admin status > status.txt
test $(grep -Fce 'Version: ' status.txt) = 3
echo "ok previous version retained"
# Check also for the staging path
rpm-ostree cleanup -p
ostree admin deploy --stage same-version-again --retain-previous-version
ostree admin finalize-staged
# Check that previous version was kept
ostree admin status > status.txt
test $(grep -Fce 'Version: ' status.txt) = 3
echo "ok previous version retained during stage"
# Cleanup refs
ostree refs --delete same-version-again
echo "ok cleanup refs" echo "ok cleanup refs"
# Now finally, try breaking staged updates and verify that ostree-boot-complete fails on the next boot # Now finally, try breaking staged updates and verify that ostree-boot-complete fails on the next boot
@ -193,9 +231,9 @@ WantedBy=multi-user.target
EOF EOF
systemctl enable hackaround-cosa-systemd-unit-checks.service systemctl enable hackaround-cosa-systemd-unit-checks.service
/tmp/autopkgtest-reboot "3" /tmp/autopkgtest-reboot "4"
;; ;;
"3") "4")
assert_file_has_content /run/ostree-boot-complete-status.txt 'error: ostree-finalize-staged.service failed on previous boot.*Operation not permitted' assert_file_has_content /run/ostree-boot-complete-status.txt 'error: ostree-finalize-staged.service failed on previous boot.*Operation not permitted'
echo "ok boot-complete.service" echo "ok boot-complete.service"
;; ;;