diff --git a/Makefile-man.am b/Makefile-man.am
index 096560b1ad..f5180d701e 100644
--- a/Makefile-man.am
+++ b/Makefile-man.am
@@ -30,6 +30,7 @@ ostree-admin-init-fs.1 ostree-admin-instutil.1 ostree-admin-stateroot-init.1 ost
ostree-admin-status.1 ostree-admin-set-origin.1 ostree-admin-switch.1 \
ostree-admin-undeploy.1 ostree-admin-upgrade.1 ostree-admin-unlock.1 \
ostree-admin-pin.1 ostree-admin-post-copy.1 ostree-admin-set-default.1 \
+ostree-admin-unlock-finalization.1 \
ostree-admin.1 ostree-cat.1 ostree-checkout.1 ostree-checksum.1 \
ostree-commit.1 ostree-create-usb.1 ostree-export.1 \
ostree-config.1 ostree-diff.1 ostree-find-remotes.1 ostree-fsck.1 \
diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt
index 7a8966421f..3fde6e6fd9 100644
--- a/apidoc/ostree-sections.txt
+++ b/apidoc/ostree-sections.txt
@@ -191,6 +191,7 @@ ostree_deployment_get_origin_relpath
ostree_deployment_get_unlocked
ostree_deployment_is_pinned
ostree_deployment_is_staged
+ostree_deployment_is_finalization_locked
ostree_deployment_set_index
ostree_deployment_set_bootserial
ostree_deployment_set_bootconfig
@@ -591,6 +592,7 @@ ostree_sysroot_write_origin_file
ostree_sysroot_stage_tree
ostree_sysroot_stage_tree_with_options
ostree_sysroot_stage_overlay_initrd
+ostree_sysroot_unlock_finalization
ostree_sysroot_deploy_tree
ostree_sysroot_deploy_tree_with_options
ostree_sysroot_get_merge_deployment
diff --git a/man/ostree-admin-unlock-finalization.xml b/man/ostree-admin-unlock-finalization.xml
new file mode 100644
index 0000000000..f49e098152
--- /dev/null
+++ b/man/ostree-admin-unlock-finalization.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+ ostree admin unlock-finalization
+ OSTree
+
+
+
+ Developer
+ Colin
+ Walters
+ walters@verbum.org
+
+
+
+
+
+ ostree admin unlock-finalization
+ 1
+
+
+
+ ostree-admin-unlock-finalization
+ Ensure staged deployment will be queued for next boot
+
+
+
+
+ ostree admin unlock-finalization OPTIONS
+
+
+
+
+ Description
+
+
+ This command requires a staged deployment. If it has been "finalization locked",
+ which means it will not be queued for the next boot by default, then this command
+ will unlock it. If it is already finalization-unlocked, then this command will
+ be a no-op.
+
+
+
+
+ Options
+
+
+
+ ="PATH"
+
+
+ Path to the system to use rather than the current one.
+
+
+
+
+
diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym
index 02d2ac2950..5236cc9c49 100644
--- a/src/libostree/libostree-devel.sym
+++ b/src/libostree/libostree-devel.sym
@@ -23,6 +23,8 @@
LIBOSTREE_2023.8 {
global:
ostree_sysroot_update_post_copy;
+ ostree_deployment_is_finalization_locked;
+ ostree_sysroot_unlock_finalization;
} LIBOSTREE_2023.4;
/* Stub section for the stable release *after* this development one; don't
diff --git a/src/libostree/ostree-deployment-private.h b/src/libostree/ostree-deployment-private.h
index 2a28bffd92..f6766c39bd 100644
--- a/src/libostree/ostree-deployment-private.h
+++ b/src/libostree/ostree-deployment-private.h
@@ -51,6 +51,7 @@ struct _OstreeDeployment
GKeyFile *origin;
OstreeDeploymentUnlockedState unlocked;
gboolean staged;
+ gboolean finalization_locked;
char **overlay_initrds;
char *overlay_initrds_id;
};
diff --git a/src/libostree/ostree-deployment.c b/src/libostree/ostree-deployment.c
index 1480d74656..8be2fdd507 100644
--- a/src/libostree/ostree-deployment.c
+++ b/src/libostree/ostree-deployment.c
@@ -461,3 +461,18 @@ ostree_deployment_is_staged (OstreeDeployment *self)
{
return self->staged;
}
+
+/**
+ * ostree_deployment_is_finalization_locked:
+ * @self: Deployment
+ *
+ * Returns: `TRUE` if deployment is queued to be "finalized" at shutdown time, but requires
+ * additional action.
+ *
+ * Since: 2023.8
+ */
+gboolean
+ostree_deployment_is_finalization_locked (OstreeDeployment *self)
+{
+ return self->finalization_locked;
+}
diff --git a/src/libostree/ostree-deployment.h b/src/libostree/ostree-deployment.h
index 0d4a5d7b02..0536d9810c 100644
--- a/src/libostree/ostree-deployment.h
+++ b/src/libostree/ostree-deployment.h
@@ -71,6 +71,8 @@ GKeyFile *ostree_deployment_get_origin (OstreeDeployment *self);
_OSTREE_PUBLIC
gboolean ostree_deployment_is_staged (OstreeDeployment *self);
_OSTREE_PUBLIC
+gboolean ostree_deployment_is_finalization_locked (OstreeDeployment *self);
+_OSTREE_PUBLIC
gboolean ostree_deployment_is_pinned (OstreeDeployment *self);
_OSTREE_PUBLIC
diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c
index 116143c160..936df29192 100644
--- a/src/libostree/ostree-sysroot-deploy.c
+++ b/src/libostree/ostree-sysroot-deploy.c
@@ -3657,6 +3657,10 @@ ostree_sysroot_stage_tree_with_options (OstreeSysroot *self, const char *osname,
g_autoptr (GVariantBuilder) builder = g_variant_builder_new ((GVariantType *)"a{sv}");
g_variant_builder_add (builder, "{sv}", "target", serialize_deployment_to_variant (deployment));
+ if (opts->locked)
+ g_variant_builder_add (builder, "{sv}", _OSTREE_SYSROOT_STAGED_KEY_LOCKED,
+ g_variant_new_boolean (TRUE));
+
if (merge_deployment)
g_variant_builder_add (builder, "{sv}", "merge-deployment",
serialize_deployment_to_variant (merge_deployment));
@@ -3706,6 +3710,54 @@ ostree_sysroot_stage_tree_with_options (OstreeSysroot *self, const char *osname,
return TRUE;
}
+/**
+ * ostree_sysroot_unlock_finalization:
+ * @self: Sysroot
+ * @deployment: Deployment which must be staged and finalization locked.
+ * @error: Error
+ *
+ * Given the target deployment (which must be the staged deployment) this API
+ * will set it to a "finalization unlocked" state.
+ *
+ * Since: 2023.8
+ */
+_OSTREE_PUBLIC
+gboolean
+ostree_sysroot_unlock_finalization (OstreeSysroot *self, OstreeDeployment *deployment,
+ GError **error)
+{
+ GCancellable *cancellable = NULL;
+ g_assert (ostree_deployment_is_staged (deployment));
+ g_assert (ostree_deployment_is_finalization_locked (deployment));
+
+ /* Read the staged state from disk */
+ glnx_autofd int fd = -1;
+ if (!glnx_openat_rdonly (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED, TRUE, &fd, error))
+ return FALSE;
+
+ g_autoptr (GBytes) contents = ot_fd_readall_or_mmap (fd, 0, error);
+ if (!contents)
+ return FALSE;
+ g_autoptr (GVariant) staged_deployment_data
+ = g_variant_new_from_bytes ((GVariantType *)"a{sv}", contents, TRUE);
+ g_autoptr (GVariantDict) staged_deployment_dict = g_variant_dict_new (staged_deployment_data);
+
+ g_variant_dict_insert (staged_deployment_dict, _OSTREE_SYSROOT_STAGED_KEY_LOCKED, "b", FALSE);
+ g_autoptr (GVariant) new_staged_deployment_data = g_variant_dict_end (staged_deployment_dict);
+
+ if (!glnx_file_replace_contents_at (fd, _OSTREE_SYSROOT_RUNSTATE_STAGED,
+ g_variant_get_data (new_staged_deployment_data),
+ g_variant_get_size (new_staged_deployment_data),
+ GLNX_FILE_REPLACE_NODATASYNC, cancellable, error))
+ return FALSE;
+
+ /* Delete the lock if there was any. */
+ if (!ot_ensure_unlinked_at (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED, error))
+ return FALSE;
+
+ return TRUE;
+}
+
/* Invoked at shutdown time by ostree-finalize-staged.service */
static gboolean
_ostree_sysroot_finalize_staged_inner (OstreeSysroot *self, GCancellable *cancellable,
@@ -3722,11 +3774,22 @@ _ostree_sysroot_finalize_staged_inner (OstreeSysroot *self, GCancellable *cancel
}
/* Check if finalization is locked. */
- if (!glnx_fstatat_allow_noent (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED, NULL, 0, error))
- return FALSE;
- if (errno == 0)
+ gboolean locked = false;
+ (void)g_variant_lookup (self->staged_deployment_data, _OSTREE_SYSROOT_STAGED_KEY_LOCKED, "b",
+ &locked);
+ if (locked)
+ g_debug ("staged is locked via metadata");
+ else
+ {
+ if (!glnx_fstatat_allow_noent (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED, NULL, 0,
+ error))
+ return FALSE;
+ if (errno == 0)
+ locked = TRUE;
+ }
+ if (locked)
{
- ot_journal_print (LOG_INFO, "Not finalizing; found " _OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED);
+ ot_journal_print (LOG_INFO, "Not finalizing; deployment is locked for finalization");
return TRUE;
}
diff --git a/src/libostree/ostree-sysroot-private.h b/src/libostree/ostree-sysroot-private.h
index 8e6945b293..5be07c24ba 100644
--- a/src/libostree/ostree-sysroot-private.h
+++ b/src/libostree/ostree-sysroot-private.h
@@ -93,6 +93,9 @@ struct OstreeSysroot
OstreeSysrootDebugFlags debug_flags;
};
+/* Key in staged deployment variant for finalization locking */
+#define _OSTREE_SYSROOT_STAGED_KEY_LOCKED "locked"
+
#define OSTREE_SYSROOT_LOCKFILE "ostree/lock"
/* We keep some transient state in /run */
#define _OSTREE_SYSROOT_RUNSTATE_STAGED "/run/ostree/staged-deployment"
diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c
index 91b63f945a..62adc6221c 100644
--- a/src/libostree/ostree-sysroot.c
+++ b/src/libostree/ostree-sysroot.c
@@ -1106,6 +1106,8 @@ _ostree_sysroot_reload_staged (OstreeSysroot *self, GError **error)
* canonical "staged_deployment" reference.
*/
self->staged_deployment->staged = TRUE;
+ (void)g_variant_dict_lookup (staged_deployment_dict, _OSTREE_SYSROOT_STAGED_KEY_LOCKED,
+ "b", &self->staged_deployment->finalization_locked);
}
}
diff --git a/src/libostree/ostree-sysroot.h b/src/libostree/ostree-sysroot.h
index b7ba6ac97b..4ddcd61d3b 100644
--- a/src/libostree/ostree-sysroot.h
+++ b/src/libostree/ostree-sysroot.h
@@ -178,7 +178,10 @@ gboolean ostree_sysroot_stage_overlay_initrd (OstreeSysroot *self, int fd, char
typedef struct
{
- gboolean unused_bools[8];
+ /* If set to true, then this deployment will be staged but "locked" and not automatically applied
+ * on reboot. */
+ gboolean locked;
+ gboolean unused_bools[7];
int unused_ints[8];
char **override_kernel_argv;
char **overlay_initrds;
@@ -215,6 +218,10 @@ gboolean ostree_sysroot_stage_tree_with_options (OstreeSysroot *self, const char
OstreeDeployment **out_new_deployment,
GCancellable *cancellable, GError **error);
+_OSTREE_PUBLIC
+gboolean ostree_sysroot_unlock_finalization (OstreeSysroot *self, OstreeDeployment *deployment,
+ GError **error);
+
_OSTREE_PUBLIC
gboolean ostree_sysroot_deployment_set_mutable (OstreeSysroot *self, OstreeDeployment *deployment,
gboolean is_mutable, GCancellable *cancellable,
diff --git a/src/ostree/ot-admin-builtin-deploy.c b/src/ostree/ot-admin-builtin-deploy.c
index c0faaab908..69a543362f 100644
--- a/src/ostree/ot-admin-builtin-deploy.c
+++ b/src/ostree/ot-admin-builtin-deploy.c
@@ -60,7 +60,7 @@ static GOptionEntry options[] = {
"Do not apply configuration (/etc and kernel arguments) from booted deployment", NULL },
{ "retain", 0, 0, G_OPTION_ARG_NONE, &opt_retain, "Do not delete previous deployments", NULL },
{ "stage", 0, 0, G_OPTION_ARG_NONE, &opt_stage, "Complete deployment at OS shutdown", NULL },
- { "lock-finalization", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_lock_finalization,
+ { "lock-finalization", 0, 0, G_OPTION_ARG_NONE, &opt_lock_finalization,
"Prevent automatic deployment finalization on shutdown", NULL },
{ "retain-pending", 0, 0, G_OPTION_ARG_NONE, &opt_retain_pending,
"Do not delete pending deployments", NULL },
@@ -123,6 +123,10 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat
return FALSE;
}
+ // Locking implies staging
+ if (opt_lock_finalization)
+ opt_stage = TRUE;
+
const char *refspec = argv[1];
OstreeRepo *repo = ostree_sysroot_repo (sysroot);
@@ -236,6 +240,7 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat
g_auto (GStrv) kargs_strv = kargs ? ostree_kernel_args_to_strv (kargs) : NULL;
OstreeSysrootDeployTreeOpts opts = {
+ .locked = opt_lock_finalization,
.override_kernel_argv = kargs_strv,
.overlay_initrds = overlay_initrd_chksums ? (char **)overlay_initrd_chksums->pdata : NULL,
};
@@ -247,9 +252,11 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat
return glnx_throw (error, "--stage cannot currently be combined with --retain arguments");
if (opt_not_as_default)
return glnx_throw (error, "--stage cannot currently be combined with --not-as-default");
- /* touch file *before* we stage to avoid races */
+ /* For compatibility with older versions of ostree, also write this legacy file.
+ * This can likely be safely deleted in the middle of 2024 say. */
if (opt_lock_finalization)
{
+ g_debug ("Writing legacy finalization lockfile");
if (!glnx_shutil_mkdir_p_at (AT_FDCWD,
dirname (strdupa (_OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED)),
0755, cancellable, error))
@@ -262,7 +269,7 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat
_OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED);
}
/* use old API if we can to exercise it in CI */
- if (!overlay_initrd_chksums)
+ if (!(overlay_initrd_chksums || opt_lock_finalization))
{
if (!ostree_sysroot_stage_tree (sysroot, opt_osname, revision, origin, merge_deployment,
kargs_strv, &new_deployment, cancellable, error))
diff --git a/src/ostree/ot-admin-builtin-status.c b/src/ostree/ot-admin-builtin-status.c
index 3addfd1615..f9fa19b85c 100644
--- a/src/ostree/ot-admin-builtin-status.c
+++ b/src/ostree/ot-admin-builtin-status.c
@@ -98,7 +98,9 @@ deployment_print_status (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploym
GKeyFile *origin = ostree_deployment_get_origin (deployment);
const char *deployment_status = "";
- if (ostree_deployment_is_staged (deployment))
+ if (ostree_deployment_is_finalization_locked (deployment))
+ deployment_status = " (finalization locked)";
+ else if (ostree_deployment_is_staged (deployment))
deployment_status = " (staged)";
else if (is_pending)
deployment_status = " (pending)";
diff --git a/src/ostree/ot-admin-builtin-unlock-finalization.c b/src/ostree/ot-admin-builtin-unlock-finalization.c
new file mode 100644
index 0000000000..7b3b16c718
--- /dev/null
+++ b/src/ostree/ot-admin-builtin-unlock-finalization.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.0+
+ *
+ * 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, see .
+ */
+
+#include "config.h"
+
+#include "ostree-sysroot-private.h"
+
+#include "ostree.h"
+#include "ot-admin-builtins.h"
+#include "ot-admin-functions.h"
+#include "ot-main.h"
+#include "otutil.h"
+
+#include
+
+static GOptionEntry options[] = { { NULL } };
+
+gboolean
+ot_admin_builtin_unlock_finalization (int argc, char **argv, OstreeCommandInvocation *invocation,
+ GCancellable *cancellable, GError **error)
+{
+ g_autoptr (GOptionContext) context = g_option_context_new ("");
+
+ g_autoptr (OstreeSysroot) sysroot = NULL;
+ if (!ostree_admin_option_context_parse (context, options, &argc, &argv,
+ OSTREE_ADMIN_BUILTIN_FLAG_SUPERUSER, invocation, &sysroot,
+ cancellable, error))
+ return FALSE;
+
+ OstreeDeployment *staged = ostree_sysroot_get_staged_deployment (sysroot);
+ if (!staged)
+ return glnx_throw (error, "No staged deployment");
+ if (!ostree_deployment_is_finalization_locked (staged))
+ {
+ // Make this not an error to allow
+ g_print ("Staged deployment is already prepared for finalization\n");
+ return 0;
+ }
+
+ return ostree_sysroot_unlock_finalization (sysroot, staged, error);
+}
diff --git a/src/ostree/ot-admin-builtins.h b/src/ostree/ot-admin-builtins.h
index 1f94e414d9..c239aea87c 100644
--- a/src/ostree/ot-admin-builtins.h
+++ b/src/ostree/ot-admin-builtins.h
@@ -49,6 +49,7 @@ BUILTINPROTO (switch);
BUILTINPROTO (upgrade);
BUILTINPROTO (kargs);
BUILTINPROTO (post_copy);
+BUILTINPROTO (unlock_finalization);
#undef BUILTINPROTO
diff --git a/tests/kolainst/destructive/staged-deploy.sh b/tests/kolainst/destructive/staged-deploy.sh
index ff6f8d7a8d..b9e97253c7 100755
--- a/tests/kolainst/destructive/staged-deploy.sh
+++ b/tests/kolainst/destructive/staged-deploy.sh
@@ -83,13 +83,15 @@ EOF
test '!' -f /run/ostree/staged-deployment
test '!' -f /run/ostree/staged-deployment
- ostree admin deploy --stage staged-deploy --lock-finalization
+ ostree admin status > status.txt
+ assert_not_file_has_content status.txt 'finalization locked'
+ ostree admin deploy staged-deploy --lock-finalization
+ ostree admin status > status.txt
+ assert_file_has_content status.txt 'finalization locked'
test -f /run/ostree/staged-deployment
- test -f /run/ostree/staged-deployment-locked
# check that we can cleanup the staged deployment
ostree admin undeploy 0
test ! -f /run/ostree/staged-deployment
- test ! -f /run/ostree/staged-deployment-locked
echo "ok cleanup staged"
# And verify that re-staging cleans the previous lock