mirror of
https://github.com/ostreedev/ostree.git
synced 2025-01-03 05:18:24 +03:00
sysroot: Support for directories instead of symbolic links in boot part
Allow manipulating and updating /boot/loader entries under a normal directory, as well as using symbolic links. For directories this uses `renameat2` to do atomic swap of the loader directory in the boot partition. It fallsback to non-atomic rename. This stays atomic on filesystems supporting links but also provide a non-atomic behavior when filesystem does not provide any atomic alternative. /boot/loader as a normal directory is needed by systemd-boot support, and can be stored under the EFI ESP vfat partition. Based on the original implementation done by Valentin David [1]. [1] https://github.com/ostreedev/ostree/pull/1967 Signed-off-by: Ricardo Salveti <ricardo@foundries.io> Signed-off-by: Jose Quaresma <jose.quaresma@foundries.io> Signed-off-by: Igor Opaniuk <igor.opaniuk@foundries.io>
This commit is contained in:
parent
64a38aec8c
commit
5be249e5ec
@ -2212,10 +2212,60 @@ prepare_new_bootloader_link (OstreeSysroot *sysroot, int current_bootversion, in
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* We generate the directory on disk, then potentially do a syncfs() to ensure
|
||||||
|
* that it (and everything else we wrote) has hit disk. Only after that do we
|
||||||
|
* rename it into place (via renameat2 RENAME_EXCHANGE).
|
||||||
|
*/
|
||||||
|
static gboolean
|
||||||
|
prepare_new_bootloader_dir (OstreeSysroot *sysroot,
|
||||||
|
int current_bootversion,
|
||||||
|
int new_bootversion,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
GLNX_AUTO_PREFIX_ERROR ("Preparing bootloader directory", error);
|
||||||
|
g_assert ((current_bootversion == 0 && new_bootversion == 1) ||
|
||||||
|
(current_bootversion == 1 && new_bootversion == 0));
|
||||||
|
|
||||||
|
if (!_ostree_sysroot_ensure_boot_fd (sysroot, error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
/* This allows us to support both /boot on a seperate filesystem to / as well
|
||||||
|
* as on the same filesystem. Allowed to fail with EPERM on ESP/vfat.
|
||||||
|
*/
|
||||||
|
if (TEMP_FAILURE_RETRY (symlinkat (".", sysroot->sysroot_fd, "boot/boot")) < 0)
|
||||||
|
if (errno != EPERM && errno != EEXIST)
|
||||||
|
return glnx_throw_errno_prefix (error, "symlinkat");
|
||||||
|
|
||||||
|
/* As the directory gets swapped with glnx_renameat2_exchange, the new bootloader
|
||||||
|
* deployment needs to first be moved to the 'old' path, as the 'current' one will
|
||||||
|
* become the older deployment after the exchange.
|
||||||
|
*/
|
||||||
|
g_autofree char *loader_new = g_strdup_printf ("loader.%d", new_bootversion);
|
||||||
|
g_autofree char *loader_old = g_strdup_printf ("loader.%d", current_bootversion);
|
||||||
|
|
||||||
|
/* Tag boot version under an ostree specific file */
|
||||||
|
g_autofree char *version_name = g_strdup_printf ("%s/ostree_bootversion", loader_new);
|
||||||
|
if (!glnx_file_replace_contents_at (sysroot->boot_fd, version_name,
|
||||||
|
(guint8*)loader_new, strlen(loader_new),
|
||||||
|
0, cancellable, error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
/* It is safe to remove older loader version as it wasn't really deployed */
|
||||||
|
if (!glnx_shutil_rm_rf_at (sysroot->boot_fd, loader_old, cancellable, error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
/* Rename new deployment to the older path before the exchange */
|
||||||
|
if (!glnx_renameat2_noreplace (sysroot->boot_fd, loader_new, sysroot->boot_fd, loader_old))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
/* Update the /boot/loader symlink to point to /boot/loader.$new_bootversion */
|
/* Update the /boot/loader symlink to point to /boot/loader.$new_bootversion */
|
||||||
static gboolean
|
static gboolean
|
||||||
swap_bootloader (OstreeSysroot *sysroot, OstreeBootloader *bootloader, int current_bootversion,
|
swap_bootloader (OstreeSysroot *sysroot, OstreeBootloader *bootloader, gboolean loader_link,
|
||||||
int new_bootversion, GCancellable *cancellable, GError **error)
|
int current_bootversion, int new_bootversion, GCancellable *cancellable, GError **error)
|
||||||
{
|
{
|
||||||
GLNX_AUTO_PREFIX_ERROR ("Final bootloader swap", error);
|
GLNX_AUTO_PREFIX_ERROR ("Final bootloader swap", error);
|
||||||
|
|
||||||
@ -2225,12 +2275,22 @@ swap_bootloader (OstreeSysroot *sysroot, OstreeBootloader *bootloader, int curre
|
|||||||
if (!_ostree_sysroot_ensure_boot_fd (sysroot, error))
|
if (!_ostree_sysroot_ensure_boot_fd (sysroot, error))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
/* The symlink was already written, and we used syncfs() to ensure
|
if (loader_link)
|
||||||
* its data is in place. Renaming now should give us atomic semantics;
|
{
|
||||||
* see https://bugzilla.gnome.org/show_bug.cgi?id=755595
|
/* The symlink was already written, and we used syncfs() to ensure
|
||||||
*/
|
* its data is in place. Renaming now should give us atomic semantics;
|
||||||
if (!glnx_renameat (sysroot->boot_fd, "loader.tmp", sysroot->boot_fd, "loader", error))
|
* see https://bugzilla.gnome.org/show_bug.cgi?id=755595
|
||||||
return FALSE;
|
*/
|
||||||
|
if (!glnx_renameat (sysroot->boot_fd, "loader.tmp", sysroot->boot_fd, "loader", error))
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* New target is currently under the old/current version */
|
||||||
|
g_autofree char *new_target = g_strdup_printf ("loader.%d", current_bootversion);
|
||||||
|
if (glnx_renameat2_exchange (sysroot->boot_fd, new_target, sysroot->boot_fd, "loader") != 0)
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
/* Now we explicitly fsync this directory, even though it
|
/* Now we explicitly fsync this directory, even though it
|
||||||
* isn't required for atomicity, for two reasons:
|
* isn't required for atomicity, for two reasons:
|
||||||
@ -2448,13 +2508,50 @@ write_deployments_bootswap (OstreeSysroot *self, GPtrArray *new_deployments,
|
|||||||
return glnx_prefix_error (error, "Bootloader write config");
|
return glnx_prefix_error (error, "Bootloader write config");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!prepare_new_bootloader_link (self, self->bootversion, new_bootversion, cancellable, error))
|
/* Handle when boot/loader is a link (normal deployment) and as a normal directory (e.g. EFI/vfat) */
|
||||||
|
struct stat stbuf;
|
||||||
|
gboolean loader_link = FALSE;
|
||||||
|
if (!glnx_fstatat_allow_noent (self->sysroot_fd, "boot/loader", &stbuf, AT_SYMLINK_NOFOLLOW, error))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
if (errno == ENOENT)
|
||||||
|
{
|
||||||
|
/* When there is no loader, check if the fs supports symlink or not */
|
||||||
|
if (TEMP_FAILURE_RETRY (symlinkat (".", self->sysroot_fd, "boot/boot")) < 0)
|
||||||
|
{
|
||||||
|
if (errno == EPERM)
|
||||||
|
loader_link = FALSE;
|
||||||
|
else if (errno != EEXIST)
|
||||||
|
return glnx_throw_errno_prefix (error, "symlinkat");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
loader_link = TRUE;
|
||||||
|
}
|
||||||
|
else if (S_ISLNK (stbuf.st_mode))
|
||||||
|
loader_link = TRUE;
|
||||||
|
else if (S_ISDIR (stbuf.st_mode))
|
||||||
|
loader_link = FALSE;
|
||||||
|
else
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (loader_link)
|
||||||
|
{
|
||||||
|
/* Default and when loader is a link is to swap links */
|
||||||
|
if (!prepare_new_bootloader_link (self, self->bootversion, new_bootversion,
|
||||||
|
cancellable, error))
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Handle boot/loader as a directory, and swap with renameat2 RENAME_EXCHANGE */
|
||||||
|
if (!prepare_new_bootloader_dir (self, self->bootversion, new_bootversion,
|
||||||
|
cancellable, error))
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
if (!full_system_sync (self, out_syncstats, cancellable, error))
|
if (!full_system_sync (self, out_syncstats, cancellable, error))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
if (!swap_bootloader (self, bootloader, self->bootversion, new_bootversion, cancellable, error))
|
if (!swap_bootloader (self, bootloader, loader_link, self->bootversion, new_bootversion, cancellable, error))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
if (out_subbootdir)
|
if (out_subbootdir)
|
||||||
|
@ -601,6 +601,12 @@ compare_loader_configs_for_sorting (gconstpointer a_pp, gconstpointer b_pp)
|
|||||||
return compare_boot_loader_configs (a, b);
|
return compare_boot_loader_configs (a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
read_current_bootversion (OstreeSysroot *self,
|
||||||
|
int *out_bootversion,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
GError **error);
|
||||||
|
|
||||||
/* Read all the bootconfigs from `/boot/loader/`. */
|
/* Read all the bootconfigs from `/boot/loader/`. */
|
||||||
gboolean
|
gboolean
|
||||||
_ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self, int bootversion,
|
_ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self, int bootversion,
|
||||||
@ -613,7 +619,16 @@ _ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self, int bootversion,
|
|||||||
g_autoptr (GPtrArray) ret_loader_configs
|
g_autoptr (GPtrArray) ret_loader_configs
|
||||||
= g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
|
= g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
|
||||||
|
|
||||||
g_autofree char *entries_path = g_strdup_printf ("boot/loader.%d/entries", bootversion);
|
g_autofree char *entries_path = NULL;
|
||||||
|
int current_version;
|
||||||
|
if (!read_current_bootversion (self, ¤t_version, cancellable, error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (current_version == bootversion)
|
||||||
|
entries_path = g_strdup ("boot/loader/entries");
|
||||||
|
else
|
||||||
|
entries_path = g_strdup_printf ("boot/loader.%d/entries", bootversion);
|
||||||
|
|
||||||
gboolean entries_exists;
|
gboolean entries_exists;
|
||||||
g_auto (GLnxDirFdIterator) dfd_iter = {
|
g_auto (GLnxDirFdIterator) dfd_iter = {
|
||||||
0,
|
0,
|
||||||
@ -660,7 +675,7 @@ _ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self, int bootversion,
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get the bootversion from the `/boot/loader` symlink. */
|
/* Get the bootversion from the `/boot/loader` directory or symlink. */
|
||||||
static gboolean
|
static gboolean
|
||||||
read_current_bootversion (OstreeSysroot *self, int *out_bootversion, GCancellable *cancellable,
|
read_current_bootversion (OstreeSysroot *self, int *out_bootversion, GCancellable *cancellable,
|
||||||
GError **error)
|
GError **error)
|
||||||
@ -673,24 +688,44 @@ read_current_bootversion (OstreeSysroot *self, int *out_bootversion, GCancellabl
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
if (errno == ENOENT)
|
if (errno == ENOENT)
|
||||||
{
|
{
|
||||||
g_debug ("Didn't find $sysroot/boot/loader symlink; assuming bootversion 0");
|
g_debug ("Didn't find $sysroot/boot/loader directory or symlink; assuming bootversion 0");
|
||||||
ret_bootversion = 0;
|
ret_bootversion = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!S_ISLNK (stbuf.st_mode))
|
if (S_ISLNK (stbuf.st_mode))
|
||||||
return glnx_throw (error, "Not a symbolic link: boot/loader");
|
{
|
||||||
|
/* Traditional link, check version by reading link name */
|
||||||
g_autofree char *target
|
g_autofree char *target =
|
||||||
= glnx_readlinkat_malloc (self->sysroot_fd, "boot/loader", cancellable, error);
|
glnx_readlinkat_malloc (self->sysroot_fd, "boot/loader", cancellable, error);
|
||||||
if (!target)
|
if (!target)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
if (g_strcmp0 (target, "loader.0") == 0)
|
if (g_strcmp0 (target, "loader.0") == 0)
|
||||||
ret_bootversion = 0;
|
ret_bootversion = 0;
|
||||||
else if (g_strcmp0 (target, "loader.1") == 0)
|
else if (g_strcmp0 (target, "loader.1") == 0)
|
||||||
ret_bootversion = 1;
|
ret_bootversion = 1;
|
||||||
|
else
|
||||||
|
return glnx_throw (error, "Invalid target '%s' in boot/loader", target);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
return glnx_throw (error, "Invalid target '%s' in boot/loader", target);
|
{
|
||||||
|
/* Loader is a directory, check version by reading ostree_bootversion */
|
||||||
|
gsize len;
|
||||||
|
g_autofree char* version =
|
||||||
|
glnx_file_get_contents_utf8_at(self->sysroot_fd, "boot/loader/ostree_bootversion",
|
||||||
|
&len, cancellable, error);
|
||||||
|
if (version == NULL)
|
||||||
|
{
|
||||||
|
g_debug ("Invalid boot/loader/ostree_bootversion, assuming bootversion 0");
|
||||||
|
ret_bootversion = 0;
|
||||||
|
}
|
||||||
|
else if (g_strcmp0 (version, "loader.0") == 0)
|
||||||
|
ret_bootversion = 0;
|
||||||
|
else if (g_strcmp0 (version, "loader.1") == 0)
|
||||||
|
ret_bootversion = 1;
|
||||||
|
else
|
||||||
|
return glnx_throw (error, "Invalid version '%s' in boot/loader/ostree_bootversion", version);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*out_bootversion = ret_bootversion;
|
*out_bootversion = ret_bootversion;
|
||||||
|
@ -515,7 +515,7 @@ main (int argc, char *argv[])
|
|||||||
* at /boot inside the deployment. */
|
* at /boot inside the deployment. */
|
||||||
if (snprintf (srcpath, sizeof (srcpath), "%s/boot/loader", root_mountpoint) < 0)
|
if (snprintf (srcpath, sizeof (srcpath), "%s/boot/loader", root_mountpoint) < 0)
|
||||||
err (EXIT_FAILURE, "failed to assemble /boot/loader path");
|
err (EXIT_FAILURE, "failed to assemble /boot/loader path");
|
||||||
if (lstat (srcpath, &stbuf) == 0 && S_ISLNK (stbuf.st_mode))
|
if (lstat (srcpath, &stbuf) == 0 && (S_ISLNK (stbuf.st_mode) || S_ISDIR (stbuf.st_mode)))
|
||||||
{
|
{
|
||||||
if (lstat ("boot", &stbuf) == 0 && S_ISDIR (stbuf.st_mode))
|
if (lstat ("boot", &stbuf) == 0 && S_ISDIR (stbuf.st_mode))
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user