@@ -10,7 +10,7 @@ use std::env::consts::ARCH;
10
10
use std:: fmt:: Write as WriteFmt ;
11
11
use std:: os:: unix:: ffi:: OsStrExt ;
12
12
13
- use anyhow:: Result ;
13
+ use anyhow:: { Context , Result } ;
14
14
use bootc_utils:: PathQuotedDisplay ;
15
15
use camino:: { Utf8Path , Utf8PathBuf } ;
16
16
use cap_std:: fs:: Dir ;
@@ -21,7 +21,7 @@ use fn_error_context::context;
21
21
use indoc:: indoc;
22
22
use linkme:: distributed_slice;
23
23
use ostree_ext:: ostree_prepareroot;
24
- use serde:: Serialize ;
24
+ use serde:: { Deserialize , Serialize } ;
25
25
26
26
/// Reference to embedded default baseimage content that should exist.
27
27
const BASEIMAGE_REF : & str = "usr/share/doc/bootc/baseimage/base" ;
@@ -57,12 +57,27 @@ impl LintError {
57
57
}
58
58
}
59
59
60
+ #[ derive( Debug , Clone ) ]
61
+ pub ( crate ) enum Applicability {
62
+ Scan ,
63
+ Fix ,
64
+ }
65
+
60
66
type LintFn = fn ( & Dir ) -> LintResult ;
67
+ type LintFnExt = fn ( & Dir , apply : Applicability ) -> LintResult ;
68
+
69
+ #[ derive( Debug , Serialize ) ]
70
+ #[ serde( rename_all = "kebab-case" ) ]
71
+ enum LintFnTy {
72
+ Scan ( #[ serde( skip) ] LintFn ) ,
73
+ ScanOrFix ( #[ serde( skip) ] LintFnExt ) ,
74
+ }
75
+
61
76
#[ distributed_slice]
62
77
pub ( crate ) static LINTS : [ Lint ] ;
63
78
64
79
/// The classification of a lint type.
65
- #[ derive( Debug , Serialize ) ]
80
+ #[ derive( Debug , Deserialize , Serialize ) ]
66
81
#[ serde( rename_all = "kebab-case" ) ]
67
82
enum LintType {
68
83
/// If this fails, it is known to be fatal - the system will not install or
@@ -78,7 +93,8 @@ pub(crate) enum WarningDisposition {
78
93
FatalWarnings ,
79
94
}
80
95
81
- #[ derive( Debug , Copy , Clone , Serialize , PartialEq , Eq ) ]
96
+ #[ derive( Debug , Copy , Clone , Deserialize , Serialize , PartialEq , Eq ) ]
97
+ #[ serde( rename_all = "kebab-case" ) ]
82
98
pub ( crate ) enum RootType {
83
99
Running ,
84
100
Alternative ,
@@ -90,8 +106,7 @@ struct Lint {
90
106
name : & ' static str ,
91
107
#[ serde( rename = "type" ) ]
92
108
ty : LintType ,
93
- #[ serde( skip) ]
94
- f : LintFn ,
109
+ apply : LintFnTy ,
95
110
description : & ' static str ,
96
111
// Set if this only applies to a specific root type.
97
112
#[ serde( skip_serializing_if = "Option::is_none" ) ]
@@ -107,7 +122,7 @@ impl Lint {
107
122
Lint {
108
123
name : name,
109
124
ty : LintType :: Fatal ,
110
- f : f ,
125
+ apply : LintFnTy :: Scan ( f ) ,
111
126
description : description,
112
127
root_type : None ,
113
128
}
@@ -121,12 +136,28 @@ impl Lint {
121
136
Lint {
122
137
name : name,
123
138
ty : LintType :: Warning ,
124
- f : f,
139
+ apply : LintFnTy :: Scan ( f) ,
140
+ description : description,
141
+ root_type : None ,
142
+ }
143
+ }
144
+
145
+ pub ( crate ) const fn new (
146
+ name : & ' static str ,
147
+ description : & ' static str ,
148
+ f : LintFnTy ,
149
+ ty : LintType ,
150
+ ) -> Self {
151
+ Lint {
152
+ name,
125
153
description : description,
154
+ ty,
155
+ apply : f,
126
156
root_type : None ,
127
157
}
128
158
}
129
159
160
+ /// Set the root filesystem type
130
161
const fn set_root_type ( mut self , v : RootType ) -> Self {
131
162
self . root_type = Some ( v) ;
132
163
self
@@ -150,6 +181,7 @@ struct LintExecutionResult {
150
181
fn lint_inner < ' skip > (
151
182
root : & Dir ,
152
183
root_type : RootType ,
184
+ fix : Applicability ,
153
185
skip : impl IntoIterator < Item = & ' skip str > ,
154
186
mut output : impl std:: io:: Write ,
155
187
) -> Result < LintExecutionResult > {
@@ -173,11 +205,15 @@ fn lint_inner<'skip>(
173
205
}
174
206
}
175
207
176
- let r = match ( lint. f ) ( & root) {
177
- Ok ( r) => r,
178
- Err ( e) => anyhow:: bail!( "Unexpected runtime error running lint {name}: {e}" ) ,
179
- } ;
208
+ // Call the lint function, and propagate the first level of error (runtime errors)
209
+ // immediately; we don't continue on and execute any other lints.
210
+ let r = match lint. apply {
211
+ LintFnTy :: ScanOrFix ( f) => f ( & root, fix. clone ( ) ) ,
212
+ LintFnTy :: Scan ( f) => f ( & root) ,
213
+ }
214
+ . with_context ( || format ! ( "Runtime error executing lint {name}" ) ) ?;
180
215
216
+ // The lint executed OK, but may have successfully detected problems.
181
217
if let Err ( e) = r {
182
218
match lint. ty {
183
219
LintType :: Fatal => {
@@ -212,10 +248,11 @@ pub(crate) fn lint<'skip>(
212
248
root : & Dir ,
213
249
warning_disposition : WarningDisposition ,
214
250
root_type : RootType ,
251
+ fix : Applicability ,
215
252
skip : impl IntoIterator < Item = & ' skip str > ,
216
253
mut output : impl std:: io:: Write ,
217
254
) -> Result < ( ) > {
218
- let r = lint_inner ( root, root_type, skip, & mut output) ?;
255
+ let r = lint_inner ( root, root_type, fix , skip, & mut output) ?;
219
256
writeln ! ( output, "Checks passed: {}" , r. passed) ?;
220
257
if r. skipped > 0 {
221
258
writeln ! ( output, "Checks skipped: {}" , r. skipped) ?;
@@ -503,7 +540,7 @@ fn check_varlog(root: &Dir) -> LintResult {
503
540
}
504
541
505
542
#[ distributed_slice( LINTS ) ]
506
- static LINT_VAR_TMPFILES : Lint = Lint :: new_warning (
543
+ static LINT_VAR_TMPFILES : Lint = Lint :: new (
507
544
"var-tmpfiles" ,
508
545
indoc ! { r#"
509
546
Check for content in /var that does not have corresponding systemd tmpfiles.d entries.
@@ -514,10 +551,27 @@ Instead, it's recommended to have /var effectively empty in the container image,
514
551
and use systemd tmpfiles.d to generate empty directories and compatibility symbolic links
515
552
as part of each boot.
516
553
"# } ,
517
- check_var_tmpfiles,
554
+ LintFnTy :: ScanOrFix ( check_var_tmpfiles) ,
555
+ LintType :: Warning ,
518
556
)
519
557
. set_root_type ( RootType :: Running ) ;
520
- fn check_var_tmpfiles ( _root : & Dir ) -> LintResult {
558
+ fn check_var_tmpfiles ( _root : & Dir , fix : Applicability ) -> LintResult {
559
+ // Handle the fix case first by doing the conversion where we can
560
+ match fix {
561
+ Applicability :: Fix => {
562
+ let r = bootc_tmpfiles:: convert_var_to_tmpfiles_current_root ( ) ?;
563
+ if let Some ( ( count, path) ) = r. generated {
564
+ println ! (
565
+ "tmpfiles.d: Generated: {} with entries: {}" ,
566
+ PathQuotedDisplay :: new( & path) ,
567
+ count
568
+ ) ;
569
+ } else {
570
+ println ! ( "tmpfiles.d: Nogenerated" ) ;
571
+ }
572
+ }
573
+ Applicability :: Scan => { }
574
+ } ;
521
575
let r = bootc_tmpfiles:: find_missing_tmpfiles_current_root ( ) ?;
522
576
if r. tmpfiles . is_empty ( ) && r. unsupported . is_empty ( ) {
523
577
return lint_ok ( ) ;
@@ -680,10 +734,10 @@ mod tests {
680
734
let mut out = Vec :: new ( ) ;
681
735
let warnings = WarningDisposition :: FatalWarnings ;
682
736
let root_type = RootType :: Alternative ;
683
- lint ( root, warnings, root_type, [ ] , & mut out) . unwrap ( ) ;
737
+ lint ( root, warnings, root_type, Applicability :: Scan , [ ] , & mut out) . unwrap ( ) ;
684
738
root. create_dir_all ( "var/run/foo" ) ?;
685
739
let mut out = Vec :: new ( ) ;
686
- assert ! ( lint( root, warnings, root_type, [ ] , & mut out) . is_err( ) ) ;
740
+ assert ! ( lint( root, warnings, root_type, Applicability :: Scan , [ ] , & mut out) . is_err( ) ) ;
687
741
Ok ( ( ) )
688
742
}
689
743
@@ -694,14 +748,14 @@ mod tests {
694
748
// Verify that all lints run
695
749
let mut out = Vec :: new ( ) ;
696
750
let root_type = RootType :: Alternative ;
697
- let r = lint_inner ( root, root_type, [ ] , & mut out) . unwrap ( ) ;
751
+ let r = lint_inner ( root, root_type, Applicability :: Scan , [ ] , & mut out) . unwrap ( ) ;
698
752
let running_only_lints = LINTS . len ( ) . checked_sub ( * ALTROOT_LINTS ) . unwrap ( ) ;
699
753
assert_eq ! ( r. passed, * ALTROOT_LINTS ) ;
700
754
assert_eq ! ( r. fatal, 0 ) ;
701
755
assert_eq ! ( r. skipped, running_only_lints) ;
702
756
assert_eq ! ( r. warnings, 0 ) ;
703
757
704
- let r = lint_inner ( root, root_type, [ "var-log" ] , & mut out) . unwrap ( ) ;
758
+ let r = lint_inner ( root, root_type, Applicability :: Scan , [ "var-log" ] , & mut out) . unwrap ( ) ;
705
759
// Trigger a failure in var-log
706
760
root. create_dir_all ( "var/log/dnf" ) ?;
707
761
root. write ( "var/log/dnf/dnf.log" , b"dummy dnf log" ) ?;
@@ -712,7 +766,7 @@ mod tests {
712
766
713
767
// But verify that not skipping it results in a warning
714
768
let mut out = Vec :: new ( ) ;
715
- let r = lint_inner ( root, root_type, [ ] , & mut out) . unwrap ( ) ;
769
+ let r = lint_inner ( root, root_type, Applicability :: Scan , [ ] , & mut out) . unwrap ( ) ;
716
770
assert_eq ! ( r. passed, ALTROOT_LINTS . checked_sub( 1 ) . unwrap( ) ) ;
717
771
assert_eq ! ( r. fatal, 0 ) ;
718
772
assert_eq ! ( r. skipped, running_only_lints) ;
@@ -928,5 +982,17 @@ mod tests {
928
982
lint_list ( & mut r) . unwrap ( ) ;
929
983
let lints: Vec < serde_yaml:: Value > = serde_yaml:: from_slice ( & r) . unwrap ( ) ;
930
984
assert_eq ! ( lints. len( ) , LINTS . len( ) ) ;
985
+ let tmpfiles = lints
986
+ . iter ( )
987
+ . find ( |v| {
988
+ let v = v. as_mapping ( ) . unwrap ( ) ;
989
+ let name = v. get ( "name" ) . unwrap ( ) ;
990
+ return name == "var-tmpfiles" ;
991
+ } )
992
+ . unwrap ( )
993
+ . as_mapping ( )
994
+ . unwrap ( ) ;
995
+ let ty = tmpfiles. get ( "apply" ) . unwrap ( ) ;
996
+ assert_eq ! ( ty. as_str( ) . unwrap( ) , "scan-or-fix" ) ;
931
997
}
932
998
}
0 commit comments