Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] sysroot: Support for directories instead of symbolic links in boot part #3359

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 107 additions & 10 deletions src/libostree/ostree-sysroot-deploy.c
Original file line number Diff line number Diff line change
Expand Up @@ -2212,10 +2212,60 @@ prepare_new_bootloader_link (OstreeSysroot *sysroot, int current_bootversion, in
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");
Comment on lines +2236 to +2238
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the bit I'd like to have dispatch on the srel file in particular.

To be conformant, we shouldn't create a symlink even if we can - the spec just says that XBOOTLDR needs to be something "the firmware" can read, and maybe someone makes a firmware that handles ext4 or whatever.

To be clear there's no good reason I am aware of to make XBOOTLDR something other than VFAT today - but still, the srel file seems like the right way to dispatch.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh and as a big bonus, dispatching on that file means we can unit test this code without setting up a special hack to make symlink writing fail with EPERM etc.


/* 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 */
static gboolean
swap_bootloader (OstreeSysroot *sysroot, OstreeBootloader *bootloader, int current_bootversion,
int new_bootversion, GCancellable *cancellable, GError **error)
swap_bootloader (OstreeSysroot *sysroot, OstreeBootloader *bootloader, gboolean loader_link,
int current_bootversion, int new_bootversion, GCancellable *cancellable, GError **error)
{
GLNX_AUTO_PREFIX_ERROR ("Final bootloader swap", error);

Expand All @@ -2225,12 +2275,22 @@ swap_bootloader (OstreeSysroot *sysroot, OstreeBootloader *bootloader, int curre
if (!_ostree_sysroot_ensure_boot_fd (sysroot, error))
return FALSE;

/* The symlink was already written, and we used syncfs() to ensure
* its data is in place. Renaming now should give us atomic semantics;
* see https://bugzilla.gnome.org/show_bug.cgi?id=755595
*/
if (!glnx_renameat (sysroot->boot_fd, "loader.tmp", sysroot->boot_fd, "loader", error))
return FALSE;
if (loader_link)
{
/* The symlink was already written, and we used syncfs() to ensure
* its data is in place. Renaming now should give us atomic semantics;
* see https://bugzilla.gnome.org/show_bug.cgi?id=755595
*/
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
* isn't required for atomicity, for two reasons:
Expand Down Expand Up @@ -2448,13 +2508,50 @@ write_deployments_bootswap (OstreeSysroot *self, GPtrArray *new_deployments,
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;
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))
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;

if (out_subbootdir)
Expand Down
65 changes: 50 additions & 15 deletions src/libostree/ostree-sysroot.c
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,12 @@ compare_loader_configs_for_sorting (gconstpointer a_pp, gconstpointer b_pp)
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/`. */
gboolean
_ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self, int bootversion,
Expand All @@ -613,7 +619,16 @@ _ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self, int bootversion,
g_autoptr (GPtrArray) ret_loader_configs
= 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, &current_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;
g_auto (GLnxDirFdIterator) dfd_iter = {
0,
Expand Down Expand Up @@ -660,7 +675,7 @@ _ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self, int bootversion,
return TRUE;
}

/* Get the bootversion from the `/boot/loader` symlink. */
/* Get the bootversion from the `/boot/loader` directory or symlink. */
static gboolean
read_current_bootversion (OstreeSysroot *self, int *out_bootversion, GCancellable *cancellable,
GError **error)
Expand All @@ -673,24 +688,44 @@ read_current_bootversion (OstreeSysroot *self, int *out_bootversion, GCancellabl
return FALSE;
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;
}
else
{
if (!S_ISLNK (stbuf.st_mode))
return glnx_throw (error, "Not a symbolic link: boot/loader");

g_autofree char *target
= glnx_readlinkat_malloc (self->sysroot_fd, "boot/loader", cancellable, error);
if (!target)
return FALSE;
if (g_strcmp0 (target, "loader.0") == 0)
ret_bootversion = 0;
else if (g_strcmp0 (target, "loader.1") == 0)
ret_bootversion = 1;
if (S_ISLNK (stbuf.st_mode))
{
/* Traditional link, check version by reading link name */
g_autofree char *target =
glnx_readlinkat_malloc (self->sysroot_fd, "boot/loader", cancellable, error);
if (!target)
return FALSE;
if (g_strcmp0 (target, "loader.0") == 0)
ret_bootversion = 0;
else if (g_strcmp0 (target, "loader.1") == 0)
ret_bootversion = 1;
else
return glnx_throw (error, "Invalid target '%s' in boot/loader", target);
}
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;
Expand Down
2 changes: 1 addition & 1 deletion src/switchroot/ostree-prepare-root.c
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ main (int argc, char *argv[])
* at /boot inside the deployment. */
if (snprintf (srcpath, sizeof (srcpath), "%s/boot/loader", root_mountpoint) < 0)
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))
{
Expand Down
Loading