@@ -13,13 +13,24 @@ use serde::Deserialize;
13
13
14
14
use crate :: deploy:: ImageState ;
15
15
16
+ /// The kargs.d configuration file.
16
17
#[ derive( Deserialize ) ]
17
18
#[ serde( rename_all = "kebab-case" , deny_unknown_fields) ]
18
19
struct Config {
20
+ /// Ordered list of kernel arguments.
19
21
kargs : Vec < String > ,
22
+ /// Optional list of architectures (using the Rust naming conventions);
23
+ /// if present and the current architecture doesn't match, the file is skipped.
20
24
match_architectures : Option < Vec < String > > ,
21
25
}
22
26
27
+ impl Config {
28
+ /// Return true if the filename is one we should parse.
29
+ fn filename_matches ( name : & str ) -> bool {
30
+ matches ! ( Utf8Path :: new( name) . extension( ) , Some ( "toml" ) )
31
+ }
32
+ }
33
+
23
34
/// Load and parse all bootc kargs.d files in the specified root, returning
24
35
/// a combined list.
25
36
pub ( crate ) fn get_kargs_in_root ( d : & Dir , sys_arch : & str ) -> Result < Vec < String > > {
@@ -39,7 +50,7 @@ pub(crate) fn get_kargs_in_root(d: &Dir, sys_arch: &str) -> Result<Vec<String>>
39
50
let name = name
40
51
. to_str ( )
41
52
. ok_or_else ( || anyhow:: anyhow!( "Invalid non-UTF8 filename: {name:?}" ) ) ?;
42
- if !matches ! ( Utf8Path :: new ( name) . extension ( ) , Some ( "toml" ) ) {
53
+ if !Config :: filename_matches ( name) {
43
54
continue ;
44
55
}
45
56
let buf = d. read_to_string ( name) ?;
@@ -49,6 +60,47 @@ pub(crate) fn get_kargs_in_root(d: &Dir, sys_arch: &str) -> Result<Vec<String>>
49
60
Ok ( ret)
50
61
}
51
62
63
+ /// Load kargs.d files from the target ostree commit root
64
+ fn get_kargs_from_ostree (
65
+ repo : & ostree:: Repo ,
66
+ fetched_tree : & ostree:: RepoFile ,
67
+ sys_arch : & str ,
68
+ ) -> Result < Vec < String > > {
69
+ let cancellable = gio:: Cancellable :: NONE ;
70
+ let queryattrs = "standard::name,standard::type" ;
71
+ let queryflags = gio:: FileQueryInfoFlags :: NOFOLLOW_SYMLINKS ;
72
+ let fetched_iter = fetched_tree. enumerate_children ( queryattrs, queryflags, cancellable) ?;
73
+ let mut ret = Vec :: new ( ) ;
74
+ while let Some ( fetched_info) = fetched_iter. next_file ( cancellable) ? {
75
+ // only read and parse the file if it is a toml file
76
+ let name = fetched_info. name ( ) ;
77
+ let name = if let Some ( name) = name. to_str ( ) {
78
+ name
79
+ } else {
80
+ continue ;
81
+ } ;
82
+ if !Config :: filename_matches ( name) {
83
+ continue ;
84
+ }
85
+
86
+ let fetched_child = fetched_iter. child ( & fetched_info) ;
87
+ let fetched_child = fetched_child
88
+ . downcast :: < ostree:: RepoFile > ( )
89
+ . expect ( "downcast" ) ;
90
+ fetched_child. ensure_resolved ( ) ?;
91
+ let fetched_contents_checksum = fetched_child. checksum ( ) ;
92
+ let f = ostree:: Repo :: load_file ( repo, fetched_contents_checksum. as_str ( ) , cancellable) ?;
93
+ let file_content = f. 0 ;
94
+ let mut reader =
95
+ ostree_ext:: prelude:: InputStreamExtManual :: into_read ( file_content. unwrap ( ) ) ;
96
+ let s = std:: io:: read_to_string ( & mut reader) ?;
97
+ let parsed_kargs =
98
+ parse_kargs_toml ( & s, sys_arch) . with_context ( || format ! ( "Parsing {name}" ) ) ?;
99
+ ret. extend ( parsed_kargs) ;
100
+ }
101
+ Ok ( ret)
102
+ }
103
+
52
104
/// Compute the kernel arguments for the new deployment. This starts from the booted
53
105
/// karg, but applies the diff between the bootc karg files in /usr/lib/bootc/kargs.d
54
106
/// between the booted deployment and the new one.
@@ -86,33 +138,8 @@ pub(crate) fn get_kargs(
86
138
return Ok ( kargs) ;
87
139
}
88
140
89
- let mut remote_kargs: Vec < String > = vec ! [ ] ;
90
- let queryattrs = "standard::name,standard::type" ;
91
- let queryflags = gio:: FileQueryInfoFlags :: NOFOLLOW_SYMLINKS ;
92
- let fetched_iter = fetched_tree. enumerate_children ( queryattrs, queryflags, cancellable) ?;
93
- while let Some ( fetched_info) = fetched_iter. next_file ( cancellable) ? {
94
- // only read and parse the file if it is a toml file
95
- let name = fetched_info. name ( ) ;
96
- if let Some ( name) = name. to_str ( ) {
97
- if name. ends_with ( ".toml" ) {
98
- let fetched_child = fetched_iter. child ( & fetched_info) ;
99
- let fetched_child = fetched_child
100
- . downcast :: < ostree:: RepoFile > ( )
101
- . expect ( "downcast" ) ;
102
- fetched_child. ensure_resolved ( ) ?;
103
- let fetched_contents_checksum = fetched_child. checksum ( ) ;
104
- let f =
105
- ostree:: Repo :: load_file ( repo, fetched_contents_checksum. as_str ( ) , cancellable) ?;
106
- let file_content = f. 0 ;
107
- let mut reader =
108
- ostree_ext:: prelude:: InputStreamExtManual :: into_read ( file_content. unwrap ( ) ) ;
109
- let s = std:: io:: read_to_string ( & mut reader) ?;
110
- let mut parsed_kargs =
111
- parse_kargs_toml ( & s, sys_arch) . with_context ( || format ! ( "Parsing {name}" ) ) ?;
112
- remote_kargs. append ( & mut parsed_kargs) ;
113
- }
114
- }
115
- }
141
+ // Fetch the kernel arguments from the new root
142
+ let remote_kargs = get_kargs_from_ostree ( repo, & fetched_tree, sys_arch) ?;
116
143
117
144
// get the diff between the existing and remote kargs
118
145
let mut added_kargs: Vec < String > = remote_kargs
@@ -156,6 +183,9 @@ fn parse_kargs_toml(contents: &str, sys_arch: &str) -> Result<Vec<String>> {
156
183
157
184
#[ cfg( test) ]
158
185
mod tests {
186
+ use fn_error_context:: context;
187
+ use rustix:: fd:: { AsFd , AsRawFd } ;
188
+
159
189
use super :: * ;
160
190
161
191
#[ test]
@@ -208,6 +238,20 @@ match-architectures = ["x86_64", "aarch64"]
208
238
assert ! ( parse_kargs_toml( test_missing, "x86_64" ) . is_err( ) ) ;
209
239
}
210
240
241
+ #[ context( "writing test kargs" ) ]
242
+ fn write_test_kargs ( td : & Dir ) -> Result < ( ) > {
243
+ td. write (
244
+ "usr/lib/bootc/kargs.d/01-foo.toml" ,
245
+ r##"kargs = ["console=tty0", "nosmt"]"## ,
246
+ ) ?;
247
+ td. write (
248
+ "usr/lib/bootc/kargs.d/02-bar.toml" ,
249
+ r##"kargs = ["console=ttyS1"]"## ,
250
+ ) ?;
251
+
252
+ Ok ( ( ) )
253
+ }
254
+
211
255
#[ test]
212
256
fn test_get_kargs_in_root ( ) -> Result < ( ) > {
213
257
let td = cap_std_ext:: cap_tempfile:: TempDir :: new ( cap_std:: ambient_authority ( ) ) ?;
@@ -220,18 +264,83 @@ match-architectures = ["x86_64", "aarch64"]
220
264
// Non-toml file
221
265
td. write ( "usr/lib/bootc/kargs.d/somegarbage" , "garbage" ) ?;
222
266
assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . len( ) , 0 ) ;
223
- td. write (
224
- "usr/lib/bootc/kargs.d/01-foo.toml" ,
225
- r##"kargs = ["console=tty0", "nosmt"]"## ,
226
- ) ?;
227
- td. write (
228
- "usr/lib/bootc/kargs.d/02-bar.toml" ,
229
- r##"kargs = ["console=ttyS1"]"## ,
230
- ) ?;
267
+
268
+ write_test_kargs ( & td) ?;
231
269
232
270
let args = get_kargs_in_root ( & td, "x86_64" ) . unwrap ( ) ;
233
271
similar_asserts:: assert_eq!( args, [ "console=tty0" , "nosmt" , "console=ttyS1" ] ) ;
234
272
235
273
Ok ( ( ) )
236
274
}
275
+
276
+ #[ context( "ostree commit" ) ]
277
+ fn ostree_commit (
278
+ repo : & ostree:: Repo ,
279
+ d : & Dir ,
280
+ path : & Utf8Path ,
281
+ ostree_ref : & str ,
282
+ ) -> Result < ( ) > {
283
+ let cancellable = gio:: Cancellable :: NONE ;
284
+ let txn = repo. auto_transaction ( cancellable) ?;
285
+
286
+ let mt = ostree:: MutableTree :: new ( ) ;
287
+ repo. write_dfd_to_mtree ( d. as_fd ( ) . as_raw_fd ( ) , path. as_str ( ) , & mt, None , cancellable)
288
+ . context ( "Writing merged filesystem to mtree" ) ?;
289
+
290
+ let merged_root = repo
291
+ . write_mtree ( & mt, cancellable)
292
+ . context ( "Writing mtree" ) ?;
293
+ let merged_root = merged_root. downcast :: < ostree:: RepoFile > ( ) . unwrap ( ) ;
294
+ let merged_commit = repo
295
+ . write_commit ( None , None , None , None , & merged_root, cancellable)
296
+ . context ( "Writing commit" ) ?;
297
+ repo. transaction_set_ref ( None , & ostree_ref, Some ( merged_commit. as_str ( ) ) ) ;
298
+ txn. commit ( cancellable) ?;
299
+ Ok ( ( ) )
300
+ }
301
+
302
+ #[ test]
303
+ fn test_get_kargs_in_ostree ( ) -> Result < ( ) > {
304
+ let cancellable = gio:: Cancellable :: NONE ;
305
+ let td = cap_std_ext:: cap_tempfile:: TempDir :: new ( cap_std:: ambient_authority ( ) ) ?;
306
+
307
+ td. create_dir ( "repo" ) ?;
308
+ let repo = & ostree:: Repo :: create_at (
309
+ td. as_fd ( ) . as_raw_fd ( ) ,
310
+ "repo" ,
311
+ ostree:: RepoMode :: Bare ,
312
+ None ,
313
+ gio:: Cancellable :: NONE ,
314
+ ) ?;
315
+
316
+ td. create_dir ( "rootfs" ) ?;
317
+ let test_rootfs = & td. open_dir ( "rootfs" ) ?;
318
+
319
+ ostree_commit ( repo, & test_rootfs, "." . into ( ) , "testref" ) ?;
320
+ // Helper closure to read the kargs
321
+ let get_kargs = |sys_arch : & str | -> Result < Vec < String > > {
322
+ let rootfs = repo. read_commit ( "testref" , cancellable) ?. 0 ;
323
+ let rootfs = rootfs. downcast_ref :: < ostree:: RepoFile > ( ) . unwrap ( ) ;
324
+ let fetched_tree = rootfs. resolve_relative_path ( "/usr/lib/bootc/kargs.d" ) ;
325
+ let fetched_tree = fetched_tree
326
+ . downcast :: < ostree:: RepoFile > ( )
327
+ . expect ( "downcast" ) ;
328
+ if !fetched_tree. query_exists ( cancellable) {
329
+ return Ok ( Default :: default ( ) ) ;
330
+ }
331
+ get_kargs_from_ostree ( repo, & fetched_tree, sys_arch)
332
+ } ;
333
+
334
+ // rootfs is empty
335
+ assert_eq ! ( get_kargs( "x86_64" ) . unwrap( ) . len( ) , 0 ) ;
336
+
337
+ test_rootfs. create_dir_all ( "usr/lib/bootc/kargs.d" ) ?;
338
+ write_test_kargs ( & test_rootfs) . unwrap ( ) ;
339
+ ostree_commit ( repo, & test_rootfs, "." . into ( ) , "testref" ) ?;
340
+
341
+ let args = get_kargs ( "x86_64" ) . unwrap ( ) ;
342
+ similar_asserts:: assert_eq!( args, [ "console=tty0" , "nosmt" , "console=ttyS1" ] ) ;
343
+
344
+ Ok ( ( ) )
345
+ }
237
346
}
0 commit comments