Skip to content

Commit bb7a7cb

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. Tests were duplicated for simplicity reasons. Based on the original implementation done by Valentin David [1]. [1] #1967 Signed-off-by: Ricardo Salveti <[email protected]> Signed-off-by: Jose Quaresma <[email protected]> Signed-off-by: Igor Opaniuk <[email protected]>
1 parent 64a38ae commit bb7a7cb

File tree

3 files changed

+158
-26
lines changed

3 files changed

+158
-26
lines changed

src/libostree/ostree-sysroot-deploy.c

+107-10
Original file line numberDiff line numberDiff line change
@@ -2212,10 +2212,60 @@ prepare_new_bootloader_link (OstreeSysroot *sysroot, int current_bootversion, in
22122212
return TRUE;
22132213
}
22142214

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

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

2228-
/* The symlink was already written, and we used syncfs() to ensure
2229-
* its data is in place. Renaming now should give us atomic semantics;
2230-
* see https://bugzilla.gnome.org/show_bug.cgi?id=755595
2231-
*/
2232-
if (!glnx_renameat (sysroot->boot_fd, "loader.tmp", sysroot->boot_fd, "loader", error))
2233-
return FALSE;
2278+
if (loader_link)
2279+
{
2280+
/* The symlink was already written, and we used syncfs() to ensure
2281+
* its data is in place. Renaming now should give us atomic semantics;
2282+
* see https://bugzilla.gnome.org/show_bug.cgi?id=755595
2283+
*/
2284+
if (!glnx_renameat (sysroot->boot_fd, "loader.tmp", sysroot->boot_fd, "loader", error))
2285+
return FALSE;
2286+
}
2287+
else
2288+
{
2289+
/* New target is currently under the old/current version */
2290+
g_autofree char *new_target = g_strdup_printf ("loader.%d", current_bootversion);
2291+
if (glnx_renameat2_exchange (sysroot->boot_fd, new_target, sysroot->boot_fd, "loader") != 0)
2292+
return FALSE;
2293+
}
22342294

22352295
/* Now we explicitly fsync this directory, even though it
22362296
* isn't required for atomicity, for two reasons:
@@ -2448,13 +2508,50 @@ write_deployments_bootswap (OstreeSysroot *self, GPtrArray *new_deployments,
24482508
return glnx_prefix_error (error, "Bootloader write config");
24492509
}
24502510

2451-
if (!prepare_new_bootloader_link (self, self->bootversion, new_bootversion, cancellable, error))
2511+
/* Handle when boot/loader is a link (normal deployment) and as a normal directory (e.g. EFI/vfat) */
2512+
struct stat stbuf;
2513+
gboolean loader_link = FALSE;
2514+
if (!glnx_fstatat_allow_noent (self->sysroot_fd, "boot/loader", &stbuf, AT_SYMLINK_NOFOLLOW, error))
24522515
return FALSE;
2516+
if (errno == ENOENT)
2517+
{
2518+
/* When there is no loader, check if the fs supports symlink or not */
2519+
if (TEMP_FAILURE_RETRY (symlinkat (".", self->sysroot_fd, "boot/boot")) < 0)
2520+
{
2521+
if (errno == EPERM)
2522+
loader_link = FALSE;
2523+
else if (errno != EEXIST)
2524+
return glnx_throw_errno_prefix (error, "symlinkat");
2525+
}
2526+
else
2527+
loader_link = TRUE;
2528+
}
2529+
else if (S_ISLNK (stbuf.st_mode))
2530+
loader_link = TRUE;
2531+
else if (S_ISDIR (stbuf.st_mode))
2532+
loader_link = FALSE;
2533+
else
2534+
return FALSE;
2535+
2536+
if (loader_link)
2537+
{
2538+
/* Default and when loader is a link is to swap links */
2539+
if (!prepare_new_bootloader_link (self, self->bootversion, new_bootversion,
2540+
cancellable, error))
2541+
return FALSE;
2542+
}
2543+
else
2544+
{
2545+
/* Handle boot/loader as a directory, and swap with renameat2 RENAME_EXCHANGE */
2546+
if (!prepare_new_bootloader_dir (self, self->bootversion, new_bootversion,
2547+
cancellable, error))
2548+
return FALSE;
2549+
}
24532550

24542551
if (!full_system_sync (self, out_syncstats, cancellable, error))
24552552
return FALSE;
24562553

2457-
if (!swap_bootloader (self, bootloader, self->bootversion, new_bootversion, cancellable, error))
2554+
if (!swap_bootloader (self, bootloader, loader_link, self->bootversion, new_bootversion, cancellable, error))
24582555
return FALSE;
24592556

24602557
if (out_subbootdir)

src/libostree/ostree-sysroot.c

+50-15
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,12 @@ compare_loader_configs_for_sorting (gconstpointer a_pp, gconstpointer b_pp)
601601
return compare_boot_loader_configs (a, b);
602602
}
603603

604+
static gboolean
605+
read_current_bootversion (OstreeSysroot *self,
606+
int *out_bootversion,
607+
GCancellable *cancellable,
608+
GError **error);
609+
604610
/* Read all the bootconfigs from `/boot/loader/`. */
605611
gboolean
606612
_ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self, int bootversion,
@@ -613,7 +619,16 @@ _ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self, int bootversion,
613619
g_autoptr (GPtrArray) ret_loader_configs
614620
= g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
615621

616-
g_autofree char *entries_path = g_strdup_printf ("boot/loader.%d/entries", bootversion);
622+
g_autofree char *entries_path = NULL;
623+
int current_version;
624+
if (!read_current_bootversion (self, &current_version, cancellable, error))
625+
return FALSE;
626+
627+
if (current_version == bootversion)
628+
entries_path = g_strdup ("boot/loader/entries");
629+
else
630+
entries_path = g_strdup_printf ("boot/loader.%d/entries", bootversion);
631+
617632
gboolean entries_exists;
618633
g_auto (GLnxDirFdIterator) dfd_iter = {
619634
0,
@@ -660,7 +675,7 @@ _ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self, int bootversion,
660675
return TRUE;
661676
}
662677

663-
/* Get the bootversion from the `/boot/loader` symlink. */
678+
/* Get the bootversion from the `/boot/loader` directory or symlink. */
664679
static gboolean
665680
read_current_bootversion (OstreeSysroot *self, int *out_bootversion, GCancellable *cancellable,
666681
GError **error)
@@ -673,24 +688,44 @@ read_current_bootversion (OstreeSysroot *self, int *out_bootversion, GCancellabl
673688
return FALSE;
674689
if (errno == ENOENT)
675690
{
676-
g_debug ("Didn't find $sysroot/boot/loader symlink; assuming bootversion 0");
691+
g_debug ("Didn't find $sysroot/boot/loader directory or symlink; assuming bootversion 0");
677692
ret_bootversion = 0;
678693
}
679694
else
680695
{
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;
696+
if (S_ISLNK (stbuf.st_mode))
697+
{
698+
/* Traditional link, check version by reading link name */
699+
g_autofree char *target =
700+
glnx_readlinkat_malloc (self->sysroot_fd, "boot/loader", cancellable, error);
701+
if (!target)
702+
return FALSE;
703+
if (g_strcmp0 (target, "loader.0") == 0)
704+
ret_bootversion = 0;
705+
else if (g_strcmp0 (target, "loader.1") == 0)
706+
ret_bootversion = 1;
707+
else
708+
return glnx_throw (error, "Invalid target '%s' in boot/loader", target);
709+
}
692710
else
693-
return glnx_throw (error, "Invalid target '%s' in boot/loader", target);
711+
{
712+
/* Loader is a directory, check version by reading ostree_bootversion */
713+
gsize len;
714+
g_autofree char* version =
715+
glnx_file_get_contents_utf8_at(self->sysroot_fd, "boot/loader/ostree_bootversion",
716+
&len, cancellable, error);
717+
if (version == NULL)
718+
{
719+
g_debug ("Invalid boot/loader/ostree_bootversion, assuming bootversion 0");
720+
ret_bootversion = 0;
721+
}
722+
else if (g_strcmp0 (version, "loader.0") == 0)
723+
ret_bootversion = 0;
724+
else if (g_strcmp0 (version, "loader.1") == 0)
725+
ret_bootversion = 1;
726+
else
727+
return glnx_throw (error, "Invalid version '%s' in boot/loader/ostree_bootversion", version);
728+
}
694729
}
695730

696731
*out_bootversion = ret_bootversion;

src/switchroot/ostree-prepare-root.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ main (int argc, char *argv[])
515515
* at /boot inside the deployment. */
516516
if (snprintf (srcpath, sizeof (srcpath), "%s/boot/loader", root_mountpoint) < 0)
517517
err (EXIT_FAILURE, "failed to assemble /boot/loader path");
518-
if (lstat (srcpath, &stbuf) == 0 && S_ISLNK (stbuf.st_mode))
518+
if (lstat (srcpath, &stbuf) == 0 && (S_ISLNK (stbuf.st_mode) || S_ISDIR (stbuf.st_mode)))
519519
{
520520
if (lstat ("boot", &stbuf) == 0 && S_ISDIR (stbuf.st_mode))
521521
{

0 commit comments

Comments
 (0)