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,17 +47,20 @@ 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 = Default :: default ( ) ;
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}
@@ -78,7 +82,7 @@ pub(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 = Default :: default ( ) ;
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,23 +167,21 @@ 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
171177 // get the diff between the existing and remote kargs
172- let mut added_kargs = remote_kargs
173- . clone ( )
178+ let added_kargs = & remote_kargs
174179 . into_iter ( )
175- . filter ( |item| ! existing_kargs. contains ( item) )
180+ . filter ( |item| existing_kargs. find ( & item. key ( ) ) . is_none ( ) )
176181 . collect :: < Vec < _ > > ( ) ;
177- let removed_kargs = existing_kargs
178- . clone ( )
182+ let removed_kargs = & existing_kargs
179183 . into_iter ( )
180- . filter ( |item| ! remote_kargs. contains ( item) )
184+ . filter ( |item| remote_kargs. find ( & item. key ( ) ) . is_none ( ) )
181185 . collect :: < Vec < _ > > ( ) ;
182186
183187 tracing:: debug!(
@@ -187,24 +191,33 @@ pub(crate) fn get_kargs(
187191 ) ;
188192
189193 // apply the diff to the system kargs
190- kargs. retain ( |x| !removed_kargs. contains ( x) ) ;
191- kargs. append ( & mut added_kargs) ;
194+ //kargs.retain(|x| !removed_kargs.contains(x));
195+ for arg in removed_kargs {
196+ kargs. remove ( & arg. key ( ) ) ;
197+ }
198+
199+ //kargs.extend(added_kargs.iter().map(|p| *p));
200+ kargs. extend ( added_kargs. iter ( ) . cloned ( ) ) ;
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,24 @@ mod tests {
216229
217230 use super :: * ;
218231
232+ use bootc_kernel_cmdline:: utf8:: Parameter ;
233+
219234 #[ test]
220235 /// Verify that kargs are only applied to supported architectures
221236 fn test_arch ( ) {
222237 // no arch specified, kargs ensure that kargs are applied unconditionally
223238 let sys_arch = "x86_64" ;
224239 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" ] ) ;
240+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
241+ let mut iter = parsed_kargs. iter ( ) ;
242+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
243+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
244+
227245 let sys_arch = "aarch64" ;
228- let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
229- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
246+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
247+ let mut iter = parsed_kargs. iter ( ) ;
248+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
249+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
230250
231251 // one arch matches and one doesn't, ensure that kargs are only applied for the matching arch
232252 let sys_arch = "aarch64" ;
@@ -235,25 +255,32 @@ match-architectures = ["x86_64"]
235255"##
236256 . to_string ( ) ;
237257 let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
238- assert_eq ! ( parsed_kargs, [ ] as [ String ; 0 ] ) ;
258+ assert ! ( parsed_kargs. is_none ( ) ) ;
239259 let file_content = r##"kargs = ["console=tty0", "nosmt"]
240260match-architectures = ["aarch64"]
241261"##
242262 . to_string ( ) ;
243- let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
244- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
263+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
264+ let mut iter = parsed_kargs. iter ( ) ;
265+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
266+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
245267
246268 // multiple arch specified, ensure that kargs are applied to both archs
247269 let sys_arch = "x86_64" ;
248270 let file_content = r##"kargs = ["console=tty0", "nosmt"]
249271match-architectures = ["x86_64", "aarch64"]
250272"##
251273 . to_string ( ) ;
252- let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
253- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
274+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
275+ let mut iter = parsed_kargs. iter ( ) ;
276+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
277+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
278+
254279 let sys_arch = "aarch64" ;
255- let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
256- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
280+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
281+ let mut iter = parsed_kargs. iter ( ) ;
282+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
283+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
257284 }
258285
259286 #[ test]
@@ -285,18 +312,24 @@ match-architectures = ["x86_64", "aarch64"]
285312 let td = cap_std_ext:: cap_tempfile:: TempDir :: new ( cap_std:: ambient_authority ( ) ) ?;
286313
287314 // No directory
288- assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . len ( ) , 0 ) ;
315+ assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . iter ( ) . count ( ) , 0 ) ;
289316 // Empty directory
290317 td. create_dir_all ( "usr/lib/bootc/kargs.d" ) ?;
291- assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . len ( ) , 0 ) ;
318+ assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . iter ( ) . count ( ) , 0 ) ;
292319 // Non-toml file
293320 td. write ( "usr/lib/bootc/kargs.d/somegarbage" , "garbage" ) ?;
294- assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . len ( ) , 0 ) ;
321+ assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . iter ( ) . count ( ) , 0 ) ;
295322
296323 write_test_kargs ( & td) ?;
297324
298325 let args = get_kargs_in_root ( & td, "x86_64" ) . unwrap ( ) ;
299- similar_asserts:: assert_eq!( args, [ "console=tty0" , "nosmt" , "console=ttyS1" ] ) ;
326+ let mut iter = args. iter ( ) ;
327+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
328+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
329+ assert_eq ! (
330+ iter. next( ) ,
331+ Some ( Parameter :: parse( "console=ttyS1" ) . unwrap( ) )
332+ ) ;
300333
301334 Ok ( ( ) )
302335 }
@@ -354,7 +387,7 @@ match-architectures = ["x86_64", "aarch64"]
354387
355388 ostree_commit ( repo, & test_rootfs, "." . into ( ) , "testref" ) ?;
356389 // Helper closure to read the kargs
357- let get_kargs = |sys_arch : & str | -> Result < Vec < String > > {
390+ let get_kargs = |sys_arch : & str | -> Result < Cmdline < ' static > > {
358391 let rootfs = repo. read_commit ( "testref" , cancellable) ?. 0 ;
359392 let rootfs = rootfs. downcast_ref :: < ostree:: RepoFile > ( ) . unwrap ( ) ;
360393 let fetched_tree = rootfs. resolve_relative_path ( "/usr/lib/bootc/kargs.d" ) ;
@@ -368,14 +401,20 @@ match-architectures = ["x86_64", "aarch64"]
368401 } ;
369402
370403 // rootfs is empty
371- assert_eq ! ( get_kargs( "x86_64" ) . unwrap( ) . len ( ) , 0 ) ;
404+ assert_eq ! ( get_kargs( "x86_64" ) . unwrap( ) . iter ( ) . count ( ) , 0 ) ;
372405
373406 test_rootfs. create_dir_all ( "usr/lib/bootc/kargs.d" ) ?;
374407 write_test_kargs ( & test_rootfs) . unwrap ( ) ;
375408 ostree_commit ( repo, & test_rootfs, "." . into ( ) , "testref" ) ?;
376409
377410 let args = get_kargs ( "x86_64" ) . unwrap ( ) ;
378- similar_asserts:: assert_eq!( args, [ "console=tty0" , "nosmt" , "console=ttyS1" ] ) ;
411+ let mut iter = args. iter ( ) ;
412+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
413+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
414+ assert_eq ! (
415+ iter. next( ) ,
416+ Some ( Parameter :: parse( "console=ttyS1" ) . unwrap( ) )
417+ ) ;
379418
380419 Ok ( ( ) )
381420 }
0 commit comments