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
This commit is contained in:
Colin Walters authored and Atomic Bot committed 2018-04-12 14:55:12 +00:00
1 parent ff50495f67
commit eb506c759c
23 files changed
+747 -110

No files matched your search

+89 -9
View File
@@ -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)
{
@@ -949,6 +1014,20 @@ ostree_sysroot_get_booted_deployment (OstreeSysroot *self)
return self->booted_deployment;
}
/**
* 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))