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 ;
@@ -46,39 +47,42 @@ 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 :: default ( ) ;
70+ for param in cmdline {
71+ let key = param . key ( ) ;
72+ if key . starts_with ( ROOT ) || key . starts_with ( ROOTFLAGS ) || key . starts_with ( INITRD_ARG_PREFIX )
73+ {
74+ result . add ( & param ) ;
75+ }
76+ }
77+ result
7478}
7579
7680/// Load kargs.d files from the target ostree commit root
7781pub ( crate ) fn get_kargs_from_ostree_root (
7882 repo : & ostree:: Repo ,
7983 root : & ostree:: RepoFile ,
8084 sys_arch : & str ,
81- ) -> Result < Vec < String > > {
85+ ) -> Result < Cmdline < ' static > > {
8286 let kargsd = root. resolve_relative_path ( KARGS_PATH ) ;
8387 let kargsd = kargsd. downcast_ref :: < ostree:: RepoFile > ( ) . expect ( "downcast" ) ;
8488 if !kargsd. query_exists ( gio:: Cancellable :: NONE ) {
@@ -92,12 +96,12 @@ fn get_kargs_from_ostree(
9296 repo : & ostree:: Repo ,
9397 fetched_tree : & ostree:: RepoFile ,
9498 sys_arch : & str ,
95- ) -> Result < Vec < String > > {
99+ ) -> Result < Cmdline < ' static > > {
96100 let cancellable = gio:: Cancellable :: NONE ;
97101 let queryattrs = "standard::name,standard::type" ;
98102 let queryflags = gio:: FileQueryInfoFlags :: NOFOLLOW_SYMLINKS ;
99103 let fetched_iter = fetched_tree. enumerate_children ( queryattrs, queryflags, cancellable) ?;
100- let mut ret = Vec :: new ( ) ;
104+ let mut ret = Cmdline :: new ( ) ;
101105 while let Some ( fetched_info) = fetched_iter. next_file ( cancellable) ? {
102106 // only read and parse the file if it is a toml file
103107 let name = fetched_info. name ( ) ;
@@ -119,9 +123,11 @@ fn get_kargs_from_ostree(
119123 let mut reader =
120124 ostree_ext:: prelude:: InputStreamExtManual :: into_read ( file_content. unwrap ( ) ) ;
121125 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) ;
126+ if let Some ( parsed_kargs) =
127+ parse_kargs_toml ( & s, sys_arch) . with_context ( || format ! ( "Parsing {name}" ) ) ?
128+ {
129+ ret. extend ( & parsed_kargs) ;
130+ }
125131 }
126132 Ok ( ret)
127133}
@@ -133,20 +139,20 @@ pub(crate) fn get_kargs(
133139 sysroot : & Storage ,
134140 merge_deployment : & Deployment ,
135141 fetched : & ImageState ,
136- ) -> Result < Vec < String > > {
142+ ) -> Result < Cmdline < ' static > > {
137143 let cancellable = gio:: Cancellable :: NONE ;
138144 let ostree = sysroot. get_ostree ( ) ?;
139145 let repo = & ostree. repo ( ) ;
140- let mut kargs = vec ! [ ] ;
141146 let sys_arch = std:: env:: consts:: ARCH ;
142147
143148 // 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- } ;
149+ let mut kargs = ostree:: Deployment :: bootconfig ( merge_deployment)
150+ . map ( |bootconfig| {
151+ ostree:: BootconfigParser :: get ( & bootconfig, "options" )
152+ . map ( |options| Cmdline :: from ( options. to_string ( ) ) )
153+ } )
154+ . flatten ( )
155+ . unwrap_or_default ( ) ;
150156
151157 // Get the kargs in kargs.d of the merge
152158 let merge_root = & crate :: utils:: deployment_fd ( ostree, merge_deployment) ?;
@@ -161,50 +167,56 @@ pub(crate) fn get_kargs(
161167 // A special case: if there's no kargs.d directory in the pending (fetched) image,
162168 // then we can just use the combined current kargs + kargs from booted
163169 if !fetched_tree. query_exists ( cancellable) {
164- kargs. extend ( existing_kargs) ;
170+ kargs. extend ( & existing_kargs) ;
165171 return Ok ( kargs) ;
166172 }
167173
168174 // Fetch the kernel arguments from the new root
169175 let remote_kargs = get_kargs_from_ostree ( repo, & fetched_tree, sys_arch) ?;
170176
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 < _ > > ( ) ;
177+ // Calculate the diff between the existing and remote kargs
178+ let added_kargs: Vec < _ > = remote_kargs
179+ . iter ( )
180+ . filter ( |item| existing_kargs. find ( & item. key ( ) ) . is_none ( ) )
181+ . collect ( ) ;
182+ let removed_kargs: Vec < _ > = existing_kargs
183+ . iter ( )
184+ . filter ( |item| remote_kargs. find ( & item. key ( ) ) . is_none ( ) )
185+ . collect ( ) ;
182186
183187 tracing:: debug!(
184188 "kargs: added={:?} removed={:?}" ,
185189 & added_kargs,
186- removed_kargs
190+ & removed_kargs
187191 ) ;
188192
189- // apply the diff to the system kargs
190- kargs. retain ( |x| !removed_kargs. contains ( x) ) ;
191- kargs. append ( & mut added_kargs) ;
193+ // Apply the diff to the system kargs
194+ for arg in & removed_kargs {
195+ kargs. remove ( & arg. key ( ) ) ;
196+ }
197+ for arg in added_kargs {
198+ kargs. add ( & arg) ;
199+ }
192200
193201 Ok ( kargs)
194202}
195203
196204/// This parses a bootc kargs.d toml file, returning the resulting
197205/// vector of kernel arguments. Architecture matching is performed using
198206/// `sys_arch`.
199- fn parse_kargs_toml ( contents : & str , sys_arch : & str ) -> Result < Vec < String > > {
207+ fn parse_kargs_toml ( contents : & str , sys_arch : & str ) -> Result < Option < Cmdline < ' static > > > {
200208 let de: Config = toml:: from_str ( contents) ?;
201209 // if arch specified, apply kargs only if the arch matches
202210 // if arch not specified, apply kargs unconditionally
203211 let matched = de
204212 . match_architectures
205213 . map ( |arches| arches. iter ( ) . any ( |s| s == sys_arch) )
206214 . unwrap_or ( true ) ;
207- let r = if matched { de. kargs } else { Vec :: new ( ) } ;
215+ let r = if matched {
216+ Some ( Cmdline :: from ( de. kargs . join ( " " ) ) )
217+ } else {
218+ None
219+ } ;
208220 Ok ( r)
209221}
210222
@@ -216,17 +228,24 @@ mod tests {
216228
217229 use super :: * ;
218230
231+ use bootc_kernel_cmdline:: utf8:: Parameter ;
232+
219233 #[ test]
220234 /// Verify that kargs are only applied to supported architectures
221235 fn test_arch ( ) {
222236 // no arch specified, kargs ensure that kargs are applied unconditionally
223237 let sys_arch = "x86_64" ;
224238 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" ] ) ;
239+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
240+ let mut iter = parsed_kargs. iter ( ) ;
241+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
242+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
243+
227244 let sys_arch = "aarch64" ;
228- let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
229- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
245+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
246+ let mut iter = parsed_kargs. iter ( ) ;
247+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
248+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
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,32 @@ 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+ let mut iter = parsed_kargs. iter ( ) ;
264+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
265+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
245266
246267 // multiple arch specified, ensure that kargs are applied to both archs
247268 let sys_arch = "x86_64" ;
248269 let file_content = r##"kargs = ["console=tty0", "nosmt"]
249270match-architectures = ["x86_64", "aarch64"]
250271"##
251272 . to_string ( ) ;
252- let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
253- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
273+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
274+ let mut iter = parsed_kargs. iter ( ) ;
275+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
276+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
277+
254278 let sys_arch = "aarch64" ;
255- let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
256- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
279+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
280+ let mut iter = parsed_kargs. iter ( ) ;
281+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
282+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
257283 }
258284
259285 #[ test]
@@ -285,18 +311,24 @@ match-architectures = ["x86_64", "aarch64"]
285311 let td = cap_std_ext:: cap_tempfile:: TempDir :: new ( cap_std:: ambient_authority ( ) ) ?;
286312
287313 // No directory
288- 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 ) ;
289315 // Empty directory
290316 td. create_dir_all ( "usr/lib/bootc/kargs.d" ) ?;
291- assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . len ( ) , 0 ) ;
317+ assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . iter ( ) . count ( ) , 0 ) ;
292318 // Non-toml file
293319 td. write ( "usr/lib/bootc/kargs.d/somegarbage" , "garbage" ) ?;
294- assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . len ( ) , 0 ) ;
320+ assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . iter ( ) . count ( ) , 0 ) ;
295321
296322 write_test_kargs ( & td) ?;
297323
298324 let args = get_kargs_in_root ( & td, "x86_64" ) . unwrap ( ) ;
299- similar_asserts:: assert_eq!( args, [ "console=tty0" , "nosmt" , "console=ttyS1" ] ) ;
325+ let mut iter = args. iter ( ) ;
326+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
327+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
328+ assert_eq ! (
329+ iter. next( ) ,
330+ Some ( Parameter :: parse( "console=ttyS1" ) . unwrap( ) )
331+ ) ;
300332
301333 Ok ( ( ) )
302334 }
@@ -354,7 +386,7 @@ match-architectures = ["x86_64", "aarch64"]
354386
355387 ostree_commit ( repo, & test_rootfs, "." . into ( ) , "testref" ) ?;
356388 // Helper closure to read the kargs
357- let get_kargs = |sys_arch : & str | -> Result < Vec < String > > {
389+ let get_kargs = |sys_arch : & str | -> Result < Cmdline < ' static > > {
358390 let rootfs = repo. read_commit ( "testref" , cancellable) ?. 0 ;
359391 let rootfs = rootfs. downcast_ref :: < ostree:: RepoFile > ( ) . unwrap ( ) ;
360392 let fetched_tree = rootfs. resolve_relative_path ( "/usr/lib/bootc/kargs.d" ) ;
@@ -368,14 +400,20 @@ match-architectures = ["x86_64", "aarch64"]
368400 } ;
369401
370402 // rootfs is empty
371- assert_eq ! ( get_kargs( "x86_64" ) . unwrap( ) . len ( ) , 0 ) ;
403+ assert_eq ! ( get_kargs( "x86_64" ) . unwrap( ) . iter ( ) . count ( ) , 0 ) ;
372404
373405 test_rootfs. create_dir_all ( "usr/lib/bootc/kargs.d" ) ?;
374406 write_test_kargs ( & test_rootfs) . unwrap ( ) ;
375407 ostree_commit ( repo, & test_rootfs, "." . into ( ) , "testref" ) ?;
376408
377409 let args = get_kargs ( "x86_64" ) . unwrap ( ) ;
378- similar_asserts:: assert_eq!( args, [ "console=tty0" , "nosmt" , "console=ttyS1" ] ) ;
410+ let mut iter = args. iter ( ) ;
411+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
412+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
413+ assert_eq ! (
414+ iter. next( ) ,
415+ Some ( Parameter :: parse( "console=ttyS1" ) . unwrap( ) )
416+ ) ;
379417
380418 Ok ( ( ) )
381419 }
0 commit comments