Skip to content

Commit

Permalink
deploy: Create fake symlinks when on FAT filesystems
Browse files Browse the repository at this point in the history
When trying to deploy on a vfat partitions, such as the EFI ESP, we
fail to create the loader symlink, and deployment fails.

We'd like to use sd-boot, which only works with the ESP, so we need
a way to deploy without using symlinks.

For a quick and dirty minimally invasive fix, I've made deployment
create a bogus symlink - a text file named loader.sln that contains
the name of the real loader dir.

I've modified systemd-boot to read these bogus symlinks as well.

https://phabricator.endlessm.com/T27040
  • Loading branch information
Derek Foreman authored and dbnicholson committed Mar 17, 2021
1 parent 1319537 commit 052f2fd
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 17 deletions.
81 changes: 74 additions & 7 deletions src/libostree/ostree-sysroot-deploy.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
#include <sys/poll.h>
#include <linux/fs.h>
#include <err.h>
#include <sys/statfs.h>
#include <linux/magic.h>

#ifdef HAVE_LIBMOUNT
#include <libmount.h>
Expand Down Expand Up @@ -59,6 +61,35 @@
static gboolean
is_ro_mount (const char *path);

static gboolean
fs_is_fat (int parent_dfd)
{
struct statfs buf;

if (TEMP_FAILURE_RETRY (fstatfs (parent_dfd, &buf)) < 0)
return FALSE;

if (buf.f_type == MSDOS_SUPER_MAGIC)
return TRUE;

return FALSE;
}

static gboolean
symlink_fat (const char *target,
int parent_dfd,
const char *linkpath,
GCancellable *cancellable,
GError **error)
{
if (!glnx_file_replace_contents_at(parent_dfd, linkpath, (guint8*)target,
-1, GLNX_FILE_REPLACE_DATASYNC_NEW,
cancellable, error))
return FALSE;

return TRUE;
}

/*
* Like symlinkat() but overwrites (atomically) an existing
* symlink.
Expand All @@ -70,6 +101,9 @@ symlink_at_replace (const char *oldpath,
GCancellable *cancellable,
GError **error)
{
g_autofree char *dest_dir = g_path_get_dirname(newpath);
glnx_autofd int dest_dir_dfd = -1;
gboolean fat;
/* Possibly in the future generate a temporary random name here,
* would need to move "generate a temporary name" code into
* libglnx or glib?
Expand All @@ -79,12 +113,35 @@ symlink_at_replace (const char *oldpath,
/* Clean up any stale temporary links */
(void) unlinkat (parent_dfd, temppath, 0);

/* Create the temp link */
if (TEMP_FAILURE_RETRY (symlinkat (oldpath, parent_dfd, temppath)) < 0)
return glnx_throw_errno_prefix (error, "symlinkat");
/* Clean up any stale FAT "symlinks" */
g_autofree char *temp_fat_path = g_strconcat (temppath, ".sln", NULL);
(void) unlinkat (parent_dfd, temp_fat_path, 0);

if (!glnx_opendirat (parent_dfd, dest_dir, TRUE, &dest_dir_dfd, error))
return FALSE;

fat = fs_is_fat (dest_dir_dfd);

/* Create the temp link */
if (!fat)
{
if (TEMP_FAILURE_RETRY (symlinkat (oldpath, parent_dfd, temppath)) < 0)
return glnx_throw_errno_prefix (error, "symlinkat");
}
else
{
if (!symlink_fat (oldpath, parent_dfd, temp_fat_path, cancellable, error))
return glnx_throw_errno_prefix (error, "symlinkat");
}
/* Rename it into place */
if (!glnx_renameat (parent_dfd, temppath, parent_dfd, newpath, error))
if (fat)
{
g_autofree char *new_fat_path = g_strconcat (newpath, ".sln", NULL);
if (!glnx_renameat (parent_dfd, temp_fat_path, parent_dfd, new_fat_path, error))
return FALSE;
return TRUE;
}
else if (!glnx_renameat (parent_dfd, temppath, parent_dfd, newpath, error))
return FALSE;

return TRUE;
Expand Down Expand Up @@ -2076,17 +2133,27 @@ swap_bootloader (OstreeSysroot *sysroot,
g_assert ((current_bootversion == 0 && new_bootversion == 1) ||
(current_bootversion == 1 && new_bootversion == 0));

gboolean fat;
glnx_autofd int boot_dfd = -1;
if (!glnx_opendirat (sysroot->sysroot_fd, "boot", TRUE, &boot_dfd, error))
return FALSE;

fat = fs_is_fat (boot_dfd);

/* 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 (boot_dfd, "loader.tmp", boot_dfd, "loader", error))
return FALSE;

if (!fat)
{
if (!glnx_renameat (boot_dfd, "loader.tmp", boot_dfd, "loader", error))
return FALSE;
}
else
{
if (!glnx_renameat (boot_dfd, "loader.tmp.sln", boot_dfd, "loader.sln", error))
return FALSE;
}
/* Now we explicitly fsync this directory, even though it
* isn't required for atomicity, for two reasons:
* - It should be very cheap as we're just syncing whatever
Expand Down
25 changes: 15 additions & 10 deletions src/libostree/ostree-sysroot.c
Original file line number Diff line number Diff line change
Expand Up @@ -580,32 +580,37 @@ read_current_bootversion (OstreeSysroot *self,
GCancellable *cancellable,
GError **error)
{
int ret_bootversion;
int ret_bootversion = 0;
struct stat stbuf;
g_autofree char *target = NULL;

if (!glnx_fstatat_allow_noent (self->sysroot_fd, "boot/loader", &stbuf, AT_SYMLINK_NOFOLLOW, error))
return FALSE;
if (errno == ENOENT)
{
ret_bootversion = 0;
/* Don't share the error because we want error handling to be the same as before we
* added fake symlinks */
target = glnx_file_get_contents_utf8_at(self->sysroot_fd, "boot/loader.sln", NULL, cancellable, NULL);
if (!target) goto not_found;
}
else
{
if (!S_ISLNK (stbuf.st_mode))
return glnx_throw (error, "Not a symbolic link: boot/loader");

g_autofree char *target =
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);
}

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);

not_found:
*out_bootversion = ret_bootversion;
return TRUE;
}
Expand Down

0 comments on commit 052f2fd

Please sign in to comment.