1
- use anyhow:: Ok ;
2
- use anyhow:: Result ;
1
+ use anyhow:: { Context , Result } ;
2
+ use camino:: Utf8Path ;
3
+ use cap_std_ext:: cap_std;
4
+ use cap_std_ext:: cap_std:: fs:: Dir ;
5
+ use cap_std_ext:: dirext:: CapStdExtDirExt ;
3
6
use ostree:: gio;
4
7
use ostree_ext:: ostree;
5
8
use ostree_ext:: ostree:: Deployment ;
@@ -17,6 +20,35 @@ struct Config {
17
20
match_architectures : Option < Vec < String > > ,
18
21
}
19
22
23
+ /// Load and parse all bootc kargs.d files in the specified root, returning
24
+ /// a combined list.
25
+ fn get_kargs_in_root ( d : & Dir , sys_arch : & str ) -> Result < Vec < String > > {
26
+ // If the directory doesn't exist, that's OK.
27
+ let d = if let Some ( d) = d. open_dir_optional ( "usr/lib/bootc/kargs.d" ) ? {
28
+ d
29
+ } else {
30
+ return Ok ( Default :: default ( ) ) ;
31
+ } ;
32
+ let mut ret = Vec :: new ( ) ;
33
+ // Read all the entries
34
+ let mut entries = d. entries ( ) ?. collect :: < std:: io:: Result < Vec < _ > > > ( ) ?;
35
+ // cc https://github.com/rust-lang/rust/issues/85573 re the allocation-per-comparison here
36
+ entries. sort_by ( |a, b| a. file_name ( ) . cmp ( & b. file_name ( ) ) ) ;
37
+ for ent in entries {
38
+ let name = ent. file_name ( ) ;
39
+ let name = name
40
+ . to_str ( )
41
+ . ok_or_else ( || anyhow:: anyhow!( "Invalid non-UTF8 filename: {name:?}" ) ) ?;
42
+ if !matches ! ( Utf8Path :: new( name) . extension( ) , Some ( "toml" ) ) {
43
+ continue ;
44
+ }
45
+ let buf = d. read_to_string ( name) ?;
46
+ let kargs = parse_kargs_toml ( & buf, sys_arch) . with_context ( || format ! ( "Parsing {name}" ) ) ?;
47
+ ret. extend ( kargs)
48
+ }
49
+ Ok ( ret)
50
+ }
51
+
20
52
/// Compute the kernel arguments for the new deployment. This starts from the booted
21
53
/// karg, but applies the diff between the bootc karg files in /usr/lib/bootc/kargs.d
22
54
/// between the booted deployment and the new one.
@@ -27,38 +59,34 @@ pub(crate) fn get_kargs(
27
59
) -> Result < Vec < String > > {
28
60
let cancellable = gio:: Cancellable :: NONE ;
29
61
let mut kargs: Vec < String > = vec ! [ ] ;
30
- let sys_arch = std:: env:: consts:: ARCH . to_string ( ) ;
62
+ let sys_arch = std:: env:: consts:: ARCH ;
31
63
32
64
// Get the running kargs of the booted system
33
65
if let Some ( bootconfig) = ostree:: Deployment :: bootconfig ( booted_deployment) {
34
66
if let Some ( options) = ostree:: BootconfigParser :: get ( & bootconfig, "options" ) {
35
- let options: Vec < & str > = options. split_whitespace ( ) . collect ( ) ;
36
- let mut options: Vec < String > = options. into_iter ( ) . map ( |s| s. to_string ( ) ) . collect ( ) ;
37
- kargs. append ( & mut options) ;
67
+ let options = options. split_whitespace ( ) . map ( |s| s. to_owned ( ) ) ;
68
+ kargs. extend ( options) ;
38
69
}
39
70
} ;
40
71
41
72
// Get the kargs in kargs.d of the booted system
42
- let mut existing_kargs: Vec < String > = vec ! [ ] ;
43
- let fragments = liboverdrop:: scan ( & [ "/usr/lib" ] , "bootc/kargs.d" , & [ "toml" ] , true ) ;
44
- for ( _name, path) in fragments {
45
- let s = std:: fs:: read_to_string ( & path) ?;
46
- let mut parsed_kargs = parse_file ( s. clone ( ) , sys_arch. clone ( ) ) ?;
47
- existing_kargs. append ( & mut parsed_kargs) ;
48
- }
73
+ let root = & cap_std:: fs:: Dir :: open_ambient_dir ( "/" , cap_std:: ambient_authority ( ) ) ?;
74
+ let existing_kargs: Vec < String > = get_kargs_in_root ( root, sys_arch) ?;
49
75
50
76
// Get the kargs in kargs.d of the pending image
51
- let mut remote_kargs: Vec < String > = vec ! [ ] ;
52
77
let ( fetched_tree, _) = repo. read_commit ( fetched. ostree_commit . as_str ( ) , cancellable) ?;
53
78
let fetched_tree = fetched_tree. resolve_relative_path ( "/usr/lib/bootc/kargs.d" ) ;
54
79
let fetched_tree = fetched_tree
55
80
. downcast :: < ostree:: RepoFile > ( )
56
81
. expect ( "downcast" ) ;
82
+ // A special case: if there's no kargs.d directory in the pending (fetched) image,
83
+ // then we can just use the combined current kargs + kargs from booted
57
84
if !fetched_tree. query_exists ( cancellable) {
58
- // if the kargs.d directory does not exist in the fetched image, return the existing kargs
59
- kargs. append ( & mut existing_kargs) ;
85
+ kargs. extend ( existing_kargs) ;
60
86
return Ok ( kargs) ;
61
87
}
88
+
89
+ let mut remote_kargs: Vec < String > = vec ! [ ] ;
62
90
let queryattrs = "standard::name,standard::type" ;
63
91
let queryflags = gio:: FileQueryInfoFlags :: NOFOLLOW_SYMLINKS ;
64
92
let fetched_iter = fetched_tree. enumerate_children ( queryattrs, queryflags, cancellable) ?;
@@ -79,7 +107,8 @@ pub(crate) fn get_kargs(
79
107
let mut reader =
80
108
ostree_ext:: prelude:: InputStreamExtManual :: into_read ( file_content. unwrap ( ) ) ;
81
109
let s = std:: io:: read_to_string ( & mut reader) ?;
82
- let mut parsed_kargs = parse_file ( s. clone ( ) , sys_arch. clone ( ) ) ?;
110
+ let mut parsed_kargs =
111
+ parse_kargs_toml ( & s, sys_arch) . with_context ( || format ! ( "Parsing {name}" ) ) ?;
83
112
remote_kargs. append ( & mut parsed_kargs) ;
84
113
}
85
114
}
@@ -110,58 +139,99 @@ pub(crate) fn get_kargs(
110
139
Ok ( kargs)
111
140
}
112
141
113
- pub fn parse_file ( file_content : String , sys_arch : String ) -> Result < Vec < String > > {
114
- let mut de: Config = toml:: from_str ( & file_content) ?;
115
- let mut parsed_kargs: Vec < String > = vec ! [ ] ;
142
+ /// This parses a bootc kargs.d toml file, returning the resulting
143
+ /// vector of kernel arguments. Architecture matching is performed using
144
+ /// `sys_arch`.
145
+ fn parse_kargs_toml ( contents : & str , sys_arch : & str ) -> Result < Vec < String > > {
146
+ let de: Config = toml:: from_str ( contents) ?;
116
147
// if arch specified, apply kargs only if the arch matches
117
148
// if arch not specified, apply kargs unconditionally
118
- match de. match_architectures {
119
- None => parsed_kargs = de. kargs ,
120
- Some ( match_architectures) => {
121
- if match_architectures. contains ( & sys_arch) {
122
- parsed_kargs. append ( & mut de. kargs ) ;
123
- }
124
- }
125
- }
126
- Ok ( parsed_kargs)
149
+ let matched = de
150
+ . match_architectures
151
+ . map ( |arches| arches. iter ( ) . any ( |s| s == sys_arch) )
152
+ . unwrap_or ( true ) ;
153
+ let r = if matched { de. kargs } else { Vec :: new ( ) } ;
154
+ Ok ( r)
127
155
}
128
156
129
- #[ test]
130
- /// Verify that kargs are only applied to supported architectures
131
- fn test_arch ( ) {
132
- // no arch specified, kargs ensure that kargs are applied unconditionally
133
- let sys_arch = "x86_64" . to_string ( ) ;
134
- let file_content = r##"kargs = ["console=tty0", "nosmt"]"## . to_string ( ) ;
135
- let parsed_kargs = parse_file ( file_content. clone ( ) , sys_arch. clone ( ) ) . unwrap ( ) ;
136
- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
137
- let sys_arch = "aarch64" . to_string ( ) ;
138
- let parsed_kargs = parse_file ( file_content. clone ( ) , sys_arch. clone ( ) ) . unwrap ( ) ;
139
- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
140
-
141
- // one arch matches and one doesn't, ensure that kargs are only applied for the matching arch
142
- let sys_arch = "aarch64" . to_string ( ) ;
143
- let file_content = r##"kargs = ["console=tty0", "nosmt"]
157
+ #[ cfg( test) ]
158
+ mod tests {
159
+ use super :: * ;
160
+
161
+ #[ test]
162
+ /// Verify that kargs are only applied to supported architectures
163
+ fn test_arch ( ) {
164
+ // no arch specified, kargs ensure that kargs are applied unconditionally
165
+ let sys_arch = "x86_64" ;
166
+ let file_content = r##"kargs = ["console=tty0", "nosmt"]"## . to_string ( ) ;
167
+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
168
+ assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
169
+ let sys_arch = "aarch64" ;
170
+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
171
+ assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
172
+
173
+ // one arch matches and one doesn't, ensure that kargs are only applied for the matching arch
174
+ let sys_arch = "aarch64" ;
175
+ let file_content = r##"kargs = ["console=tty0", "nosmt"]
144
176
match-architectures = ["x86_64"]
145
177
"##
146
- . to_string ( ) ;
147
- let parsed_kargs = parse_file ( file_content. clone ( ) , sys_arch. clone ( ) ) . unwrap ( ) ;
148
- assert_eq ! ( parsed_kargs, [ ] as [ String ; 0 ] ) ;
149
- let file_content = r##"kargs = ["console=tty0", "nosmt"]
178
+ . to_string ( ) ;
179
+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
180
+ assert_eq ! ( parsed_kargs, [ ] as [ String ; 0 ] ) ;
181
+ let file_content = r##"kargs = ["console=tty0", "nosmt"]
150
182
match-architectures = ["aarch64"]
151
183
"##
152
- . to_string ( ) ;
153
- let parsed_kargs = parse_file ( file_content. clone ( ) , sys_arch. clone ( ) ) . unwrap ( ) ;
154
- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
184
+ . to_string ( ) ;
185
+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
186
+ assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
155
187
156
- // multiple arch specified, ensure that kargs are applied to both archs
157
- let sys_arch = "x86_64" . to_string ( ) ;
158
- let file_content = r##"kargs = ["console=tty0", "nosmt"]
188
+ // multiple arch specified, ensure that kargs are applied to both archs
189
+ let sys_arch = "x86_64" ;
190
+ let file_content = r##"kargs = ["console=tty0", "nosmt"]
159
191
match-architectures = ["x86_64", "aarch64"]
160
192
"##
161
- . to_string ( ) ;
162
- let parsed_kargs = parse_file ( file_content. clone ( ) , sys_arch. clone ( ) ) . unwrap ( ) ;
163
- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
164
- std:: env:: set_var ( "ARCH" , "aarch64" ) ;
165
- let parsed_kargs = parse_file ( file_content. clone ( ) , sys_arch. clone ( ) ) . unwrap ( ) ;
166
- assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
193
+ . to_string ( ) ;
194
+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
195
+ assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
196
+ std:: env:: set_var ( "ARCH" , "aarch64" ) ;
197
+ let parsed_kargs = parse_kargs_toml ( & file_content, sys_arch) . unwrap ( ) ;
198
+ assert_eq ! ( parsed_kargs, [ "console=tty0" , "nosmt" ] ) ;
199
+ }
200
+
201
+ #[ test]
202
+ /// Verify some error cases
203
+ fn test_invalid ( ) {
204
+ let test_invalid_extra = r#"kargs = ["console=tty0", "nosmt"]\nfoo=bar"# ;
205
+ assert ! ( parse_kargs_toml( test_invalid_extra, "x86_64" ) . is_err( ) ) ;
206
+
207
+ let test_missing = r#"foo=bar"# ;
208
+ assert ! ( parse_kargs_toml( test_missing, "x86_64" ) . is_err( ) ) ;
209
+ }
210
+
211
+ #[ test]
212
+ fn test_get_kargs_in_root ( ) -> Result < ( ) > {
213
+ let td = cap_std_ext:: cap_tempfile:: TempDir :: new ( cap_std:: ambient_authority ( ) ) ?;
214
+
215
+ // No directory
216
+ assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . len( ) , 0 ) ;
217
+ // Empty directory
218
+ td. create_dir_all ( "usr/lib/bootc/kargs.d" ) ?;
219
+ assert_eq ! ( get_kargs_in_root( & td, "x86_64" ) . unwrap( ) . len( ) , 0 ) ;
220
+ // Non-toml file
221
+ td. write ( "usr/lib/bootc/kargs.d/somegarbage" , "garbage" ) ?;
222
+ 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
+ ) ?;
231
+
232
+ let args = get_kargs_in_root ( & td, "x86_64" ) . unwrap ( ) ;
233
+ similar_asserts:: assert_eq!( args, [ "console=tty0" , "nosmt" , "console=ttyS1" ] ) ;
234
+
235
+ Ok ( ( ) )
236
+ }
167
237
}
0 commit comments