@@ -2212,10 +2212,60 @@ prepare_new_bootloader_link (OstreeSysroot *sysroot, int current_bootversion, in
2212
2212
return TRUE;
2213
2213
}
2214
2214
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
+
2215
2265
/* Update the /boot/loader symlink to point to /boot/loader.$new_bootversion */
2216
2266
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 )
2219
2269
{
2220
2270
GLNX_AUTO_PREFIX_ERROR ("Final bootloader swap" , error );
2221
2271
@@ -2225,12 +2275,22 @@ swap_bootloader (OstreeSysroot *sysroot, OstreeBootloader *bootloader, int curre
2225
2275
if (!_ostree_sysroot_ensure_boot_fd (sysroot , error ))
2226
2276
return FALSE;
2227
2277
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
+ }
2234
2294
2235
2295
/* Now we explicitly fsync this directory, even though it
2236
2296
* isn't required for atomicity, for two reasons:
@@ -2448,13 +2508,50 @@ write_deployments_bootswap (OstreeSysroot *self, GPtrArray *new_deployments,
2448
2508
return glnx_prefix_error (error , "Bootloader write config" );
2449
2509
}
2450
2510
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 ))
2452
2515
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
+ }
2453
2550
2454
2551
if (!full_system_sync (self , out_syncstats , cancellable , error ))
2455
2552
return FALSE;
2456
2553
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 ))
2458
2555
return FALSE;
2459
2556
2460
2557
if (out_subbootdir )
0 commit comments