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 :: new ( ) ;
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,19 @@ 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+ . and_then ( | bootconfig| {
151+ ostree :: BootconfigParser :: get ( & bootconfig , " options" )
152+ . map ( | options| Cmdline :: from ( options . to_string ( ) ) )
153+ } )
154+ . unwrap_or_default ( ) ;
150155
151156 // Get the kargs in kargs.d of the merge
152157 let merge_root = & crate :: utils:: deployment_fd ( ostree, merge_deployment) ?;
@@ -161,50 +166,56 @@ pub(crate) fn get_kargs(
161166 // A special case: if there's no kargs.d directory in the pending (fetched) image,
162167 // then we can just use the combined current kargs + kargs from booted
163168 if !fetched_tree. query_exists ( cancellable) {
164- kargs. extend ( existing_kargs) ;
169+ kargs. extend ( & existing_kargs) ;
165170 return Ok ( kargs) ;
166171 }
167172
168173 // Fetch the kernel arguments from the new root
169174 let remote_kargs = get_kargs_from_ostree ( repo, & fetched_tree, sys_arch) ?;
170175
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 < _ > > ( ) ;
176+ // Calculate the diff between the existing and remote kargs
177+ let added_kargs: Vec < _ > = remote_kargs
178+ . iter ( )
179+ . filter ( |item| !existing_kargs. iter ( ) . any ( |existing| * item == existing) )
180+ . collect ( ) ;
181+ let removed_kargs: Vec < _ > = existing_kargs
182+ . iter ( )
183+ . filter ( |item| !remote_kargs. iter ( ) . any ( |remote| * item == remote) )
184+ . collect ( ) ;
182185
183186 tracing:: debug!(
184187 "kargs: added={:?} removed={:?}" ,
185188 & added_kargs,
186189 removed_kargs
187190 ) ;
188191
189- // apply the diff to the system kargs
190- kargs. retain ( |x| !removed_kargs. contains ( x) ) ;
191- kargs. append ( & mut added_kargs) ;
192+ // Apply the diff to the system kargs
193+ for arg in & removed_kargs {
194+ kargs. remove_exact ( arg) ;
195+ }
196+ for arg in & added_kargs {
197+ kargs. add ( arg) ;
198+ }
192199
193200 Ok ( kargs)
194201}
195202
196203/// This parses a bootc kargs.d toml file, returning the resulting
197204/// vector of kernel arguments. Architecture matching is performed using
198205/// `sys_arch`.
199- fn parse_kargs_toml ( contents : & str , sys_arch : & str ) -> Result < Vec < String > > {
206+ fn parse_kargs_toml ( contents : & str , sys_arch : & str ) -> Result < Option < Cmdline < ' static > > > {
200207 let de: Config = toml:: from_str ( contents) ?;
201208 // if arch specified, apply kargs only if the arch matches
202209 // if arch not specified, apply kargs unconditionally
203210 let matched = de
204211 . match_architectures
205212 . map ( |arches| arches. iter ( ) . any ( |s| s == sys_arch) )
206213 . unwrap_or ( true ) ;
207- let r = if matched { de. kargs } else { Vec :: new ( ) } ;
214+ let r = if matched {
215+ Some ( Cmdline :: from ( de. kargs . join ( " " ) ) )
216+ } else {
217+ None
218+ } ;
208219 Ok ( r)
209220}
210221
@@ -216,17 +227,24 @@ mod tests {
216227
217228 use super :: * ;
218229
230+ use bootc_kernel_cmdline:: utf8:: Parameter ;
231+
219232 #[ test]
220233 /// Verify that kargs are only applied to supported architectures
221234 fn test_arch ( ) {
222235 // no arch specified, kargs ensure that kargs are applied unconditionally
223236 let sys_arch = "x86_64" ;
224237 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" ] ) ;
238+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
239+ let mut iter = parsed_kargs. iter ( ) ;
240+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
241+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
242+
227243 let sys_arch = "aarch64" ;
228- let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
229- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
244+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
245+ let mut iter = parsed_kargs. iter ( ) ;
246+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
247+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
230248
231249 // one arch matches and one doesn't, ensure that kargs are only applied for the matching arch
232250 let sys_arch = "aarch64" ;
@@ -235,25 +253,32 @@ match-architectures = ["x86_64"]
235253"##
236254 . to_string ( ) ;
237255 let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
238- assert_eq ! ( parsed_kargs, [ ] as [ String ; 0 ] ) ;
256+ assert ! ( parsed_kargs. is_none ( ) ) ;
239257 let file_content = r##"kargs = ["console=tty0", "nosmt"]
240258match-architectures = ["aarch64"]
241259"##
242260 . to_string ( ) ;
243- let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
244- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
261+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
262+ let mut iter = parsed_kargs. iter ( ) ;
263+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
264+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
245265
246266 // multiple arch specified, ensure that kargs are applied to both archs
247267 let sys_arch = "x86_64" ;
248268 let file_content = r##"kargs = ["console=tty0", "nosmt"]
249269match-architectures = ["x86_64", "aarch64"]
250270"##
251271 . to_string ( ) ;
252- let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
253- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
272+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
273+ let mut iter = parsed_kargs. iter ( ) ;
274+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
275+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
276+
254277 let sys_arch = "aarch64" ;
255- let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
256- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
278+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) . unwrap ( ) ;
279+ let mut iter = parsed_kargs. iter ( ) ;
280+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
281+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
257282 }
258283
259284 #[ test]
@@ -285,18 +310,24 @@ match-architectures = ["x86_64", "aarch64"]
285310 let td = cap_std_ext:: cap_tempfile:: TempDir :: new ( cap_std:: ambient_authority ( ) ) ?;
286311
287312 // No directory
288- assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . len ( ) , 0 ) ;
313+ assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . iter ( ) . count ( ) , 0 ) ;
289314 // Empty directory
290315 td. create_dir_all ( "usr/lib/bootc/kargs.d" ) ?;
291- assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . len ( ) , 0 ) ;
316+ assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . iter ( ) . count ( ) , 0 ) ;
292317 // Non-toml file
293318 td. write ( "usr/lib/bootc/kargs.d/somegarbage" , "garbage" ) ?;
294- assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . len ( ) , 0 ) ;
319+ assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . iter ( ) . count ( ) , 0 ) ;
295320
296321 write_test_kargs ( & td) ?;
297322
298323 let args = get_kargs_in_root ( & td, "x86_64" ) . unwrap ( ) ;
299- similar_asserts:: assert_eq!( args, [ "console=tty0" , "nosmt" , "console=ttyS1" ] ) ;
324+ let mut iter = args. iter ( ) ;
325+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
326+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
327+ assert_eq ! (
328+ iter. next( ) ,
329+ Some ( Parameter :: parse( "console=ttyS1" ) . unwrap( ) )
330+ ) ;
300331
301332 Ok ( ( ) )
302333 }
@@ -354,7 +385,7 @@ match-architectures = ["x86_64", "aarch64"]
354385
355386 ostree_commit ( repo, & test_rootfs, "." . into ( ) , "testref" ) ?;
356387 // Helper closure to read the kargs
357- let get_kargs = |sys_arch : & str | -> Result < Vec < String > > {
388+ let get_kargs = |sys_arch : & str | -> Result < Cmdline < ' static > > {
358389 let rootfs = repo. read_commit ( "testref" , cancellable) ?. 0 ;
359390 let rootfs = rootfs. downcast_ref :: < ostree:: RepoFile > ( ) . unwrap ( ) ;
360391 let fetched_tree = rootfs. resolve_relative_path ( "/usr/lib/bootc/kargs.d" ) ;
@@ -368,14 +399,20 @@ match-architectures = ["x86_64", "aarch64"]
368399 } ;
369400
370401 // rootfs is empty
371- assert_eq ! ( get_kargs( "x86_64" ) . unwrap( ) . len ( ) , 0 ) ;
402+ assert_eq ! ( get_kargs( "x86_64" ) . unwrap( ) . iter ( ) . count ( ) , 0 ) ;
372403
373404 test_rootfs. create_dir_all ( "usr/lib/bootc/kargs.d" ) ?;
374405 write_test_kargs ( & test_rootfs) . unwrap ( ) ;
375406 ostree_commit ( repo, & test_rootfs, "." . into ( ) , "testref" ) ?;
376407
377408 let args = get_kargs ( "x86_64" ) . unwrap ( ) ;
378- similar_asserts:: assert_eq!( args, [ "console=tty0" , "nosmt" , "console=ttyS1" ] ) ;
409+ let mut iter = args. iter ( ) ;
410+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "console=tty0" ) . unwrap( ) ) ) ;
411+ assert_eq ! ( iter. next( ) , Some ( Parameter :: parse( "nosmt" ) . unwrap( ) ) ) ;
412+ assert_eq ! (
413+ iter. next( ) ,
414+ Some ( Parameter :: parse( "console=ttyS1" ) . unwrap( ) )
415+ ) ;
379416
380417 Ok ( ( ) )
381418 }
0 commit comments