11//! This module handles the bootc-owned kernel argument lists in `/usr/lib/bootc/kargs.d`.
22use anyhow:: { Context , Result } ;
3+ use bootc_kernel_cmdline:: utf8:: Cmdline ;
34use camino:: Utf8Path ;
45use cap_std_ext:: cap_std:: fs:: Dir ;
56use cap_std_ext:: cap_std:: fs_utf8:: Dir as DirUtf8 ;
@@ -20,11 +21,11 @@ use crate::store::Storage;
2021const KARGS_PATH : & str = "usr/lib/bootc/kargs.d" ;
2122
2223/// The default root filesystem mount specification.
23- pub ( crate ) const ROOT : & str = "root= " ;
24+ pub ( crate ) const ROOT_KEY : & str = "root" ;
2425/// This is used by dracut.
2526pub ( crate ) const INITRD_ARG_PREFIX : & str = "rd." ;
2627/// The kernel argument for configuring the rootfs flags.
27- pub ( crate ) const ROOTFLAGS : & str = "rootflags= " ;
28+ pub ( crate ) const ROOTFLAGS_KEY : & str = "rootflags" ;
2829
2930/// The kargs.d configuration file.
3031#[ derive( Deserialize ) ]
@@ -46,39 +47,44 @@ impl Config {
4647
4748/// Load and parse all bootc kargs.d files in the specified root, returning
4849/// a combined list.
49- pub ( crate ) fn get_kargs_in_root ( d : & Dir , sys_arch : & str ) -> Result < Vec < String > > {
50+ pub ( crate ) fn get_kargs_in_root ( d : & Dir , sys_arch : & str ) -> Result < Cmdline < ' static > > {
5051 // If the directory doesn't exist, that's OK.
5152 let Some ( d) = d. open_dir_optional ( KARGS_PATH ) ?. map ( DirUtf8 :: from_cap_std) else {
5253 return Ok ( Default :: default ( ) ) ;
5354 } ;
54- let mut ret = Vec :: new ( ) ;
55+ let mut ret = Cmdline :: new ( ) ;
5556 let entries = d. filenames_filtered_sorted ( |_, name| Config :: filename_matches ( name) ) ?;
5657 for name in entries {
5758 let buf = d. read_to_string ( & name) ?;
58- let kargs = parse_kargs_toml ( & buf, sys_arch) . with_context ( || format ! ( "Parsing {name}" ) ) ?;
59- ret. extend ( kargs)
59+ if let Some ( kargs) =
60+ parse_kargs_toml ( & buf, sys_arch) . with_context ( || format ! ( "Parsing {name}" ) ) ?
61+ {
62+ ret. extend ( & kargs)
63+ }
6064 }
6165 Ok ( ret)
6266}
6367
64- pub ( crate ) fn root_args_from_cmdline < ' a > ( cmdline : & ' a [ & str ] ) -> Vec < & ' a str > {
65- cmdline
66- . iter ( )
67- . filter ( |arg| {
68- arg. starts_with ( ROOT )
69- || arg. starts_with ( ROOTFLAGS )
70- || arg. starts_with ( INITRD_ARG_PREFIX )
71- } )
72- . copied ( )
73- . collect ( )
68+ pub ( crate ) fn root_args_from_cmdline ( cmdline : & Cmdline ) -> Cmdline < ' static > {
69+ let mut result = Cmdline :: new ( ) ;
70+ for param in cmdline {
71+ let key = param. key ( ) ;
72+ if key == ROOT_KEY . into ( )
73+ || key == ROOTFLAGS_KEY . into ( )
74+ || key. starts_with ( INITRD_ARG_PREFIX )
75+ {
76+ result. add ( & param) ;
77+ }
78+ }
79+ result
7480}
7581
7682/// Load kargs.d files from the target ostree commit root
7783pub ( crate ) fn get_kargs_from_ostree_root (
7884 repo : & ostree:: Repo ,
7985 root : & ostree:: RepoFile ,
8086 sys_arch : & str ,
81- ) -> Result < Vec < String > > {
87+ ) -> Result < Cmdline < ' static > > {
8288 let kargsd = root. resolve_relative_path ( KARGS_PATH ) ;
8389 let kargsd = kargsd. downcast_ref :: < ostree:: RepoFile > ( ) . expect ( "downcast" ) ;
8490 if !kargsd. query_exists ( gio:: Cancellable :: NONE ) {
@@ -92,12 +98,12 @@ fn get_kargs_from_ostree(
9298 repo : & ostree:: Repo ,
9399 fetched_tree : & ostree:: RepoFile ,
94100 sys_arch : & str ,
95- ) -> Result < Vec < String > > {
101+ ) -> Result < Cmdline < ' static > > {
96102 let cancellable = gio:: Cancellable :: NONE ;
97103 let queryattrs = "standard::name,standard::type" ;
98104 let queryflags = gio:: FileQueryInfoFlags :: NOFOLLOW_SYMLINKS ;
99105 let fetched_iter = fetched_tree. enumerate_children ( queryattrs, queryflags, cancellable) ?;
100- let mut ret = Vec :: new ( ) ;
106+ let mut ret = Cmdline :: new ( ) ;
101107 while let Some ( fetched_info) = fetched_iter. next_file ( cancellable) ? {
102108 // only read and parse the file if it is a toml file
103109 let name = fetched_info. name ( ) ;
@@ -119,9 +125,11 @@ fn get_kargs_from_ostree(
119125 let mut reader =
120126 ostree_ext:: prelude:: InputStreamExtManual :: into_read ( file_content. unwrap ( ) ) ;
121127 let s = std:: io:: read_to_string ( & mut reader) ?;
122- let parsed_kargs =
123- parse_kargs_toml ( & s, sys_arch) . with_context ( || format ! ( "Parsing {name}" ) ) ?;
124- ret. extend ( parsed_kargs) ;
128+ if let Some ( parsed_kargs) =
129+ parse_kargs_toml ( & s, sys_arch) . with_context ( || format ! ( "Parsing {name}" ) ) ?
130+ {
131+ ret. extend ( & parsed_kargs) ;
132+ }
125133 }
126134 Ok ( ret)
127135}
@@ -133,20 +141,19 @@ pub(crate) fn get_kargs(
133141 sysroot : & Storage ,
134142 merge_deployment : & Deployment ,
135143 fetched : & ImageState ,
136- ) -> Result < Vec < String > > {
144+ ) -> Result < Cmdline < ' static > > {
137145 let cancellable = gio:: Cancellable :: NONE ;
138146 let ostree = sysroot. get_ostree ( ) ?;
139147 let repo = & ostree. repo ( ) ;
140- let mut kargs = vec ! [ ] ;
141148 let sys_arch = std:: env:: consts:: ARCH ;
142149
143150 // Get the kargs used for the merge in the bootloader config
144- if let Some ( bootconfig ) = ostree:: Deployment :: bootconfig ( merge_deployment) {
145- if let Some ( options ) = ostree :: BootconfigParser :: get ( & bootconfig, "options" ) {
146- let options = options . split_whitespace ( ) . map ( |s| s . to_owned ( ) ) ;
147- kargs . extend ( options) ;
148- }
149- } ;
151+ let mut kargs = ostree:: Deployment :: bootconfig ( merge_deployment)
152+ . and_then ( | bootconfig| {
153+ ostree :: BootconfigParser :: get ( & bootconfig , " options" )
154+ . map ( | options| Cmdline :: from ( options . to_string ( ) ) )
155+ } )
156+ . unwrap_or_default ( ) ;
150157
151158 // Get the kargs in kargs.d of the merge
152159 let merge_root = & crate :: utils:: deployment_fd ( ostree, merge_deployment) ?;
@@ -161,50 +168,56 @@ pub(crate) fn get_kargs(
161168 // A special case: if there's no kargs.d directory in the pending (fetched) image,
162169 // then we can just use the combined current kargs + kargs from booted
163170 if !fetched_tree. query_exists ( cancellable) {
164- kargs. extend ( existing_kargs) ;
171+ kargs. extend ( & existing_kargs) ;
165172 return Ok ( kargs) ;
166173 }
167174
168175 // Fetch the kernel arguments from the new root
169176 let remote_kargs = get_kargs_from_ostree ( repo, & fetched_tree, sys_arch) ?;
170177
171- // get the diff between the existing and remote kargs
172- let mut added_kargs = remote_kargs
173- . clone ( )
174- . into_iter ( )
175- . filter ( |item| !existing_kargs. contains ( item) )
176- . collect :: < Vec < _ > > ( ) ;
177- let removed_kargs = existing_kargs
178- . clone ( )
179- . into_iter ( )
180- . filter ( |item| !remote_kargs. contains ( item) )
181- . collect :: < Vec < _ > > ( ) ;
178+ // Calculate the diff between the existing and remote kargs
179+ let added_kargs: Vec < _ > = remote_kargs
180+ . iter ( )
181+ . filter ( |item| !existing_kargs. iter ( ) . any ( |existing| * item == existing) )
182+ . collect ( ) ;
183+ let removed_kargs: Vec < _ > = existing_kargs
184+ . iter ( )
185+ . filter ( |item| !remote_kargs. iter ( ) . any ( |remote| * item == remote) )
186+ . collect ( ) ;
182187
183188 tracing:: debug!(
184189 "kargs: added={:?} removed={:?}" ,
185190 & added_kargs,
186191 removed_kargs
187192 ) ;
188193
189- // apply the diff to the system kargs
190- kargs. retain ( |x| !removed_kargs. contains ( x) ) ;
191- kargs. append ( & mut added_kargs) ;
194+ // Apply the diff to the system kargs
195+ for arg in & removed_kargs {
196+ kargs. remove_exact ( arg) ;
197+ }
198+ for arg in & added_kargs {
199+ kargs. add ( arg) ;
200+ }
192201
193202 Ok ( kargs)
194203}
195204
196205/// This parses a bootc kargs.d toml file, returning the resulting
197206/// vector of kernel arguments. Architecture matching is performed using
198207/// `sys_arch`.
199- fn parse_kargs_toml ( contents : & str , sys_arch : & str ) -> Result < Vec < String > > {
208+ fn parse_kargs_toml ( contents : & str , sys_arch : & str ) -> Result < Option < Cmdline < ' static > > > {
200209 let de: Config = toml:: from_str ( contents) ?;
201210 // if arch specified, apply kargs only if the arch matches
202211 // if arch not specified, apply kargs unconditionally
203212 let matched = de
204213 . match_architectures
205214 . map ( |arches| arches. iter ( ) . any ( |s| s == sys_arch) )
206215 . unwrap_or ( true ) ;
207- let r = if matched { de. kargs } else { Vec :: new ( ) } ;
216+ let r = if matched {
217+ Some ( Cmdline :: from ( de. kargs . join ( " " ) ) )
218+ } else {
219+ None
220+ } ;
208221 Ok ( r)
209222}
210223
@@ -216,17 +229,23 @@ mod tests {
216229
217230 use super :: * ;
218231
232+ fn assert_cmdline_eq ( cmdline : & Cmdline , expected_params : & [ & str ] ) {
233+ let actual_params: Vec < _ > = cmdline. iter_str ( ) . collect ( ) ;
234+ assert_eq ! ( actual_params, expected_params) ;
235+ }
236+
219237 #[ test]
220238 /// Verify that kargs are only applied to supported architectures
221239 fn test_arch ( ) {
222240 // no arch specified, kargs ensure that kargs are applied unconditionally
223241 let sys_arch = "x86_64" ;
224242 let file_content = r##"kargs = ["console=tty0", "nosmt"]"## . to_string ( ) ;
225- let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
226- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
243+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
244+ assert_cmdline_eq ( & parsed_kargs, & [ "console=tty0" , "nosmt" ] ) ;
245+
227246 let sys_arch = "aarch64" ;
228- let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
229- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
247+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
248+ assert_cmdline_eq ( & parsed_kargs, & [ "console=tty0" , "nosmt" ] ) ;
230249
231250 // one arch matches and one doesn't, ensure that kargs are only applied for the matching arch
232251 let sys_arch = "aarch64" ;
@@ -235,25 +254,26 @@ match-architectures = ["x86_64"]
235254"##
236255 . to_string ( ) ;
237256 let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
238- assert_eq ! ( parsed_kargs, [ ] as [ String ; 0 ] ) ;
257+ assert ! ( parsed_kargs. is_none ( ) ) ;
239258 let file_content = r##"kargs = ["console=tty0", "nosmt"]
240259match-architectures = ["aarch64"]
241260"##
242261 . to_string ( ) ;
243- let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
244- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
262+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
263+ assert_cmdline_eq ( & parsed_kargs, & [ "console=tty0" , "nosmt" ] ) ;
245264
246265 // multiple arch specified, ensure that kargs are applied to both archs
247266 let sys_arch = "x86_64" ;
248267 let file_content = r##"kargs = ["console=tty0", "nosmt"]
249268match-architectures = ["x86_64", "aarch64"]
250269"##
251270 . to_string ( ) ;
252- let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
253- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
271+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
272+ assert_cmdline_eq ( & parsed_kargs, & [ "console=tty0" , "nosmt" ] ) ;
273+
254274 let sys_arch = "aarch64" ;
255- let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
256- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
275+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
276+ assert_cmdline_eq ( & parsed_kargs, & [ "console=tty0" , "nosmt" ] ) ;
257277 }
258278
259279 #[ test]
@@ -285,18 +305,18 @@ match-architectures = ["x86_64", "aarch64"]
285305 let td = cap_std_ext:: cap_tempfile:: TempDir :: new ( cap_std:: ambient_authority ( ) ) ?;
286306
287307 // No directory
288- assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . len ( ) , 0 ) ;
308+ assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . iter ( ) . count ( ) , 0 ) ;
289309 // Empty directory
290310 td. create_dir_all ( "usr/lib/bootc/kargs.d" ) ?;
291- assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . len ( ) , 0 ) ;
311+ assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . iter ( ) . count ( ) , 0 ) ;
292312 // Non-toml file
293313 td. write ( "usr/lib/bootc/kargs.d/somegarbage" , "garbage" ) ?;
294- assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . len ( ) , 0 ) ;
314+ assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . iter ( ) . count ( ) , 0 ) ;
295315
296316 write_test_kargs ( & td) ?;
297317
298318 let args = get_kargs_in_root ( & td, "x86_64" ) . unwrap ( ) ;
299- similar_asserts :: assert_eq! ( args, [ "console=tty0" , "nosmt" , "console=ttyS1" ] ) ;
319+ assert_cmdline_eq ( & args, & [ "console=tty0" , "nosmt" , "console=ttyS1" ] ) ;
300320
301321 Ok ( ( ) )
302322 }
@@ -354,7 +374,7 @@ match-architectures = ["x86_64", "aarch64"]
354374
355375 ostree_commit ( repo, & test_rootfs, "." . into ( ) , "testref" ) ?;
356376 // Helper closure to read the kargs
357- let get_kargs = |sys_arch : & str | -> Result < Vec < String > > {
377+ let get_kargs = |sys_arch : & str | -> Result < Cmdline < ' static > > {
358378 let rootfs = repo. read_commit ( "testref" , cancellable) ?. 0 ;
359379 let rootfs = rootfs. downcast_ref :: < ostree:: RepoFile > ( ) . unwrap ( ) ;
360380 let fetched_tree = rootfs. resolve_relative_path ( "/usr/lib/bootc/kargs.d" ) ;
@@ -368,14 +388,14 @@ match-architectures = ["x86_64", "aarch64"]
368388 } ;
369389
370390 // rootfs is empty
371- assert_eq ! ( get_kargs( "x86_64" ) . unwrap( ) . len ( ) , 0 ) ;
391+ assert_eq ! ( get_kargs( "x86_64" ) . unwrap( ) . iter ( ) . count ( ) , 0 ) ;
372392
373393 test_rootfs. create_dir_all ( "usr/lib/bootc/kargs.d" ) ?;
374394 write_test_kargs ( & test_rootfs) . unwrap ( ) ;
375395 ostree_commit ( repo, & test_rootfs, "." . into ( ) , "testref" ) ?;
376396
377397 let args = get_kargs ( "x86_64" ) . unwrap ( ) ;
378- similar_asserts :: assert_eq! ( args, [ "console=tty0" , "nosmt" , "console=ttyS1" ] ) ;
398+ assert_cmdline_eq ( & args, & [ "console=tty0" , "nosmt" , "console=ttyS1" ] ) ;
379399
380400 Ok ( ( ) )
381401 }
0 commit comments