Skip to content

Commit 320b7e9

Browse files
ricardosalvetiigoropaniuk
authored andcommitted
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] ostreedev#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>
1 parent ba91a28 commit 320b7e9

3 files changed

Lines changed: 170 additions & 30 deletions

File tree

src/libostree/ostree-sysroot-deploy.c

Lines changed: 114 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,21 @@ sysroot_flags_to_copy_flags (GLnxFileCopyFlags defaults, OstreeSysrootDebugFlags
102102
return defaults;
103103
}
104104

105+
static gboolean
106+
create_bootself_link (OstreeSysroot *self, GCancellable *cancellable, GError **error)
107+
{
108+
/* When there is no loader, check if the fs supports symlink or not */
109+
if (TEMP_FAILURE_RETRY (symlinkat (".", self->sysroot_fd, "boot/boot")) < 0)
110+
{
111+
if (errno == EPERM)
112+
return FALSE;
113+
else if (errno != EEXIST)
114+
return glnx_throw_errno_prefix (error, "symlinkat");
115+
}
116+
117+
return TRUE;
118+
}
119+
105120
/* Try a hardlink if we can, otherwise fall back to copying. Used
106121
* right now for kernels/initramfs/device trees in /boot, where we can just
107122
* hardlink if we're on the same partition.
@@ -2190,9 +2205,7 @@ prepare_new_bootloader_link (OstreeSysroot *sysroot, int current_bootversion, in
21902205

21912206
/* This allows us to support both /boot on a seperate filesystem to / as well
21922207
* as on the same filesystem. */
2193-
if (TEMP_FAILURE_RETRY (symlinkat (".", sysroot->sysroot_fd, "boot/boot")) < 0)
2194-
if (errno != EEXIST)
2195-
return glnx_throw_errno_prefix (error, "symlinkat");
2208+
(void)create_bootself_link (sysroot, cancellable, error);
21962209

21972210
g_autofree char *new_target = g_strdup_printf ("loader.%d", new_bootversion);
21982211

@@ -2204,10 +2217,57 @@ prepare_new_bootloader_link (OstreeSysroot *sysroot, int current_bootversion, in
22042217
return TRUE;
22052218
}
22062219

2220+
/* We generate the directory on disk, then potentially do a syncfs() to ensure
2221+
* that it (and everything else we wrote) has hit disk. Only after that do we
2222+
* rename it into place (via renameat2 RENAME_EXCHANGE).
2223+
*/
2224+
static gboolean
2225+
prepare_new_bootloader_dir (OstreeSysroot *sysroot, int current_bootversion, int new_bootversion,
2226+
GCancellable *cancellable, GError **error)
2227+
{
2228+
GLNX_AUTO_PREFIX_ERROR ("Preparing bootloader directory", error);
2229+
g_assert ((current_bootversion == 0 && new_bootversion == 1)
2230+
|| (current_bootversion == 1 && new_bootversion == 0));
2231+
2232+
if (!_ostree_sysroot_ensure_boot_fd (sysroot, error))
2233+
return FALSE;
2234+
2235+
/* This allows us to support both /boot on a seperate filesystem to / as well
2236+
* as on the same filesystem. Allowed to fail with EPERM on ESP/vfat.
2237+
*/
2238+
if (TEMP_FAILURE_RETRY (symlinkat (".", sysroot->sysroot_fd, "boot/boot")) < 0)
2239+
if (errno != EPERM && errno != EEXIST)
2240+
return glnx_throw_errno_prefix (error, "symlinkat");
2241+
2242+
/* As the directory gets swapped with glnx_renameat2_exchange, the new bootloader
2243+
* deployment needs to first be moved to the 'old' path, as the 'current' one will
2244+
* become the older deployment after the exchange.
2245+
*/
2246+
g_autofree char *loader_new = g_strdup_printf ("loader.%d", new_bootversion);
2247+
g_autofree char *loader_old = g_strdup_printf ("loader.%d", current_bootversion);
2248+
2249+
/* Tag boot version under an ostree specific file */
2250+
g_autofree char *version_name = g_strdup_printf ("%s/ostree_bootversion", loader_new);
2251+
if (!glnx_file_replace_contents_at (sysroot->boot_fd, version_name, (guint8 *)loader_new,
2252+
strlen (loader_new), 0, cancellable, error))
2253+
return FALSE;
2254+
2255+
/* It is safe to remove older loader version as it wasn't really deployed */
2256+
if (!glnx_shutil_rm_rf_at (sysroot->boot_fd, loader_old, cancellable, error))
2257+
return FALSE;
2258+
2259+
/* Rename new deployment to the older path before the exchange */
2260+
if (!glnx_renameat2_noreplace (sysroot->boot_fd, loader_new, sysroot->boot_fd, loader_old))
2261+
return FALSE;
2262+
2263+
return TRUE;
2264+
}
2265+
22072266
/* Update the /boot/loader symlink to point to /boot/loader.$new_bootversion */
22082267
static gboolean
2209-
swap_bootloader (OstreeSysroot *sysroot, OstreeBootloader *bootloader, int current_bootversion,
2210-
int new_bootversion, GCancellable *cancellable, GError **error)
2268+
swap_bootloader (OstreeSysroot *sysroot, OstreeBootloader *bootloader, gboolean loader_link,
2269+
int current_bootversion, int new_bootversion, GCancellable *cancellable,
2270+
GError **error)
22112271
{
22122272
GLNX_AUTO_PREFIX_ERROR ("Final bootloader swap", error);
22132273

@@ -2217,14 +2277,24 @@ swap_bootloader (OstreeSysroot *sysroot, OstreeBootloader *bootloader, int curre
22172277
if (!_ostree_sysroot_ensure_boot_fd (sysroot, error))
22182278
return FALSE;
22192279

2220-
g_assert_cmpint (sysroot->boot_fd, !=, -1);
2280+
if (loader_link)
2281+
{
2282+
g_assert_cmpint (sysroot->boot_fd, !=, -1);
22212283

2222-
/* The symlink was already written, and we used syncfs() to ensure
2223-
* its data is in place. Renaming now should give us atomic semantics;
2224-
* see https://bugzilla.gnome.org/show_bug.cgi?id=755595
2225-
*/
2226-
if (!glnx_renameat (sysroot->boot_fd, "loader.tmp", sysroot->boot_fd, "loader", error))
2227-
return FALSE;
2284+
/* The symlink was already written, and we used syncfs() to ensure
2285+
* its data is in place. Renaming now should give us atomic semantics;
2286+
* see https://bugzilla.gnome.org/show_bug.cgi?id=755595
2287+
*/
2288+
if (!glnx_renameat (sysroot->boot_fd, "loader.tmp", sysroot->boot_fd, "loader", error))
2289+
return FALSE;
2290+
}
2291+
else
2292+
{
2293+
/* New target is currently under the old/current version */
2294+
g_autofree char *new_target = g_strdup_printf ("loader.%d", current_bootversion);
2295+
if (glnx_renameat2_exchange (sysroot->boot_fd, new_target, sysroot->boot_fd, "loader") != 0)
2296+
return FALSE;
2297+
}
22282298

22292299
/* As grub doesn't support replaying XFS journal,
22302300
* we must fsfreeze/thaw again here:
@@ -2447,13 +2517,43 @@ write_deployments_bootswap (OstreeSysroot *self, GPtrArray *new_deployments,
24472517
return glnx_prefix_error (error, "Bootloader write config");
24482518
}
24492519

2450-
if (!prepare_new_bootloader_link (self, self->bootversion, new_bootversion, cancellable, error))
2520+
/* Handle when boot/loader is a symlink (original ostree model) or is a directory (e.g. EFI/vfat)
2521+
* for recommended systemd type1 BLS
2522+
*/
2523+
struct stat stbuf;
2524+
gboolean loader_link = FALSE;
2525+
if (!glnx_fstatat_allow_noent (self->sysroot_fd, "boot/loader", &stbuf, AT_SYMLINK_NOFOLLOW,
2526+
error))
24512527
return FALSE;
2528+
if (errno == ENOENT)
2529+
loader_link = create_bootself_link (self, cancellable, error);
2530+
else if (S_ISLNK (stbuf.st_mode))
2531+
loader_link = TRUE;
2532+
else if (S_ISDIR (stbuf.st_mode))
2533+
loader_link = FALSE;
2534+
else
2535+
return FALSE;
2536+
2537+
if (loader_link)
2538+
{
2539+
/* Default and when loader is a link is to swap links */
2540+
if (!prepare_new_bootloader_link (self, self->bootversion, new_bootversion, cancellable,
2541+
error))
2542+
return FALSE;
2543+
}
2544+
else
2545+
{
2546+
/* Handle boot/loader as a directory, and swap with renameat2 RENAME_EXCHANGE */
2547+
if (!prepare_new_bootloader_dir (self, self->bootversion, new_bootversion, cancellable,
2548+
error))
2549+
return FALSE;
2550+
}
24522551

24532552
if (!full_system_sync (self, out_syncstats, cancellable, error))
24542553
return FALSE;
24552554

2456-
if (!swap_bootloader (self, bootloader, self->bootversion, new_bootversion, cancellable, error))
2555+
if (!swap_bootloader (self, bootloader, loader_link, self->bootversion, new_bootversion,
2556+
cancellable, error))
24572557
return FALSE;
24582558

24592559
if (out_subbootdir)

src/libostree/ostree-sysroot.c

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
#include "ostree-sysroot-private.h"
3939
#include "otcore.h"
4040

41+
#define BOOTVERSION_FILE "boot/loader/ostree_bootversion"
42+
4143
/**
4244
* SECTION:ostree-sysroot
4345
* @title: Root partition mount point
@@ -601,6 +603,9 @@ compare_loader_configs_for_sorting (gconstpointer a_pp, gconstpointer b_pp)
601603
return compare_boot_loader_configs (a, b);
602604
}
603605

606+
static gboolean read_current_bootversion (OstreeSysroot *self, int *out_bootversion,
607+
GCancellable *cancellable, GError **error);
608+
604609
/* Read all the bootconfigs from `/boot/loader/`. */
605610
gboolean
606611
_ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self, int bootversion,
@@ -613,7 +618,16 @@ _ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self, int bootversion,
613618
g_autoptr (GPtrArray) ret_loader_configs
614619
= g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
615620

616-
g_autofree char *entries_path = g_strdup_printf ("boot/loader.%d/entries", bootversion);
621+
g_autofree char *entries_path = NULL;
622+
int current_version;
623+
if (!read_current_bootversion (self, &current_version, cancellable, error))
624+
return FALSE;
625+
626+
if (current_version == bootversion)
627+
entries_path = g_strdup ("boot/loader/entries");
628+
else
629+
entries_path = g_strdup_printf ("boot/loader.%d/entries", bootversion);
630+
617631
gboolean entries_exists;
618632
g_auto (GLnxDirFdIterator) dfd_iter = {
619633
0,
@@ -660,7 +674,7 @@ _ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self, int bootversion,
660674
return TRUE;
661675
}
662676

663-
/* Get the bootversion from the `/boot/loader` symlink. */
677+
/* Get the bootversion from the `/boot/loader` directory or symlink. */
664678
static gboolean
665679
read_current_bootversion (OstreeSysroot *self, int *out_bootversion, GCancellable *cancellable,
666680
GError **error)
@@ -673,24 +687,50 @@ read_current_bootversion (OstreeSysroot *self, int *out_bootversion, GCancellabl
673687
return FALSE;
674688
if (errno == ENOENT)
675689
{
676-
g_debug ("Didn't find $sysroot/boot/loader symlink; assuming bootversion 0");
690+
g_debug ("Didn't find $sysroot/boot/loader directory or symlink; assuming bootversion 0");
677691
ret_bootversion = 0;
678692
}
679693
else
680694
{
681-
if (!S_ISLNK (stbuf.st_mode))
682-
return glnx_throw (error, "Not a symbolic link: boot/loader");
683-
684-
g_autofree char *target
685-
= glnx_readlinkat_malloc (self->sysroot_fd, "boot/loader", cancellable, error);
686-
if (!target)
687-
return FALSE;
688-
if (g_strcmp0 (target, "loader.0") == 0)
689-
ret_bootversion = 0;
690-
else if (g_strcmp0 (target, "loader.1") == 0)
691-
ret_bootversion = 1;
695+
if (S_ISLNK (stbuf.st_mode))
696+
{
697+
/* Traditional link, check version by reading link name */
698+
g_autofree char *target
699+
= glnx_readlinkat_malloc (self->sysroot_fd, "boot/loader", cancellable, error);
700+
if (!target)
701+
return FALSE;
702+
if (g_strcmp0 (target, "loader.0") == 0)
703+
ret_bootversion = 0;
704+
else if (g_strcmp0 (target, "loader.1") == 0)
705+
ret_bootversion = 1;
706+
else
707+
return glnx_throw (error, "Invalid target '%s' in boot/loader", target);
708+
}
692709
else
693-
return glnx_throw (error, "Invalid target '%s' in boot/loader", target);
710+
{
711+
/* Loader is a directory, check version by reading ostree_bootversion */
712+
glnx_autofd int bversion_fd = -1;
713+
if (!ot_openat_ignore_enoent (self->sysroot_fd, BOOTVERSION_FILE,
714+
&bversion_fd, error))
715+
return FALSE;
716+
717+
if (bversion_fd == -1)
718+
{
719+
g_debug ("File " BOOTVERSION_FILE " is not available, assuming bootversion 0");
720+
ret_bootversion = 0;
721+
}
722+
else
723+
{
724+
g_autofree char *version
725+
= glnx_fd_readall_utf8 (bversion_fd, NULL, cancellable, error);
726+
if (g_strcmp0 (version, "loader.0") == 0)
727+
ret_bootversion = 0;
728+
else if (g_strcmp0 (version, "loader.1") == 0)
729+
ret_bootversion = 1;
730+
else
731+
return glnx_throw (error, "Invalid version '%s' in " BOOTVERSION_FILE, version);
732+
}
733+
}
694734
}
695735

696736
*out_bootversion = ret_bootversion;

src/switchroot/ostree-prepare-root.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ main (int argc, char *argv[])
519519
* at /boot inside the deployment. */
520520
if (snprintf (srcpath, sizeof (srcpath), "%s/boot/loader", root_mountpoint) < 0)
521521
err (EXIT_FAILURE, "failed to assemble /boot/loader path");
522-
if (lstat (srcpath, &stbuf) == 0 && S_ISLNK (stbuf.st_mode))
522+
if (lstat (srcpath, &stbuf) == 0 && (S_ISLNK (stbuf.st_mode) || S_ISDIR (stbuf.st_mode)))
523523
{
524524
if (lstat ("boot", &stbuf) == 0 && S_ISDIR (stbuf.st_mode))
525525
{

0 commit comments

Comments
 (0)