@@ -4,9 +4,10 @@ use std::io::BufWriter;
4
4
use std:: io:: Write ;
5
5
use std:: process:: Command ;
6
6
use std:: process:: Stdio ;
7
+ use std:: str:: FromStr ;
7
8
use std:: sync:: Arc ;
8
9
9
- use anyhow:: { Context , Result } ;
10
+ use anyhow:: { anyhow , Context , Result } ;
10
11
use camino:: Utf8Path ;
11
12
use camino:: Utf8PathBuf ;
12
13
use cap_std:: fs:: Dir ;
@@ -199,6 +200,68 @@ struct InstallAleph {
199
200
kernel : String ,
200
201
}
201
202
203
+ /// A mount specification is a subset of a line in `/etc/fstab`.
204
+ ///
205
+ /// There are 3 (ASCII) whitespace separated values:
206
+ ///
207
+ /// SOURCE TARGET [OPTIONS]
208
+ ///
209
+ /// Examples:
210
+ /// - /dev/vda3 /boot ext4 ro
211
+ /// - /dev/nvme0n1p4 /
212
+ /// - /dev/sda2 /var/mnt xfs
213
+ #[ derive( Debug , Clone ) ]
214
+ pub ( crate ) struct MountSpec {
215
+ pub ( crate ) source : String ,
216
+ pub ( crate ) target : String ,
217
+ pub ( crate ) fstype : String ,
218
+ pub ( crate ) options : Option < String > ,
219
+ }
220
+
221
+ impl MountSpec {
222
+ const AUTO : & ' static str = "auto" ;
223
+
224
+ pub ( crate ) fn new ( src : & str , target : & str ) -> Self {
225
+ MountSpec {
226
+ source : src. to_string ( ) ,
227
+ target : target. to_string ( ) ,
228
+ fstype : Self :: AUTO . to_string ( ) ,
229
+ options : None ,
230
+ }
231
+ }
232
+
233
+ pub ( crate ) fn to_fstab ( & self ) -> String {
234
+ let options = self . options . as_deref ( ) . unwrap_or ( "defaults" ) ;
235
+ format ! (
236
+ "{} {} {} {} 0 0" ,
237
+ self . source, self . target, self . fstype, options
238
+ )
239
+ }
240
+ }
241
+
242
+ impl FromStr for MountSpec {
243
+ type Err = anyhow:: Error ;
244
+
245
+ fn from_str ( s : & str ) -> Result < Self > {
246
+ let mut parts = s. split_ascii_whitespace ( ) . fuse ( ) ;
247
+ let source = parts. next ( ) . unwrap_or_default ( ) ;
248
+ if source. is_empty ( ) {
249
+ anyhow:: bail!( "Invalid empty mount specification" ) ;
250
+ }
251
+ let target = parts
252
+ . next ( )
253
+ . ok_or_else ( || anyhow ! ( "Missing target in mount specification {s}" ) ) ?;
254
+ let fstype = parts. next ( ) . unwrap_or ( Self :: AUTO ) ;
255
+ let options = parts. next ( ) . map ( ToOwned :: to_owned) ;
256
+ Ok ( Self {
257
+ source : source. to_string ( ) ,
258
+ fstype : fstype. to_string ( ) ,
259
+ target : target. to_string ( ) ,
260
+ options,
261
+ } )
262
+ }
263
+ }
264
+
202
265
fn sgdisk_partition (
203
266
sgdisk : & mut Command ,
204
267
n : u32 ,
@@ -411,9 +474,7 @@ async fn initialize_ostree_root_from_self(
411
474
. context ( "Opening etc/fstab" )
412
475
. map ( BufWriter :: new) ?
413
476
} ;
414
- let boot_uuid = & root_setup. boot_uuid ;
415
- let bootfs_type_str = root_setup. bootfs_type . to_string ( ) ;
416
- writeln ! ( f, "UUID={boot_uuid} /boot {bootfs_type_str} defaults 1 2" ) ?;
477
+ writeln ! ( f, "{}" , root_setup. boot. to_fstab( ) ) ?;
417
478
f. flush ( ) ?;
418
479
419
480
let uname = cap_std_ext:: rustix:: process:: uname ( ) ;
@@ -481,11 +542,24 @@ struct RootSetup {
481
542
device : Utf8PathBuf ,
482
543
rootfs : Utf8PathBuf ,
483
544
rootfs_fd : Dir ,
484
- bootfs_type : Filesystem ,
485
- boot_uuid : uuid:: Uuid ,
545
+ boot : MountSpec ,
486
546
kargs : Vec < String > ,
487
547
}
488
548
549
+ impl RootSetup {
550
+ /// Get the UUID= mount specifier for the /boot filesystem. At the current time this is
551
+ /// required.
552
+ fn get_boot_uuid ( & self ) -> Result < & str > {
553
+ let bootsrc = & self . boot . source ;
554
+ if let Some ( ( t, rest) ) = bootsrc. split_once ( '=' ) {
555
+ if t. eq_ignore_ascii_case ( "uuid" ) {
556
+ return Ok ( rest) ;
557
+ }
558
+ }
559
+ anyhow:: bail!( "/boot is not specified via UUID= (this is currently required): {bootsrc}" )
560
+ }
561
+ }
562
+
489
563
#[ context( "Creating rootfs" ) ]
490
564
fn install_create_rootfs ( state : & State , opts : InstallBlockDeviceOpts ) -> Result < RootSetup > {
491
565
// Verify that the target is empty (if not already wiped in particular, but it's
@@ -622,7 +696,9 @@ fn install_create_rootfs(state: &State, opts: InstallBlockDeviceOpts) -> Result<
622
696
let rootdev = & format ! ( "{device}{ROOTPN}" ) ;
623
697
let root_uuid = mkfs ( rootdev, opts. filesystem , Some ( "root" ) , [ ] ) ?;
624
698
let rootarg = format ! ( "root=UUID={root_uuid}" ) ;
625
- let bootarg = format ! ( "boot=UUID={boot_uuid}" ) ;
699
+ let bootsrc = format ! ( "UUID={boot_uuid}" ) ;
700
+ let bootarg = format ! ( "boot={bootsrc}" ) ;
701
+ let boot = MountSpec :: new ( bootsrc. as_str ( ) , "/boot" ) ;
626
702
let kargs = vec ! [ rootarg, RW_KARG . to_string( ) , bootarg] ;
627
703
628
704
mount ( rootdev, & rootfs) ?;
@@ -651,8 +727,7 @@ fn install_create_rootfs(state: &State, opts: InstallBlockDeviceOpts) -> Result<
651
727
device,
652
728
rootfs,
653
729
rootfs_fd,
654
- bootfs_type,
655
- boot_uuid,
730
+ boot,
656
731
kargs,
657
732
} )
658
733
}
@@ -829,7 +904,8 @@ pub(crate) async fn install(opts: InstallOpts) -> Result<()> {
829
904
. context ( "Writing aleph version" ) ?;
830
905
}
831
906
832
- crate :: bootloader:: install_via_bootupd ( & rootfs. device , & rootfs. rootfs , & rootfs. boot_uuid ) ?;
907
+ let boot_uuid = rootfs. get_boot_uuid ( ) ?;
908
+ crate :: bootloader:: install_via_bootupd ( & rootfs. device , & rootfs. rootfs , boot_uuid) ?;
833
909
834
910
// If Ignition is specified, enable it
835
911
if let Some ( ignition_file) = state. config_opts . ignition_file . as_deref ( ) {
0 commit comments