summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorColin Walters <walters@verbum.org>2018-02-22 15:27:59 -0500
committerAtomic Bot <atomic-devel@projectatomic.io>2018-04-12 14:55:12 +0000
commiteb506c759c666af2461f1ba3dda4e31ea49ebc41 (patch)
treeec78dcc19982e3b14545ad2b22902a85c9a7e4ef /src
parentff50495f67a95c9b2fef5f9b84bc91469a46eb27 (diff)
downloadostree-eb506c759c666af2461f1ba3dda4e31ea49ebc41.tar.gz
Add concept of "staged" deployment
Add API to write a deployment state to `/run/ostree/staged-deployment`, along with a systemd service which runs at shutdown time. This is a big change to the ostree model for hosts, but it closes a longstanding set of bugs; many, many people have hit the "losing changes in /etc" problem. It also avoids the other problem of racing with programs that modify `/etc` such as LVM backups: https://bugzilla.redhat.com/show_bug.cgi?id=1365297 We need this in particular to go to a full-on model for automatically updated host systems where (like a dual-partition model) everything is fully prepared and the reboot can be taken asynchronously. Closes: https://github.com/ostreedev/ostree/issues/545 Closes: #1503 Approved by: jlebon
Diffstat (limited to 'src')
-rw-r--r--src/boot/ostree-finalize-staged.service36
-rw-r--r--src/libostree/libostree-devel.sym3
-rw-r--r--src/libostree/ostree-cmdprivate.c5
-rw-r--r--src/libostree/ostree-cmdprivate.h1
-rw-r--r--src/libostree/ostree-deployment-private.h2
-rw-r--r--src/libostree/ostree-deployment.c13
-rw-r--r--src/libostree/ostree-deployment.h3
-rw-r--r--src/libostree/ostree-sysroot-cleanup.c9
-rw-r--r--src/libostree/ostree-sysroot-deploy.c409
-rw-r--r--src/libostree/ostree-sysroot-private.h21
-rw-r--r--src/libostree/ostree-sysroot.c98
-rw-r--r--src/libostree/ostree-sysroot.h13
-rw-r--r--src/ostree/ot-admin-builtin-deploy.c56
-rw-r--r--src/ostree/ot-admin-builtin-finalize-staged.c58
-rw-r--r--src/ostree/ot-admin-builtin-status.c14
-rw-r--r--src/ostree/ot-admin-builtins.h1
-rw-r--r--src/ostree/ot-builtin-admin.c3
17 files changed, 639 insertions, 106 deletions
diff --git a/src/boot/ostree-finalize-staged.service b/src/boot/ostree-finalize-staged.service
new file mode 100644
index 00000000..570138cd
--- /dev/null
+++ b/src/boot/ostree-finalize-staged.service
@@ -0,0 +1,36 @@
+# Copyright (C) 2018 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.
+
+# For some implementation discussion, see:
+# https://lists.freedesktop.org/archives/systemd-devel/2018-March/040557.html
+[Unit]
+Description=OSTree Finalize Staged Deployment
+ConditionPathExists=/run/ostree-booted
+DefaultDependencies=no
+
+RequiresMountsFor=/sysroot
+After=basic.target
+Before=multi-user.target final.target
+Conflicts=final.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStop=/usr/bin/ostree admin finalize-staged
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym
index 3377ae12..07e11cb6 100644
--- a/src/libostree/libostree-devel.sym
+++ b/src/libostree/libostree-devel.sym
@@ -19,6 +19,9 @@
/* Add new symbols here. Release commits should copy this section into -released.sym. */
LIBOSTREE_2018.5 {
+ ostree_sysroot_stage_tree;
+ ostree_sysroot_get_staged_deployment;
+ ostree_deployment_is_staged;
} LIBOSTREE_2018.3;
/* Stub section for the stable release *after* this development one; don't
diff --git a/src/libostree/ostree-cmdprivate.c b/src/libostree/ostree-cmdprivate.c
index 49d3f5e5..de82521c 100644
--- a/src/libostree/ostree-cmdprivate.c
+++ b/src/libostree/ostree-cmdprivate.c
@@ -26,7 +26,7 @@
#include "ostree-core-private.h"
#include "ostree-repo-pull-private.h"
#include "ostree-repo-static-delta-private.h"
-#include "ostree-sysroot.h"
+#include "ostree-sysroot-private.h"
#include "ostree-bootloader-grub2.h"
#include "otutil.h"
@@ -52,7 +52,8 @@ ostree_cmd__private__ (void)
_ostree_repo_static_delta_dump,
_ostree_repo_static_delta_query_exists,
_ostree_repo_static_delta_delete,
- _ostree_repo_verify_bindings
+ _ostree_repo_verify_bindings,
+ _ostree_sysroot_finalize_staged,
};
return &table;
diff --git a/src/libostree/ostree-cmdprivate.h b/src/libostree/ostree-cmdprivate.h
index 1ac5a1c8..592157bf 100644
--- a/src/libostree/ostree-cmdprivate.h
+++ b/src/libostree/ostree-cmdprivate.h
@@ -34,6 +34,7 @@ typedef struct {
gboolean (* ostree_static_delta_query_exists) (OstreeRepo *repo, const char *delta_id, gboolean *out_exists, GCancellable *cancellable, GError **error);
gboolean (* ostree_static_delta_delete) (OstreeRepo *repo, const char *delta_id, GCancellable *cancellable, GError **error);
gboolean (* ostree_repo_verify_bindings) (const char *collection_id, const char *ref_name, GVariant *commit, GError **error);
+ gboolean (* ostree_finalize_staged) (OstreeSysroot *sysroot, GCancellable *cancellable, GError **error);
} OstreeCmdPrivateVTable;
/* Note this not really "public", we just export the symbol, but not the header */
diff --git a/src/libostree/ostree-deployment-private.h b/src/libostree/ostree-deployment-private.h
index 114e2f63..ad77317d 100644
--- a/src/libostree/ostree-deployment-private.h
+++ b/src/libostree/ostree-deployment-private.h
@@ -36,6 +36,7 @@ G_BEGIN_DECLS
* @bootconfig: Bootloader configuration
* @origin: How to construct an upgraded version of this tree
* @unlocked: The unlocked state
+ * @staged: TRUE iff this deployment is staged
*/
struct _OstreeDeployment
{
@@ -50,6 +51,7 @@ struct _OstreeDeployment
OstreeBootconfigParser *bootconfig;
GKeyFile *origin;
OstreeDeploymentUnlockedState unlocked;
+ gboolean staged;
};
void _ostree_deployment_set_bootcsum (OstreeDeployment *self, const char *bootcsum);
diff --git a/src/libostree/ostree-deployment.c b/src/libostree/ostree-deployment.c
index 75a5bd1d..820c2632 100644
--- a/src/libostree/ostree-deployment.c
+++ b/src/libostree/ostree-deployment.c
@@ -339,3 +339,16 @@ ostree_deployment_is_pinned (OstreeDeployment *self)
return FALSE;
return g_key_file_get_boolean (self->origin, OSTREE_ORIGIN_TRANSIENT_GROUP, "pinned", NULL);
}
+
+/**
+ * ostree_deployment_is_staged:
+ * @self: Deployment
+ *
+ * Returns: `TRUE` if deployment should be "finalized" at shutdown time
+ * Since: 2018.3
+ */
+gboolean
+ostree_deployment_is_staged (OstreeDeployment *self)
+{
+ return self->staged;
+}
diff --git a/src/libostree/ostree-deployment.h b/src/libostree/ostree-deployment.h
index 612222a2..756e39d2 100644
--- a/src/libostree/ostree-deployment.h
+++ b/src/libostree/ostree-deployment.h
@@ -73,7 +73,8 @@ OstreeBootconfigParser *ostree_deployment_get_bootconfig (OstreeDeployment *self
_OSTREE_PUBLIC
GKeyFile *ostree_deployment_get_origin (OstreeDeployment *self);
-
+_OSTREE_PUBLIC
+gboolean ostree_deployment_is_staged (OstreeDeployment *self);
_OSTREE_PUBLIC
gboolean ostree_deployment_is_pinned (OstreeDeployment *self);
diff --git a/src/libostree/ostree-sysroot-cleanup.c b/src/libostree/ostree-sysroot-cleanup.c
index 1d46222b..3698767f 100644
--- a/src/libostree/ostree-sysroot-cleanup.c
+++ b/src/libostree/ostree-sysroot-cleanup.c
@@ -308,6 +308,15 @@ cleanup_old_deployments (OstreeSysroot *self,
g_hash_table_replace (active_boot_checksums, bootcsum, bootcsum);
}
+ /* And also the staged deployment, if any */
+ if (self->staged_deployment)
+ {
+ char *deployment_path = ostree_sysroot_get_deployment_dirpath (self, self->staged_deployment);
+ g_hash_table_replace (active_deployment_dirs, deployment_path, deployment_path);
+ char *bootcsum = g_strdup (ostree_deployment_get_bootcsum (self->staged_deployment));
+ g_hash_table_replace (active_boot_checksums, bootcsum, bootcsum);
+ }
+
/* Find all deployment directories, both active and inactive */
g_autoptr(GPtrArray) all_deployment_dirs = NULL;
if (!list_all_deployment_directories (self, &all_deployment_dirs,
diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c
index 927809e9..b593ce38 100644
--- a/src/libostree/ostree-sysroot-deploy.c
+++ b/src/libostree/ostree-sysroot-deploy.c
@@ -684,10 +684,15 @@ selinux_relabel_dir (OstreeSysroot *sysroot,
static gboolean
selinux_relabel_var_if_needed (OstreeSysroot *sysroot,
OstreeSePolicy *sepolicy,
- int os_deploy_dfd,
+ OstreeDeployment *deployment,
GCancellable *cancellable,
GError **error)
{
+ const char *osdeploypath = glnx_strjoina ("ostree/deploy/", ostree_deployment_get_osname (deployment));
+ glnx_autofd int os_deploy_dfd = -1;
+ if (!glnx_opendirat (sysroot->sysroot_fd, osdeploypath, TRUE, &os_deploy_dfd, error))
+ return FALSE;
+
/* This is a bit of a hack; we should change the code at some
* point in the distant future to only create (and label) /var
* when doing a deployment.
@@ -743,12 +748,10 @@ prepare_deployment_etc (OstreeSysroot *sysroot,
OstreeRepo *repo,
OstreeDeployment *deployment,
int deployment_dfd,
- OstreeSePolicy **out_sepolicy,
GCancellable *cancellable,
GError **error)
{
GLNX_AUTO_PREFIX_ERROR ("Preparing /etc", error);
- g_autoptr(OstreeSePolicy) sepolicy = NULL;
struct stat stbuf;
if (!glnx_fstatat_allow_noent (deployment_dfd, "etc", &stbuf, AT_SYMLINK_NOFOLLOW, error))
@@ -781,7 +784,7 @@ prepare_deployment_etc (OstreeSysroot *sysroot,
/* Here, we initialize SELinux policy from the /usr/etc inside
* the root - this is before we've finalized the configuration
* merge into /etc. */
- sepolicy = ostree_sepolicy_new_at (deployment_dfd, cancellable, error);
+ g_autoptr(OstreeSePolicy) sepolicy = ostree_sepolicy_new_at (deployment_dfd, cancellable, error);
if (!sepolicy)
return FALSE;
if (ostree_sepolicy_get_name (sepolicy) != NULL)
@@ -796,8 +799,6 @@ prepare_deployment_etc (OstreeSysroot *sysroot,
}
- if (out_sepolicy)
- *out_sepolicy = g_steal_pointer (&sepolicy);
return TRUE;
}
@@ -831,7 +832,6 @@ write_origin_file_internal (OstreeSysroot *sysroot,
ostree_deployment_get_csum (deployment),
ostree_deployment_get_deployserial (deployment));
-
gsize len;
g_autofree char *contents = g_key_file_to_data (origin, &len, error);
if (!contents)
@@ -2324,46 +2324,47 @@ allocate_deployserial (OstreeSysroot *self,
return TRUE;
}
-/**
- * ostree_sysroot_deploy_tree:
- * @self: Sysroot
- * @osname: (allow-none): osname to use for merge deployment
- * @revision: Checksum to add
- * @origin: (allow-none): Origin to use for upgrades
- * @provided_merge_deployment: (allow-none): Use this deployment for merge path
- * @override_kernel_argv: (allow-none) (array zero-terminated=1) (element-type utf8): Use these as kernel arguments; if %NULL, inherit options from provided_merge_deployment
- * @out_new_deployment: (out): The new deployment path
- * @cancellable: Cancellable
- * @error: Error
- *
- * Check out deployment tree with revision @revision, performing a 3
- * way merge with @provided_merge_deployment for configuration.
+void
+_ostree_deployment_set_bootconfig_from_kargs (OstreeDeployment *deployment,
+ char **override_kernel_argv)
+{
+ /* Create an empty boot configuration; we will merge things into
+ * it as we go.
+ */
+ g_autoptr(OstreeBootconfigParser) bootconfig = ostree_bootconfig_parser_new ();
+ ostree_deployment_set_bootconfig (deployment, bootconfig);
+
+ /* After this, install_deployment_kernel() will set the other boot
+ * options and write it out to disk.
+ */
+ if (override_kernel_argv)
+ {
+ g_autoptr(OstreeKernelArgs) kargs = _ostree_kernel_args_new ();
+ _ostree_kernel_args_append_argv (kargs, override_kernel_argv);
+ g_autofree char *new_options = _ostree_kernel_args_to_string (kargs);
+ ostree_bootconfig_parser_set (bootconfig, "options", new_options);
+ }
+}
+
+/* The first part of writing a deployment. This primarily means doing the
+ * hardlink farm checkout, but we also compute some initial state.
*/
-gboolean
-ostree_sysroot_deploy_tree (OstreeSysroot *self,
- const char *osname,
- const char *revision,
- GKeyFile *origin,
- OstreeDeployment *provided_merge_deployment,
- char **override_kernel_argv,
- OstreeDeployment **out_new_deployment,
- GCancellable *cancellable,
- GError **error)
+static gboolean
+sysroot_initialize_deployment (OstreeSysroot *self,
+ const char *osname,
+ const char *revision,
+ GKeyFile *origin,
+ char **override_kernel_argv,
+ OstreeDeployment **out_new_deployment,
+ GCancellable *cancellable,
+ GError **error)
{
g_return_val_if_fail (osname != NULL || self->booted_deployment != NULL, FALSE);
if (osname == NULL)
osname = ostree_deployment_get_osname (self->booted_deployment);
- const char *osdeploypath = glnx_strjoina ("ostree/deploy/", osname);
- glnx_autofd int os_deploy_dfd = -1;
- if (!glnx_opendirat (self->sysroot_fd, osdeploypath, TRUE, &os_deploy_dfd, error))
- return FALSE;
-
OstreeRepo *repo = ostree_sysroot_repo (self);
- g_autoptr(OstreeDeployment) merge_deployment = NULL;
- if (provided_merge_deployment != NULL)
- merge_deployment = g_object_ref (provided_merge_deployment);
gint new_deployserial;
if (!allocate_deployserial (self, osname, revision, &new_deployserial,
@@ -2388,66 +2389,328 @@ ostree_sysroot_deploy_tree (OstreeSysroot *self,
return FALSE;
_ostree_deployment_set_bootcsum (new_deployment, kernel_layout->bootcsum);
+ _ostree_deployment_set_bootconfig_from_kargs (new_deployment, override_kernel_argv);
- /* Initial empty boot configuration. */
- g_autoptr(OstreeBootconfigParser) bootconfig = ostree_bootconfig_parser_new ();
- ostree_deployment_set_bootconfig (new_deployment, bootconfig);
+ if (!prepare_deployment_etc (self, repo, new_deployment, deployment_dfd,
+ cancellable, error))
+ return FALSE;
- /* Handle kernel arguments. After this, install_deployment_kernel() will set
- * the other boot options and write it out to disk.
- */
- if (override_kernel_argv)
- {
- /* We have an override set, use it */
- g_autoptr(OstreeKernelArgs) kargs = _ostree_kernel_args_new ();
- _ostree_kernel_args_append_argv (kargs, override_kernel_argv);
- g_autofree char *new_options = _ostree_kernel_args_to_string (kargs);
- ostree_bootconfig_parser_set (bootconfig, "options", new_options);
- }
- else if (provided_merge_deployment)
+ ot_transfer_out_value (out_new_deployment, &new_deployment);
+ return TRUE;
+}
+
+static gboolean
+sysroot_finalize_deployment (OstreeSysroot *self,
+ OstreeDeployment *deployment,
+ char **override_kernel_argv,
+ OstreeDeployment *merge_deployment,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree char *deployment_path = ostree_sysroot_get_deployment_dirpath (self, deployment);
+ glnx_autofd int deployment_dfd = -1;
+ if (!glnx_opendirat (self->sysroot_fd, deployment_path, TRUE, &deployment_dfd, error))
+ return FALSE;
+
+ /* Only use the merge if we didn't get an override */
+ if (!override_kernel_argv && merge_deployment)
{
- /* Use the merge options by default */
- OstreeBootconfigParser *merge_bootconfig = ostree_deployment_get_bootconfig (provided_merge_deployment);
+ /* Override the bootloader arguments */
+ OstreeBootconfigParser *merge_bootconfig = ostree_deployment_get_bootconfig (merge_deployment);
if (merge_bootconfig)
{
const char *opts = ostree_bootconfig_parser_get (merge_bootconfig, "options");
- ostree_bootconfig_parser_set (bootconfig, "options", opts);
+ ostree_bootconfig_parser_set (ostree_deployment_get_bootconfig (deployment), "options", opts);
}
- }
- g_autoptr(OstreeSePolicy) sepolicy = NULL;
- if (!prepare_deployment_etc (self, repo, new_deployment, deployment_dfd,
- &sepolicy, cancellable, error))
- return FALSE;
+ }
if (merge_deployment)
{
- if (!merge_configuration_from (self, merge_deployment,
- new_deployment, deployment_dfd,
+ /* And do the /etc merge */
+ if (!merge_configuration_from (self, merge_deployment, deployment, deployment_dfd,
cancellable, error))
return FALSE;
}
- if (!selinux_relabel_var_if_needed (self, sepolicy, os_deploy_dfd,
- cancellable, error))
+ g_autoptr(OstreeSePolicy) sepolicy = ostree_sepolicy_new_at (deployment_dfd, cancellable, error);
+ if (!sepolicy)
return FALSE;
+ if (!selinux_relabel_var_if_needed (self, sepolicy, deployment, cancellable, error))
+ return FALSE;
+
+ /* Rewrite the origin using the final merged selinux config, just to be
+ * conservative about getting the right labels.
+ */
+ if (!write_origin_file_internal (self, sepolicy, deployment,
+ ostree_deployment_get_origin (deployment),
+ GLNX_FILE_REPLACE_NODATASYNC,
+ cancellable, error))
+ return FALSE;
+
+ /* Seal it */
if (!(self->debug_flags & OSTREE_SYSROOT_DEBUG_MUTABLE_DEPLOYMENTS))
{
- if (!ostree_sysroot_deployment_set_mutable (self, new_deployment, FALSE,
+ if (!ostree_sysroot_deployment_set_mutable (self, deployment, FALSE,
cancellable, error))
return FALSE;
}
- /* Don't fsync here, as we assume that's all done in
- * ostree_sysroot_write_deployments().
+ return TRUE;
+}
+
+/**
+ * ostree_sysroot_deploy_tree:
+ * @self: Sysroot
+ * @osname: (allow-none): osname to use for merge deployment
+ * @revision: Checksum to add
+ * @origin: (allow-none): Origin to use for upgrades
+ * @provided_merge_deployment: (allow-none): Use this deployment for merge path
+ * @override_kernel_argv: (allow-none) (array zero-terminated=1) (element-type utf8): Use these as kernel arguments; if %NULL, inherit options from provided_merge_deployment
+ * @out_new_deployment: (out): The new deployment path
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Check out deployment tree with revision @revision, performing a 3
+ * way merge with @provided_merge_deployment for configuration.
+ *
+ * While this API is not deprecated, you most likely want to use the
+ * ostree_sysroot_stage_tree() API.
+ */
+gboolean
+ostree_sysroot_deploy_tree (OstreeSysroot *self,
+ const char *osname,
+ const char *revision,
+ GKeyFile *origin,
+ OstreeDeployment *provided_merge_deployment,
+ char **override_kernel_argv,
+ OstreeDeployment **out_new_deployment,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(OstreeDeployment) deployment = NULL;
+ if (!sysroot_initialize_deployment (self, osname, revision, origin, override_kernel_argv,
+ &deployment, cancellable, error))
+ return FALSE;
+
+ if (!sysroot_finalize_deployment (self, deployment, override_kernel_argv,
+ provided_merge_deployment,
+ cancellable, error))
+ return FALSE;
+
+ *out_new_deployment = g_steal_pointer (&deployment);
+ return TRUE;
+}
+
+/* Serialize information about a deployment to a variant, used by the staging
+ * code.
+ */
+static GVariant *
+serialize_deployment_to_variant (OstreeDeployment *deployment)
+{
+ g_auto(GVariantBuilder) builder = OT_VARIANT_BUILDER_INITIALIZER;
+ g_variant_builder_init (&builder, (GVariantType*)"a{sv}");
+ g_autofree char *name =
+ g_strdup_printf ("%s.%d", ostree_deployment_get_csum (deployment),
+ ostree_deployment_get_deployserial (deployment));
+ g_variant_builder_add (&builder, "{sv}", "name",
+ g_variant_new_string (name));
+ g_variant_builder_add (&builder, "{sv}", "osname",
+ g_variant_new_string (ostree_deployment_get_osname (deployment)));
+ g_variant_builder_add (&builder, "{sv}", "bootcsum",
+ g_variant_new_string (ostree_deployment_get_bootcsum (deployment)));
+
+ return g_variant_builder_end (&builder);
+}
+
+static gboolean
+require_str_key (GVariantDict *dict,
+ const char *name,
+ const char **ret,
+ GError **error)
+{
+ if (!g_variant_dict_lookup (dict, name, "&s", ret))
+ return glnx_throw (error, "Missing key: %s", name);
+ return TRUE;
+}
+
+/* Reverse of the above; convert a variant to a deployment. Note that the
+ * deployment may not actually be present; this should be verified by
+ * higher level code.
+ */
+OstreeDeployment *
+_ostree_sysroot_deserialize_deployment_from_variant (GVariant *v,
+ GError **error)
+{
+ g_autoptr(GVariantDict) dict = g_variant_dict_new (v);
+ const char *name = NULL;
+ if (!require_str_key (dict, "name", &name, error))
+ return FALSE;
+ const char *bootcsum = NULL;
+ if (!require_str_key (dict, "bootcsum", &bootcsum, error))
+ return FALSE;
+ const char *osname = NULL;
+ if (!require_str_key (dict, "osname", &osname, error))
+ return FALSE;
+ g_autofree char *checksum = NULL;
+ gint deployserial;
+ if (!_ostree_sysroot_parse_deploy_path_name (name, &checksum, &deployserial, error))
+ return NULL;
+ return ostree_deployment_new (-1, osname, checksum, deployserial,
+ bootcsum, -1);
+}
+
+
+/**
+ * ostree_sysroot_stage_tree:
+ * @self: Sysroot
+ * @osname: (allow-none): osname to use for merge deployment
+ * @revision: Checksum to add
+ * @origin: (allow-none): Origin to use for upgrades
+ * @merge_deployment: (allow-none): Use this deployment for merge path
+ * @override_kernel_argv: (allow-none) (array zero-terminated=1) (element-type utf8): Use these as kernel arguments; if %NULL, inherit options from provided_merge_deployment
+ * @out_new_deployment: (out): The new deployment path
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Like ostree_sysroot_deploy_tree(), but "finalization" only occurs at OS
+ * shutdown time.
+ */
+gboolean
+ostree_sysroot_stage_tree (OstreeSysroot *self,
+ const char *osname,
+ const char *revision,
+ GKeyFile *origin,
+ OstreeDeployment *merge_deployment,
+ char **override_kernel_argv,
+ OstreeDeployment **out_new_deployment,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* This is a bit of a hack. When adding a new service we have to end up getting
+ * into the presets for downstream distros; see e.g. https://src.fedoraproject.org/rpms/ostree/pull-request/7
+ *
+ * Then again, it's perhaps a bit nicer to only start the service on-demand anyways.
*/
- if (!write_origin_file_internal (self, sepolicy, new_deployment, NULL,
- GLNX_FILE_REPLACE_NODATASYNC,
- cancellable, error))
+ const char *const systemctl_argv[] = {"systemctl", "start", "ostree-finalize-staged.service", NULL};
+ int estatus;
+ if (!g_spawn_sync (NULL, (char**)systemctl_argv, NULL, G_SPAWN_SEARCH_PATH,
+ NULL, NULL, NULL, NULL, &estatus, error))
+ return FALSE;
+ if (!g_spawn_check_exit_status (estatus, error))
+ return FALSE;
+
+ g_autoptr(OstreeDeployment) deployment = NULL;
+ if (!sysroot_initialize_deployment (self, osname, revision, origin, override_kernel_argv,
+ &deployment, cancellable, error))
+ return FALSE;
+
+ /* Write out the origin file using the sepolicy from the non-merged root for
+ * now (i.e. using /usr/etc policy, not /etc); in practice we don't really
+ * expect people to customize the label for it.
+ */
+ { g_autofree char *deployment_path = ostree_sysroot_get_deployment_dirpath (self, deployment);
+ glnx_autofd int deployment_dfd = -1;
+ if (!glnx_opendirat (self->sysroot_fd, deployment_path, FALSE,
+ &deployment_dfd, error))
+ return FALSE;
+ g_autoptr(OstreeSePolicy) sepolicy = ostree_sepolicy_new_at (deployment_dfd, cancellable, error);
+ if (!sepolicy)
+ return FALSE;
+ if (!write_origin_file_internal (self, sepolicy, deployment,
+ ostree_deployment_get_origin (deployment),
+ GLNX_FILE_REPLACE_NODATASYNC,
+ cancellable, error))
+ return FALSE;
+ }
+
+ /* After here we defer action until shutdown. The remaining arguments (merge
+ * deployment, kargs) are serialized to a state file in /run.
+ */
+
+ /* "target" is the staged deployment */
+ g_autoptr(GVariantBuilder) builder = g_variant_builder_new ((GVariantType*)"a{sv}");
+ g_variant_builder_add (builder, "{sv}", "target",
+ serialize_deployment_to_variant (deployment));
+
+ if (merge_deployment)
+ g_variant_builder_add (builder, "{sv}", "merge-deployment",
+ serialize_deployment_to_variant (merge_deployment));
+
+ if (override_kernel_argv)
+ g_variant_builder_add (builder, "{sv}", "kargs",
+ g_variant_new_strv ((const char *const*)override_kernel_argv, -1));
+
+ const char *parent = dirname (strdupa (_OSTREE_SYSROOT_RUNSTATE_STAGED));
+ if (!glnx_shutil_mkdir_p_at (AT_FDCWD, parent, 0755, cancellable, error))
+ return FALSE;
+
+ g_autoptr(GVariant) state = g_variant_ref_sink (g_variant_builder_end (builder));
+ if (!glnx_file_replace_contents_at (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED,
+ g_variant_get_data (state), g_variant_get_size (state),
+ GLNX_FILE_REPLACE_NODATASYNC,
+ cancellable, error))
+ return FALSE;
+
+ /* If we have a previous one, clean it up */
+ if (self->staged_deployment)
+ {
+ if (!_ostree_sysroot_rmrf_deployment (self, self->staged_deployment, cancellable, error))
+ return FALSE;
+ }
+
+ if (!_ostree_sysroot_reload_staged (self, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* Invoked at shutdown time by ostree-finalize-staged.service */
+gboolean
+_ostree_sysroot_finalize_staged (OstreeSysroot *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* It's totally fine if there's no staged deployment; perhaps down the line
+ * though we could teach the ostree cmdline to tell systemd to activate the
+ * service when a staged deployment is created.
+ */
+ if (!self->staged_deployment)
+ return TRUE;
+
+ g_assert (self->staged_deployment_data);
+
+ g_autoptr(OstreeDeployment) merge_deployment = NULL;
+ g_autoptr(GVariant) merge_deployment_v = NULL;
+ if (g_variant_lookup (self->staged_deployment_data, "merge-deployment", "@a{sv}",
+ &merge_deployment_v))
+ {
+ merge_deployment =
+ _ostree_sysroot_deserialize_deployment_from_variant (merge_deployment_v, error);
+ if (!merge_deployment)
+ return FALSE;
+ }
+ g_autofree char **kargs = NULL;
+ g_variant_lookup (self->staged_deployment_data, "kargs", "^a&s", &kargs);
+
+ /* Unlink the staged state now; if we're interrupted in the middle,
+ * we don't want e.g. deal with the partially written /etc merge.
+ */
+ if (!glnx_unlinkat (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED, 0, error))
+ return FALSE;
+
+ if (!sysroot_finalize_deployment (self, self->staged_deployment, NULL, merge_deployment,
+ cancellable, error))
+ return FALSE;
+
+ /* TODO: Proxy across flags too? */
+ OstreeSysrootSimpleWriteDeploymentFlags flags = 0;
+ if (!ostree_sysroot_simple_write_deployment (self, ostree_deployment_get_osname (self->staged_deployment),
+ self->staged_deployment, merge_deployment, flags,
+ cancellable, error))
return FALSE;
- ot_transfer_out_value (out_new_deployment, &new_deployment);
return TRUE;
}
diff --git a/src/libostree/ostree-sysroot-private.h b/src/libostree/ostree-sysroot-private.h
index 01b370e8..a2f3b869 100644
--- a/src/libostree/ostree-sysroot-private.h
+++ b/src/libostree/ostree-sysroot-private.h
@@ -61,6 +61,8 @@ struct OstreeSysroot {
int bootversion;
int subbootversion;
OstreeDeployment *booted_deployment;
+ OstreeDeployment *staged_deployment;
+ GVariant *staged_deployment_data;
struct timespec loaded_ts;
/* Only access through ostree_sysroot_[_get]repo() */
@@ -71,6 +73,7 @@ struct OstreeSysroot {
#define OSTREE_SYSROOT_LOCKFILE "ostree/lock"
/* We keep some transient state in /run */
+#define _OSTREE_SYSROOT_RUNSTATE_STAGED "/run/ostree/staged-deployment"
#define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_DIR "/run/ostree/deployment-state/"
#define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT "unlocked-development"
@@ -105,6 +108,22 @@ _ostree_sysroot_list_deployment_dirs_for_os (int deploydir_dfd,
GCancellable *cancellable,
GError **error);
+void
+_ostree_deployment_set_bootconfig_from_kargs (OstreeDeployment *deployment,
+ char **override_kernel_argv);
+
+gboolean
+_ostree_sysroot_reload_staged (OstreeSysroot *self, GError **error);
+
+gboolean
+_ostree_sysroot_finalize_staged (OstreeSysroot *self,
+ GCancellable *cancellable,
+ GError **error);
+
+OstreeDeployment *
+_ostree_sysroot_deserialize_deployment_from_variant (GVariant *v,
+ GError **error);
+
char *
_ostree_sysroot_get_origin_relpath (GFile *path,
guint32 *out_device,
@@ -118,6 +137,8 @@ _ostree_sysroot_rmrf_deployment (OstreeSysroot *sysroot,
GCancellable *cancellable,
GError **error);
+char * _ostree_sysroot_get_runstate_path (OstreeDeployment *deployment, const char *key);
+
char *_ostree_sysroot_join_lines (GPtrArray *lines);
gboolean _ostree_sysroot_query_bootloader (OstreeSysroot *sysroot,
diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c
index f77d7703..f4a8eade 100644
--- a/src/libostree/ostree-sysroot.c
+++ b/src/libostree/ostree-sysroot.c
@@ -82,6 +82,8 @@ ostree_sysroot_finalize (GObject *object)
g_clear_object (&self->repo);
g_clear_pointer (&self->deployments, g_ptr_array_unref);
g_clear_object (&self->booted_deployment);
+ g_clear_object (&self->staged_deployment);
+ g_clear_pointer (&self->staged_deployment_data, (GDestroyNotify)g_variant_unref);
glnx_release_lock_file (&self->lock);
@@ -584,14 +586,14 @@ parse_bootlink (const char *bootlink,
return TRUE;
}
-static char *
-get_unlocked_development_path (OstreeDeployment *deployment)
+char *
+_ostree_sysroot_get_runstate_path (OstreeDeployment *deployment, const char *key)
{
return g_strdup_printf ("%s%s.%d/%s",
_OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_DIR,
ostree_deployment_get_csum (deployment),
ostree_deployment_get_deployserial (deployment),
- _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT);
+ key);
}
static gboolean
@@ -636,9 +638,10 @@ parse_deployment (OstreeSysroot *self,
return FALSE;
/* See if this is the booted deployment */
+ const gboolean root_is_ostree_booted =
+ (self->ostree_booted && self->root_is_sysroot);
const gboolean looking_for_booted_deployment =
- (self->ostree_booted && self->root_is_sysroot &&
- !self->booted_deployment);
+ (root_is_ostree_booted && !self->booted_deployment);
gboolean is_booted_deployment = FALSE;
if (looking_for_booted_deployment)
{
@@ -665,7 +668,8 @@ parse_deployment (OstreeSysroot *self,
ostree_deployment_set_origin (ret_deployment, origin);
ret_deployment->unlocked = OSTREE_DEPLOYMENT_UNLOCKED_NONE;
- g_autofree char *unlocked_development_path = get_unlocked_development_path (ret_deployment);
+ g_autofree char *unlocked_development_path =
+ _ostree_sysroot_get_runstate_path (ret_deployment, _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT);
struct stat stbuf;
if (lstat (unlocked_development_path, &stbuf) == 0)
ret_deployment->unlocked = OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT;
@@ -789,6 +793,60 @@ ensure_repo (OstreeSysroot *self,
return TRUE;
}
+/* Reload the staged deployment from the file in /run */
+gboolean
+_ostree_sysroot_reload_staged (OstreeSysroot *self,
+ GError **error)
+{
+ GLNX_AUTO_PREFIX_ERROR ("Loading staged deployment", error);
+ const gboolean root_is_ostree_booted =
+ self->ostree_booted && self->root_is_sysroot;
+ if (!root_is_ostree_booted)
+ return TRUE; /* Note early return */
+
+ g_assert (self->booted_deployment);
+
+ g_clear_object (&self->staged_deployment);
+ g_clear_pointer (&self->staged_deployment_data, (GDestroyNotify)g_variant_unref);
+
+ /* Read the staged state from disk */
+ glnx_autofd int fd = -1;
+ if (!ot_openat_ignore_enoent (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED, &fd, error))
+ return FALSE;
+ if (fd != -1)
+ {
+ 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);
+
+ /* Parse it */
+ g_autoptr(GVariant) target = NULL;
+ g_autofree char **kargs = NULL;
+ g_variant_dict_lookup (staged_deployment_dict, "target", "@a{sv}", &target);
+ g_variant_dict_lookup (staged_deployment_dict, "kargs", "^a&s", &kargs);
+ if (target)
+ {
+ self->staged_deployment =
+ _ostree_sysroot_deserialize_deployment_from_variant (target, error);
+ if (!self->staged_deployment)
+ return FALSE;
+ _ostree_deployment_set_bootconfig_from_kargs (self->staged_deployment, kargs);
+ self->staged_deployment_data = g_steal_pointer (&staged_deployment_data);
+ /* We set this flag for ostree_deployment_is_staged() because that API
+ * doesn't have access to the sysroot, which currently has the
+ * canonical "staged_deployment" reference.
+ */
+ self->staged_deployment->staged = TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
gboolean
ostree_sysroot_load_if_changed (OstreeSysroot *self,
gboolean *out_changed,
@@ -857,6 +915,7 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self,
g_clear_pointer (&self->deployments, g_ptr_array_unref);
g_clear_object (&self->booted_deployment);
+ g_clear_object (&self->staged_deployment);
self->bootversion = -1;
self->subbootversion = -1;
@@ -880,17 +939,23 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self,
}
}
- if (self->ostree_booted && self->root_is_sysroot
- && !self->booted_deployment)
+ const gboolean root_is_ostree_booted =
+ self->ostree_booted && self->root_is_sysroot;
+ if (root_is_ostree_booted && !self->booted_deployment)
return glnx_throw (error, "Unexpected state: /run/ostree-booted found and in / sysroot but not in a booted deployment");
+ /* Ensure the entires are sorted */
g_ptr_array_sort (deployments, compare_deployments_by_boot_loader_version_reversed);
+ /* And then set their index variables */
for (guint i = 0; i < deployments->len; i++)
{
OstreeDeployment *deployment = deployments->pdata[i];
ostree_deployment_set_index (deployment, i);
}
+ if (!_ostree_sysroot_reload_staged (self, error))
+ return FALSE;
+
/* Determine whether we're "physical" or not, the first time we initialize */
if (!self->loaded)
{
@@ -950,6 +1015,20 @@ ostree_sysroot_get_booted_deployment (OstreeSysroot *self)
}
/**
+ * ostree_sysroot_get_staged_deployment:
+ * @self: Sysroot
+ *
+ * Returns: (transfer none): The currently staged deployment, or %NULL if none
+ */
+OstreeDeployment *
+ostree_sysroot_get_staged_deployment (OstreeSysroot *self)
+{
+ g_return_val_if_fail (self->loaded, NULL);
+
+ return self->staged_deployment;
+}
+
+/**
* ostree_sysroot_get_deployments:
* @self: Sysroot
*
@@ -1769,7 +1848,8 @@ ostree_sysroot_deployment_unlock (OstreeSysroot *self,
break;
case OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT:
{
- g_autofree char *devpath = get_unlocked_development_path (deployment);
+ g_autofree char *devpath =
+ _ostree_sysroot_get_runstate_path (deployment, _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT);
g_autofree char *devpath_parent = dirname (g_strdup (devpath));
if (!glnx_shutil_mkdir_p_at (AT_FDCWD, devpath_parent, 0755, cancellable, error))
diff --git a/src/libostree/ostree-sysroot.h b/src/libostree/ostree-sysroot.h
index e4763d37..47cbb022 100644
--- a/src/libostree/ostree-sysroot.h
+++ b/src/libostree/ostree-sysroot.h
@@ -74,6 +74,8 @@ _OSTREE_PUBLIC
GPtrArray *ostree_sysroot_get_deployments (OstreeSysroot *self);
_OSTREE_PUBLIC
OstreeDeployment *ostree_sysroot_get_booted_deployment (OstreeSysroot *self);
+_OSTREE_PUBLIC
+OstreeDeployment *ostree_sysroot_get_staged_deployment (OstreeSysroot *self);
_OSTREE_PUBLIC
GFile *ostree_sysroot_get_deployment_directory (OstreeSysroot *self,
@@ -175,6 +177,17 @@ gboolean ostree_sysroot_deploy_tree (OstreeSysroot *self,
GError **error);
_OSTREE_PUBLIC
+gboolean ostree_sysroot_stage_tree (OstreeSysroot *self,
+ const char *osname,
+ const char *revision,
+ GKeyFile *origin,
+ OstreeDeployment *merge_deployment,
+ char **override_kernel_argv,
+ OstreeDeployment **out_new_deployment,
+ GCancellable *cancellable,
+ GError **error);
+
+_OSTREE_PUBLIC
gboolean ostree_sysroot_deployment_set_mutable (OstreeSysroot *self,
OstreeDeployment *deployment,
gboolean is_mutable,
diff --git a/src/ostree/ot-admin-builtin-deploy.c b/src/ostree/ot-admin-builtin-deploy.c
index d9905212..f6c0c161 100644
--- a/src/ostree/ot-admin-builtin-deploy.c
+++ b/src/ostree/ot-admin-builtin-deploy.c
@@ -34,6 +34,7 @@
#include <glib/gi18n.h>
static gboolean opt_retain;
+static gboolean opt_stage;
static gboolean opt_retain_pending;
static gboolean opt_retain_rollback;
static gboolean opt_not_as_default;
@@ -50,6 +51,7 @@ static GOptionEntry options[] = {
{ "origin-file", 0, 0, G_OPTION_ARG_FILENAME, &opt_origin_path, "Specify origin file", "FILENAME" },
{ "no-prune", 0, 0, G_OPTION_ARG_NONE, &opt_no_prune, "Don't prune the repo when done", 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 },
{ "retain-pending", 0, 0, G_OPTION_ARG_NONE, &opt_retain_pending, "Do not delete pending deployments", NULL },
{ "retain-rollback", 0, 0, G_OPTION_ARG_NONE, &opt_retain_rollback, "Do not delete rollback deployments", NULL },
{ "not-as-default", 0, 0, G_OPTION_ARG_NONE, &opt_not_as_default, "Append rather than prepend new deployment", NULL },
@@ -157,31 +159,45 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat
g_autoptr(OstreeDeployment) new_deployment = NULL;
g_auto(GStrv) kargs_strv = _ostree_kernel_args_to_strv (kargs);
- if (!ostree_sysroot_deploy_tree (sysroot, opt_osname, revision, origin, merge_deployment,
- kargs_strv, &new_deployment, cancellable, error))
- return FALSE;
-
- OstreeSysrootSimpleWriteDeploymentFlags flags = OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN;
- if (opt_retain)
- flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN;
- else
+ if (opt_stage)
{
- if (opt_retain_pending)
- flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PENDING;
- if (opt_retain_rollback)
- flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_ROLLBACK;
+ if (opt_retain_pending || opt_retain_rollback)
+ 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");
+ if (!ostree_sysroot_stage_tree (sysroot, opt_osname, revision, origin, merge_deployment,
+ kargs_strv, &new_deployment, cancellable, error))
+ return FALSE;
}
+ else
+ {
+ if (!ostree_sysroot_deploy_tree (sysroot, opt_osname, revision, origin, merge_deployment,
+ kargs_strv, &new_deployment, cancellable, error))
+ return FALSE;
- if (opt_not_as_default)
- flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NOT_DEFAULT;
-
- if (!ostree_sysroot_simple_write_deployment (sysroot, opt_osname, new_deployment,
- merge_deployment, flags, cancellable, error))
- return FALSE;
+ OstreeSysrootSimpleWriteDeploymentFlags flags = OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN;
+ if (opt_retain)
+ flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN;
+ else
+ {
+ if (opt_retain_pending)
+ flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PENDING;
+ if (opt_retain_rollback)
+ flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_ROLLBACK;
+ }
+
+ if (opt_not_as_default)
+ flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NOT_DEFAULT;
+
+ if (!ostree_sysroot_simple_write_deployment (sysroot, opt_osname, new_deployment,
+ merge_deployment, flags, cancellable, error))
+ return FALSE;
+ }
- /* And finally, cleanup of any leftover data.
+ /* And finally, cleanup of any leftover data. In stage mode, we
+ * don't do a full cleanup as we didn't touch the bootloader.
*/
- if (opt_no_prune)
+ if (opt_no_prune || opt_stage)
{
if (!ostree_sysroot_prepare_cleanup (sysroot, cancellable, error))
return FALSE;
diff --git a/src/ostree/ot-admin-builtin-finalize-staged.c b/src/ostree/ot-admin-builtin-finalize-staged.c
new file mode 100644
index 00000000..6740f82a
--- /dev/null
+++ b/src/ostree/ot-admin-builtin-finalize-staged.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include "ot-main.h"
+#include "ot-admin-builtins.h"
+#include "ot-admin-functions.h"
+#include "ostree.h"
+#include "otutil.h"
+
+#include "ostree-cmdprivate.h"
+#include "ostree.h"
+
+/* Called by ostree-finalize-staged.service, and in turn
+ * invokes a cmdprivate function inside the shared library.
+ */
+gboolean
+ot_admin_builtin_finalize_staged (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error)
+{
+ /* Just a sanity check; we shouldn't be called outside of the service though.
+ */
+ struct stat stbuf;
+ if (fstatat (AT_FDCWD, "/run/ostree-booted", &stbuf, 0) < 0)
+ return TRUE;
+
+ g_autoptr(GFile) sysroot_file = g_file_new_for_path ("/");
+ g_autoptr(OstreeSysroot) sysroot = ostree_sysroot_new (sysroot_file);
+
+ if (!ostree_sysroot_load (sysroot, cancellable, error))
+ return FALSE;
+ if (!ostree_cmd__private__()->ostree_finalize_staged (sysroot, cancellable, error))
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/src/ostree/ot-admin-builtin-status.c b/src/ostree/ot-admin-builtin-status.c
index 096155c6..55be6994 100644
--- a/src/ostree/ot-admin-builtin-status.c
+++ b/src/ostree/ot-admin-builtin-status.c
@@ -96,7 +96,9 @@ deployment_print_status (OstreeSysroot *sysroot,
GKeyFile *origin = ostree_deployment_get_origin (deployment);
const char *deployment_status = "";
- if (is_pending)
+ if (ostree_deployment_is_staged (deployment))
+ deployment_status = " (staged)";
+ else if (is_pending)
deployment_status = " (pending)";
else if (is_rollback)
deployment_status = " (rollback)";
@@ -199,6 +201,16 @@ ot_admin_builtin_status (int argc, char **argv, OstreeCommandInvocation *invocat
}
else
{
+ OstreeDeployment *staged = ostree_sysroot_get_staged_deployment (sysroot);
+ if (staged)
+ {
+ if (!deployment_print_status (sysroot, repo, staged,
+ FALSE, FALSE, FALSE,
+ cancellable,
+ error))
+ return FALSE;
+ }
+
for (guint i = 0; i < deployments->len; i++)
{
OstreeDeployment *deployment = deployments->pdata[i];
diff --git a/src/ostree/ot-admin-builtins.h b/src/ostree/ot-admin-builtins.h
index a81f4d62..d88fc0b9 100644
--- a/src/ostree/ot-admin-builtins.h
+++ b/src/ostree/ot-admin-builtins.h
@@ -40,6 +40,7 @@ BUILTINPROTO(undeploy);
BUILTINPROTO(deploy);
BUILTINPROTO(cleanup);
BUILTINPROTO(pin);
+BUILTINPROTO(finalize_staged);
BUILTINPROTO(unlock);
BUILTINPROTO(status);
BUILTINPROTO(set_origin);
diff --git a/src/ostree/ot-builtin-admin.c b/src/ostree/ot-builtin-admin.c
index 1262c5a5..b26eea81 100644
--- a/src/ostree/ot-builtin-admin.c
+++ b/src/ostree/ot-builtin-admin.c
@@ -57,6 +57,9 @@ static OstreeCommand admin_subcommands[] = {
{ "pin", OSTREE_BUILTIN_FLAG_NO_REPO,
ot_admin_builtin_pin,
"Change the \"pinning\" state of a deployment" },
+ { "finalize-staged", OSTREE_BUILTIN_FLAG_NO_REPO | OSTREE_BUILTIN_FLAG_HIDDEN,
+ ot_admin_builtin_finalize_staged,
+ "Internal command to run at shutdown time" },
{ "status", OSTREE_BUILTIN_FLAG_NO_REPO,
ot_admin_builtin_status,
"List deployments" },