2
2
//
3
3
// SPDX-License-Identifier: MPL-2.0
4
4
5
+ use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
6
+ use std:: sync:: { Arc , RwLock } ;
5
7
use std:: { collections:: BTreeSet , fmt, io, path:: PathBuf } ;
6
8
7
9
use itertools:: Itertools ;
8
10
9
11
use fs_err as fs;
12
+ use rayon:: iter:: { IntoParallelRefIterator , ParallelIterator } ;
10
13
use stone:: { payload:: layout, write:: digest} ;
11
14
use tui:: {
12
15
dialoguer:: { theme:: ColorfulTheme , Confirm } ,
@@ -37,9 +40,6 @@ pub fn verify(client: &Client, yes: bool, verbose: bool) -> Result<(), client::E
37
40
} )
38
41
. into_group_map ( ) ;
39
42
40
- let mut issues = vec ! [ ] ;
41
- let mut hasher = digest:: Hasher :: new ( ) ;
42
-
43
43
let pb = ProgressBar :: new ( unique_assets. len ( ) as u64 )
44
44
. with_message ( "Verifying" )
45
45
. with_style (
@@ -49,14 +49,18 @@ pub fn verify(client: &Client, yes: bool, verbose: bool) -> Result<(), client::E
49
49
) ;
50
50
pb. tick ( ) ;
51
51
52
- // For each asset, ensure it exists in the content store and isn't corrupt (hash is correct)
53
- for ( hash, meta) in unique_assets
52
+ let issues_arcrw = Arc :: new ( RwLock :: new ( Vec :: new ( ) ) ) ;
53
+
54
+ let sorted_unique_assets = unique_assets
54
55
. into_iter ( )
55
56
. sorted_by_key ( |( key, _) | format ! ( "{key:0>32}" ) )
56
- {
57
+ . collect :: < Vec < _ > > ( ) ;
58
+
59
+ // For each asset, ensure it exists in the content store and isn't corrupt (hash is correct)
60
+ sorted_unique_assets. par_iter ( ) . for_each ( |( hash, meta) | {
57
61
let display_hash = format ! ( "{hash:0>32}" ) ;
58
62
59
- let path = cache:: asset_path ( & client. installation , & hash) ;
63
+ let path = cache:: asset_path ( & client. installation , hash) ;
60
64
61
65
let files = meta. iter ( ) . map ( |( _, file) | file) . cloned ( ) . collect :: < BTreeSet < _ > > ( ) ;
62
66
@@ -67,43 +71,55 @@ pub fn verify(client: &Client, yes: bool, verbose: bool) -> Result<(), client::E
67
71
if verbose {
68
72
pb. suspend ( || println ! ( " {} {display_hash} - {files:?}" , "×" . yellow( ) ) ) ;
69
73
}
70
- issues. push ( Issue :: MissingAsset {
71
- hash : display_hash,
72
- files,
73
- packages : meta. into_iter ( ) . map ( |( package, _) | package) . collect ( ) ,
74
- } ) ;
75
- continue ;
74
+ {
75
+ let mut lock = issues_arcrw. write ( ) . unwrap ( ) ;
76
+ lock. push ( Issue :: MissingAsset {
77
+ hash : display_hash,
78
+ files,
79
+ packages : meta. iter ( ) . map ( |( package, _) | package) . collect ( ) ,
80
+ } ) ;
81
+ }
82
+ return ;
76
83
}
77
84
85
+ let mut hasher = digest:: Hasher :: new ( ) ;
78
86
hasher. reset ( ) ;
79
87
80
88
let mut digest_writer = digest:: Writer :: new ( io:: sink ( ) , & mut hasher) ;
81
- let mut file = fs:: File :: open ( & path) ?;
89
+ let res = fs:: File :: open ( & path) ;
90
+
91
+ if res. is_err ( ) {
92
+ return ;
93
+ }
94
+
95
+ let mut file = res. unwrap ( ) ;
82
96
83
- // Copy bytes to null sink so we don't
84
- // explode memory
85
- io:: copy ( & mut file, & mut digest_writer) ?;
97
+ // Copy bytes to null sink so we don't explode memory
98
+ io:: copy ( & mut file, & mut digest_writer) . unwrap_or_default ( ) ;
86
99
87
100
let verified_hash = format ! ( "{:02x}" , hasher. digest128( ) ) ;
88
101
89
- if verified_hash != hash {
102
+ if & verified_hash != hash {
90
103
pb. inc ( 1 ) ;
91
104
if verbose {
92
105
pb. suspend ( || println ! ( " {} {display_hash} - {files:?}" , "×" . yellow( ) ) ) ;
93
106
}
94
- issues. push ( Issue :: CorruptAsset {
95
- hash : display_hash,
96
- files,
97
- packages : meta. into_iter ( ) . map ( |( package, _) | package) . collect ( ) ,
98
- } ) ;
99
- continue ;
107
+ {
108
+ let mut lock = issues_arcrw. write ( ) . unwrap ( ) ;
109
+ lock. push ( Issue :: CorruptAsset {
110
+ hash : display_hash,
111
+ files,
112
+ packages : meta. iter ( ) . map ( |( package, _) | package) . collect ( ) ,
113
+ } ) ;
114
+ }
115
+ return ;
100
116
}
101
117
102
118
pb. inc ( 1 ) ;
103
119
if verbose {
104
120
pb. suspend ( || println ! ( " {} {display_hash} - {files:?}" , "»" . green( ) ) ) ;
105
121
}
106
- }
122
+ } ) ;
107
123
108
124
// Get all states
109
125
let states = client. state_db . all ( ) ?;
@@ -114,13 +130,11 @@ pub fn verify(client: &Client, yes: bool, verbose: bool) -> Result<(), client::E
114
130
println ! ( "Verifying states" ) ;
115
131
} ) ;
116
132
117
- // Check the VFS of each state exists properly on the FS
118
- for state in & states {
133
+ states. par_iter ( ) . for_each ( |state| {
119
134
pb. set_message ( format ! ( "Verifying state #{}" , state. id) ) ;
120
-
121
135
let is_active = client. installation . active_state == Some ( state. id ) ;
122
136
123
- let vfs = client. vfs ( state. selections . iter ( ) . map ( |s| & s. package ) ) ? ;
137
+ let vfs = client. vfs ( state. selections . iter ( ) . map ( |s| & s. package ) ) . unwrap ( ) ;
124
138
125
139
let base = if is_active {
126
140
client. installation . root . join ( "usr" )
@@ -130,7 +144,7 @@ pub fn verify(client: &Client, yes: bool, verbose: bool) -> Result<(), client::E
130
144
131
145
let files = vfs. iter ( ) . collect :: < Vec < _ > > ( ) ;
132
146
133
- let mut num_issues = 0 ;
147
+ let counter = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
134
148
135
149
for file in files {
136
150
let path = base. join ( file. path ( ) . strip_prefix ( "/usr/" ) . unwrap_or_default ( ) ) ;
@@ -144,33 +158,38 @@ pub fn verify(client: &Client, yes: bool, verbose: bool) -> Result<(), client::E
144
158
Ok ( true ) => { }
145
159
Ok ( false ) if path. is_symlink ( ) => { }
146
160
_ => {
147
- num_issues += 1 ;
148
- issues. push ( Issue :: MissingVFSPath { path, state : state. id } ) ;
161
+ counter. fetch_add ( 1 , Ordering :: Relaxed ) ;
162
+ {
163
+ let mut lock = issues_arcrw. write ( ) . unwrap ( ) ;
164
+ lock. push ( Issue :: MissingVFSPath { path, state : state. id } ) ;
165
+ }
149
166
}
150
167
}
151
168
}
152
169
153
170
pb. inc ( 1 ) ;
154
171
if verbose {
155
- let mark = if num_issues > 0 { "×" . yellow ( ) } else { "»" . green ( ) } ;
172
+ let mark = if counter. load ( Ordering :: Relaxed ) > 0 {
173
+ "×" . yellow ( )
174
+ } else {
175
+ "»" . green ( )
176
+ } ;
156
177
pb. suspend ( || println ! ( " {mark} state #{}" , state. id) ) ;
157
178
}
158
- }
179
+ } ) ;
159
180
160
181
pb. finish_and_clear ( ) ;
161
182
162
- if issues. is_empty ( ) {
183
+ let lock = issues_arcrw. write ( ) . unwrap ( ) ;
184
+
185
+ if lock. is_empty ( ) {
163
186
println ! ( "No issues found" ) ;
164
187
return Ok ( ( ) ) ;
165
188
}
166
189
167
- println ! (
168
- "Found {} issue{}" ,
169
- issues. len( ) ,
170
- if issues. len( ) == 1 { "" } else { "s" }
171
- ) ;
190
+ println ! ( "Found {} issue{}" , lock. len( ) , if lock. len( ) == 1 { "" } else { "s" } ) ;
172
191
173
- for issue in & issues {
192
+ for issue in lock . iter ( ) {
174
193
println ! ( " {} {issue}" , "×" . yellow( ) ) ;
175
194
}
176
195
@@ -187,15 +206,15 @@ pub fn verify(client: &Client, yes: bool, verbose: bool) -> Result<(), client::E
187
206
}
188
207
189
208
// Calculate and resolve the unique set of packages with asset issues
190
- let issue_packages = issues
209
+ let issue_packages = lock
191
210
. iter ( )
192
211
. filter_map ( Issue :: packages)
193
212
. flatten ( )
194
213
. collect :: < BTreeSet < _ > > ( )
195
214
. into_iter ( )
196
215
. map ( |id| {
197
216
client. install_db . get ( id) . map ( |meta| Package {
198
- id : id. clone ( ) ,
217
+ id : id. to_owned ( ) . to_owned ( ) ,
199
218
meta,
200
219
flags : package:: Flags :: default ( ) ,
201
220
} )
@@ -205,7 +224,7 @@ pub fn verify(client: &Client, yes: bool, verbose: bool) -> Result<(), client::E
205
224
// We had some corrupt or missing assets, let's resolve that!
206
225
if !issue_packages. is_empty ( ) {
207
226
// Remove all corrupt assets
208
- for corrupt_hash in issues . iter ( ) . filter_map ( Issue :: corrupt_hash) {
227
+ for corrupt_hash in lock . iter ( ) . filter_map ( Issue :: corrupt_hash) {
209
228
let path = cache:: asset_path ( & client. installation , corrupt_hash) ;
210
229
fs:: remove_file ( & path) ?;
211
230
}
@@ -227,7 +246,7 @@ pub fn verify(client: &Client, yes: bool, verbose: bool) -> Result<(), client::E
227
246
. any ( |s| issue_packages. iter ( ) . any ( |p| p. id == s. package ) )
228
247
. then_some ( & state. id )
229
248
} )
230
- . chain ( issues . iter ( ) . filter_map ( Issue :: state) )
249
+ . chain ( lock . iter ( ) . filter_map ( Issue :: state) )
231
250
. collect :: < BTreeSet < _ > > ( ) ;
232
251
233
252
println ! ( "Reblitting affected states" ) ;
@@ -277,24 +296,24 @@ pub fn verify(client: &Client, yes: bool, verbose: bool) -> Result<(), client::E
277
296
}
278
297
279
298
#[ derive( Debug ) ]
280
- enum Issue {
299
+ enum Issue < ' a > {
281
300
CorruptAsset {
282
301
hash : String ,
283
302
files : BTreeSet < String > ,
284
- packages : BTreeSet < package:: Id > ,
303
+ packages : BTreeSet < & ' a package:: Id > ,
285
304
} ,
286
305
MissingAsset {
287
306
hash : String ,
288
307
files : BTreeSet < String > ,
289
- packages : BTreeSet < package:: Id > ,
308
+ packages : BTreeSet < & ' a package:: Id > ,
290
309
} ,
291
310
MissingVFSPath {
292
311
path : PathBuf ,
293
312
state : state:: Id ,
294
313
} ,
295
314
}
296
315
297
- impl Issue {
316
+ impl Issue < ' _ > {
298
317
fn corrupt_hash ( & self ) -> Option < & str > {
299
318
match self {
300
319
Issue :: CorruptAsset { hash, .. } => Some ( hash) ,
@@ -303,7 +322,7 @@ impl Issue {
303
322
}
304
323
}
305
324
306
- fn packages ( & self ) -> Option < & BTreeSet < package:: Id > > {
325
+ fn packages ( & self ) -> Option < & BTreeSet < & package:: Id > > {
307
326
match self {
308
327
Issue :: CorruptAsset { packages, .. } | Issue :: MissingAsset { packages, .. } => Some ( packages) ,
309
328
Issue :: MissingVFSPath { .. } => None ,
@@ -318,7 +337,7 @@ impl Issue {
318
337
}
319
338
}
320
339
321
- impl fmt:: Display for Issue {
340
+ impl fmt:: Display for Issue < ' _ > {
322
341
fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
323
342
match self {
324
343
Issue :: CorruptAsset { hash, files, .. } => write ! ( f, "Corrupt asset {hash} - {files:?}" ) ,
0 commit comments