@@ -108,7 +108,7 @@ use ostree_ext::container_utils::ostree_booted;
108108use ostree_ext:: prelude:: FileExt ;
109109use ostree_ext:: sysroot:: SysrootLock ;
110110use ostree_ext:: { gio, ostree} ;
111- use rustix:: fs:: Mode ;
111+ use rustix:: { fd :: AsFd , fs:: Mode , fs :: StatVfsMountFlags } ;
112112
113113use composefs:: fsverity:: Sha512HashValue ;
114114use composefs:: repository:: RepositoryConfig ;
@@ -302,15 +302,51 @@ pub(crate) enum BootedStorageKind<'a> {
302302}
303303
304304/// Open the physical root (/sysroot) and /run directories for a booted system.
305- fn get_physical_root_and_run ( ) -> Result < ( Dir , Dir ) > {
306- let physical_root = {
307- let d = Dir :: open_ambient_dir ( "/sysroot" , cap_std:: ambient_authority ( ) )
308- . context ( "Opening /sysroot" ) ?;
309- open_dir_remount_rw ( & d, "." . into ( ) ) ?
310- } ;
305+ ///
306+ /// The returned boolean is `true` if `/sysroot` is on a physically read-only
307+ /// medium (e.g. a live ISO) that cannot be remounted writable.
308+ ///
309+ /// Note that ostree mounts `/sysroot` read-only by default even on writable
310+ /// systems and remounts it writable on demand, so being read-only is not by
311+ /// itself meaningful: we only conclude the system is read-only if a remount
312+ /// attempt *fails* and the backing block device confirms it.
313+ fn get_physical_root_and_run ( ) -> Result < ( Dir , Dir , bool ) > {
314+ let d = Dir :: open_ambient_dir ( "/sysroot" , cap_std:: ambient_authority ( ) )
315+ . context ( "Opening /sysroot" ) ?;
316+ let is_ro = sysroot_is_read_only ( & d) ?;
317+ let physical_root = d. open_dir ( "." ) . context ( "Opening /sysroot" ) ?;
311318 let run =
312319 Dir :: open_ambient_dir ( "/run" , cap_std:: ambient_authority ( ) ) . context ( "Opening /run" ) ?;
313- Ok ( ( physical_root, run) )
320+ Ok ( ( physical_root, run, is_ro) )
321+ }
322+
323+ /// Determine whether `/sysroot` (given as `d`) is on a physically read-only
324+ /// block device, remounting it read-write as a side effect when necessary.
325+ ///
326+ /// ostree mounts `/sysroot` read-only by default even on writable systems and
327+ /// remounts it writable on demand, so the read-only mount flag alone is not
328+ /// meaningful. The authoritative signal is the backing block device's read-only
329+ /// flag, which is set for a live ISO (a read-only loop device over an immutable
330+ /// rootfs image) and propagates from a whole disk to its partitions. We check
331+ /// that first and avoid even attempting a remount that is bound to fail.
332+ fn sysroot_is_read_only ( d : & Dir ) -> Result < bool > {
333+ let st = rustix:: fs:: fstatvfs ( d. as_fd ( ) ) ?;
334+ if !st. f_flag . contains ( StatVfsMountFlags :: RDONLY ) {
335+ // Already writable: the common case.
336+ return Ok ( false ) ;
337+ }
338+
339+ // The backing block device is physically read-only (e.g. a live ISO); there
340+ // is no point attempting a remount, and write operations are not possible.
341+ if matches ! ( bootc_blockdev:: is_dir_backing_device_ro( d) , Ok ( Some ( true ) ) ) {
342+ return Ok ( true ) ;
343+ }
344+
345+ // The mount is read-only but the device is writable: this is the normal
346+ // ostree case where /sysroot is mounted read-only by default. Remount it
347+ // writable on demand; a failure here is an unexpected error.
348+ open_dir_remount_rw ( d, "." . into ( ) ) ?;
349+ Ok ( false )
314350}
315351
316352impl BootedStorage {
@@ -321,7 +357,7 @@ impl BootedStorage {
321357 pub ( crate ) async fn new ( env : Environment ) -> Result < Option < Self > > {
322358 let r = match & env {
323359 Environment :: ComposefsBooted ( cmdline) => {
324- let ( physical_root, run) = get_physical_root_and_run ( ) ?;
360+ let ( physical_root, run, is_ro ) = get_physical_root_and_run ( ) ?;
325361 let mut composefs = ComposefsRepository :: open_path ( & physical_root, COMPOSEFS ) ?;
326362 if cmdline. allow_missing_fsverity {
327363 composefs. set_insecure ( ) ;
@@ -346,6 +382,7 @@ impl BootedStorage {
346382 let storage = Storage {
347383 physical_root,
348384 physical_root_path : Utf8PathBuf :: from ( "/sysroot" ) ,
385+ is_ro,
349386 run,
350387 boot_dir : Some ( boot_dir) ,
351388 esp : Some ( esp_mount) ,
@@ -372,16 +409,25 @@ impl BootedStorage {
372409 // remount /sysroot as writable, and we call set_mount_namespace_in_use()
373410 // to indicate we're in a mount namespace. Without actually being in a
374411 // mount namespace, this would leave the global /sysroot writable.
375- let ( physical_root, run) = get_physical_root_and_run ( ) ?;
412+ let ( physical_root, run, is_ro ) = get_physical_root_and_run ( ) ?;
376413
377414 let sysroot = ostree:: Sysroot :: new_default ( ) ;
378- sysroot. set_mount_namespace_in_use ( ) ;
379- let sysroot = ostree_ext:: sysroot:: SysrootLock :: new_from_sysroot ( & sysroot) . await ?;
415+ // On a read-only sysroot (e.g. a live ISO) we must NOT mark the mount
416+ // namespace as in-use, otherwise ostree will attempt to remount /sysroot
417+ // read-write on write operations and fail. We also avoid taking the write
418+ // lock (which would write a lockfile to the read-only filesystem).
419+ let sysroot = if is_ro {
420+ ostree_ext:: sysroot:: SysrootLock :: from_assumed_locked ( & sysroot)
421+ } else {
422+ sysroot. set_mount_namespace_in_use ( ) ;
423+ ostree_ext:: sysroot:: SysrootLock :: new_from_sysroot ( & sysroot) . await ?
424+ } ;
380425 sysroot. load ( gio:: Cancellable :: NONE ) ?;
381426
382427 let storage = Storage {
383428 physical_root,
384429 physical_root_path : Utf8PathBuf :: from ( "/sysroot" ) ,
430+ is_ro,
385431 run,
386432 boot_dir : None ,
387433 esp : None ,
@@ -432,6 +478,10 @@ pub(crate) struct Storage {
432478 /// This is `/sysroot` on a running system, or the target mount point during install.
433479 pub physical_root_path : Utf8PathBuf ,
434480
481+ /// True if the physical root (`/sysroot`) is on a physically read-only
482+ /// block device (e.g. a live ISO) and cannot be made writable.
483+ pub ( crate ) is_ro : bool ,
484+
435485 /// The 'boot' directory, useful and `Some` only for composefs systems
436486 /// For grub booted systems, this points to `/sysroot/boot`
437487 /// For systemd booted systems, this points to the ESP
@@ -498,6 +548,7 @@ impl Storage {
498548 Ok ( Self {
499549 physical_root,
500550 physical_root_path,
551+ is_ro : false ,
501552 run,
502553 boot_dir : None ,
503554 esp : None ,
@@ -507,6 +558,16 @@ impl Storage {
507558 } )
508559 }
509560
561+ /// Ensure the storage is writable, erroring with a clear message if the
562+ /// underlying `/sysroot` is on a read-only block device (e.g. a live ISO).
563+ pub ( crate ) fn require_writable ( & self ) -> Result < ( ) > {
564+ anyhow:: ensure!(
565+ !self . is_ro,
566+ "Cannot perform this operation: /sysroot is on a read-only block device (e.g. a live ISO)"
567+ ) ;
568+ Ok ( ( ) )
569+ }
570+
510571 /// Returns `boot_dir` if it exists
511572 pub ( crate ) fn require_boot_dir ( & self ) -> Result < & Dir > {
512573 self . boot_dir
0 commit comments