@@ -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 */
22082267static 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 )
0 commit comments