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

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

Open
wants to merge 3 commits 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
4 changes: 4 additions & 0 deletions Makefile-tests.am
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ _installed_or_uninstalled_test_scripts = \
tests/test-admin-upgrade-endoflife.sh \
tests/test-admin-upgrade-systemd-update.sh \
tests/test-admin-deploy-syslinux.sh \
tests/test-admin-dir-deploy-syslinux.sh \
tests/test-admin-deploy-bootprefix.sh \
tests/test-admin-deploy-composefs.sh \
tests/test-admin-deploy-var.sh \
Expand All @@ -112,9 +113,12 @@ _installed_or_uninstalled_test_scripts = \
tests/test-admin-deploy-switch.sh \
tests/test-admin-deploy-etcmerge-cornercases.sh \
tests/test-admin-deploy-uboot.sh \
tests/test-admin-dir-deploy-uboot.sh \
tests/test-admin-deploy-grub2.sh \
tests/test-admin-dir-deploy-grub2.sh \
tests/test-admin-deploy-nomerge.sh \
tests/test-admin-deploy-none.sh \
tests/test-admin-dir-deploy-none.sh \
tests/test-admin-deploy-bootid-gc.sh \
tests/test-admin-deploy-whiteouts.sh \
tests/test-admin-deploy-emptyetc.sh \
Expand Down
171 changes: 161 additions & 10 deletions src/libostree/ostree-sysroot-deploy.c
Original file line number Diff line number Diff line change
Expand Up @@ -2180,6 +2180,56 @@ install_deployment_kernel (OstreeSysroot *sysroot, int new_bootversion,
return TRUE;
}

/* Determine whether an existing directory implements the semantics described in
* https://uapi-group.org/specifications/specs/boot_loader_specification/#type-1-boot-loader-entry-keys
*/
static gboolean
is_bootconfig_type1_semantics (OstreeSysroot *sysroot, GCancellable *cancellable, GError **error)
{
struct stat stbuf;

if (!_ostree_sysroot_ensure_boot_fd (sysroot, error))
return FALSE;

if (!glnx_fstatat_allow_noent (sysroot->boot_fd, "loader/entries.srel", &stbuf,
AT_SYMLINK_NOFOLLOW, error))
return FALSE;

if (errno == ENOENT)
{
g_debug ("Didn't find loader/entries.srel file");
return FALSE;
}
else
{
/* Get semantics type by reading loader/entries.srel */
gsize len;
g_autofree char *type = glnx_file_get_contents_utf8_at (
sysroot->boot_fd, "loader/entries.srel", &len, cancellable, error);
if (type == NULL)
{
g_debug ("Invalid loader/entries.srel file");
return FALSE;
}

/* Remove trailing newline symbol if there is any */
type[strcspn (type, "\n")] = 0;

if (g_strcmp0 (type, "type1") == 0)
{
g_debug ("type1 semantics is found in loader/entries.srel file");
return TRUE;
}
else
{
g_debug ("Unsupported semantics type ('%s') in loader/entries.srel file", type);
return FALSE;
}
}

return FALSE;
}

/* We generate the symlink 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.
Expand Down Expand Up @@ -2208,10 +2258,57 @@ 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");
igoropaniuk marked this conversation as resolved.
Show resolved Hide resolved

/* 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 @@ -2221,12 +2318,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 @@ -2444,13 +2551,57 @@ 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;
gboolean force_type1_semantics = is_bootconfig_type1_semantics (self, cancellable, error);
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 (force_type1_semantics && loader_link)
return glnx_throw_errno_prefix (error, "type1 semantics, but boot/loader is symlink");

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
62 changes: 47 additions & 15 deletions src/libostree/ostree-sysroot.c
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,9 @@ 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 +616,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 +672,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 +685,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
Loading