1use commit::do_commit;
8use errors::MetadataError;
9use fidl_fuchsia_update_verify::HealthVerificationProxy;
10use fuchsia_async::TimeoutExt as _;
11use futures::channel::oneshot;
12use policy::PolicyEngine;
13use std::time::Instant;
14use zx::{EventPair, Peered};
15use {fidl_fuchsia_paver as fpaver, fuchsia_inspect as finspect};
16
17mod commit;
18mod configuration_without_recovery;
19mod errors;
20mod inspect;
21mod policy;
22
23pub async fn put_metadata_in_happy_state(
36 boot_manager: &fpaver::BootManagerProxy,
37 p_internal: &EventPair,
38 unblocker: oneshot::Sender<()>,
39 health_verification: &HealthVerificationProxy,
40 commit_timeout: zx::MonotonicDuration,
41 node: &finspect::Node,
42 commit_inspect: &CommitInspect,
43) -> Result<CommitResult, MetadataError> {
44 let start_time = Instant::now();
45 let res = put_metadata_in_happy_state_impl(
46 boot_manager,
47 p_internal,
48 unblocker,
49 health_verification,
50 commit_inspect,
51 )
52 .on_timeout(commit_timeout, || Err(MetadataError::Timeout))
53 .await;
54
55 match &res {
56 Ok(CommitResult::CommitNotNecessary) => (),
57 Ok(CommitResult::CommittedSystem) | Err(_) => {
58 inspect::write_to_inspect(node, res.as_ref().map(|_| ()), start_time.elapsed())
59 }
60 }
61
62 res
63}
64
65async fn put_metadata_in_happy_state_impl(
66 boot_manager: &fpaver::BootManagerProxy,
67 p_internal: &EventPair,
68 unblocker: oneshot::Sender<()>,
69 health_verification: &HealthVerificationProxy,
70 commit_inspect: &CommitInspect,
71) -> Result<CommitResult, MetadataError> {
72 let mut unblocker = Some(unblocker);
73 let commit_result = {
74 let engine = PolicyEngine::build(boot_manager).await.map_err(MetadataError::Policy)?;
75 if let Some((current_config, boot_attempts)) = engine.should_verify_and_commit() {
76 unblocker = unblock_fidl_server(unblocker)?;
79 let () =
80 zx::Status::ok(health_verification.query_health_checks().await.map_err(|e| {
81 MetadataError::HealthVerification(errors::HealthVerificationError::Fidl(e))
82 })?)
83 .map_err(|e| {
84 MetadataError::HealthVerification(errors::HealthVerificationError::Unhealthy(e))
85 })?;
86 let () =
87 do_commit(boot_manager, current_config).await.map_err(MetadataError::Commit)?;
88 let () = commit_inspect.record_boot_attempts(boot_attempts);
89 CommitResult::CommittedSystem
90 } else {
91 CommitResult::CommitNotNecessary
92 }
93 };
94
95 let () = p_internal
97 .signal_peer(zx::Signals::NONE, zx::Signals::USER_0)
98 .map_err(MetadataError::SignalPeer)?;
99
100 unblock_fidl_server(unblocker)?;
102
103 Ok(commit_result)
104}
105
106#[derive(Debug)]
107pub enum CommitResult {
108 CommittedSystem,
109 CommitNotNecessary,
110}
111
112impl CommitResult {
113 pub fn log_msg(&self) -> &'static str {
114 match self {
115 Self::CommittedSystem => "Committed system.",
116 Self::CommitNotNecessary => "Commit not necessary.",
117 }
118 }
119}
120
121pub struct CommitInspect(finspect::Node);
123
124impl CommitInspect {
125 pub fn new(node: finspect::Node) -> Self {
126 Self(node)
127 }
128
129 fn record_boot_attempts(&self, count: Option<u8>) {
130 match count {
131 Some(count) => self.0.record_uint("boot_attempts", count.into()),
132 None => self.0.record_uint("boot_attempts_missing", 0),
133 }
134 }
135}
136
137fn unblock_fidl_server(
138 unblocker: Option<oneshot::Sender<()>>,
139) -> Result<Option<oneshot::Sender<()>>, MetadataError> {
140 if let Some(sender) = unblocker {
141 let () = sender.send(()).map_err(|_| MetadataError::Unblock)?;
142 }
143 Ok(None)
144}
145
146#[cfg(test)]
149mod tests {
150 use super::errors::HealthVerificationError;
151 use super::*;
152 use assert_matches::assert_matches;
153 use configuration_without_recovery::ConfigurationWithoutRecovery;
154 use fasync::OnSignals;
155 use fuchsia_async as fasync;
156 use mock_health_verification::MockHealthVerificationService;
157 use mock_paver::{hooks as mphooks, MockPaverServiceBuilder, PaverEvent};
158 use std::sync::atomic::{AtomicU32, Ordering};
159 use std::sync::Arc;
160 use zx::{AsHandleRef, Status};
161
162 fn health_verification_and_call_count(
163 status: zx::Status,
164 ) -> (HealthVerificationProxy, Arc<AtomicU32>) {
165 let call_count = Arc::new(AtomicU32::new(0));
166 let call_count_clone = Arc::clone(&call_count);
167 let verification = Arc::new(MockHealthVerificationService::new(move || {
168 call_count_clone.fetch_add(1, Ordering::SeqCst);
169 status
170 }));
171
172 let (health_verification, server) = verification.spawn_health_verification_service();
173 let () = server.detach();
174
175 (health_verification, call_count)
176 }
177
178 #[fasync::run_singlethreaded(test)]
181 async fn test_does_not_change_metadata_when_device_does_not_support_abr() {
182 let paver = Arc::new(
183 MockPaverServiceBuilder::new()
184 .boot_manager_close_with_epitaph(Status::NOT_SUPPORTED)
185 .build(),
186 );
187 let (p_internal, p_external) = EventPair::create();
188 let (unblocker, unblocker_recv) = oneshot::channel();
189 let (health_verification, health_verification_call_count) =
190 health_verification_and_call_count(zx::Status::OK);
191
192 put_metadata_in_happy_state_impl(
193 &paver.spawn_boot_manager_service(),
194 &p_internal,
195 unblocker,
196 &health_verification,
197 &CommitInspect::new(finspect::Node::default()),
198 )
199 .await
200 .unwrap();
201
202 assert_eq!(paver.take_events(), vec![]);
203 assert_eq!(
204 p_external.wait_handle(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE_PAST),
205 Ok(zx::Signals::USER_0)
206 );
207 assert_eq!(unblocker_recv.await, Ok(()));
208 assert_eq!(health_verification_call_count.load(Ordering::SeqCst), 0);
209 }
210
211 #[fasync::run_singlethreaded(test)]
214 async fn test_does_not_change_metadata_when_device_in_recovery() {
215 let paver = Arc::new(
216 MockPaverServiceBuilder::new()
217 .current_config(fpaver::Configuration::Recovery)
218 .insert_hook(mphooks::config_status(|_| Ok(fpaver::ConfigurationStatus::Healthy)))
219 .build(),
220 );
221 let (p_internal, p_external) = EventPair::create();
222 let (unblocker, unblocker_recv) = oneshot::channel();
223 let (health_verification, health_verification_call_count) =
224 health_verification_and_call_count(zx::Status::OK);
225
226 put_metadata_in_happy_state_impl(
227 &paver.spawn_boot_manager_service(),
228 &p_internal,
229 unblocker,
230 &health_verification,
231 &CommitInspect::new(finspect::Node::default()),
232 )
233 .await
234 .unwrap();
235
236 assert_eq!(paver.take_events(), vec![PaverEvent::QueryCurrentConfiguration]);
237 assert_eq!(
238 p_external.wait_handle(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE_PAST),
239 Ok(zx::Signals::USER_0)
240 );
241 assert_eq!(unblocker_recv.await, Ok(()));
242 assert_eq!(health_verification_call_count.load(Ordering::SeqCst), 0);
243 }
244
245 async fn test_does_not_change_metadata_when_current_is_healthy(
248 current_config: &ConfigurationWithoutRecovery,
249 ) {
250 let paver = Arc::new(
251 MockPaverServiceBuilder::new()
252 .current_config(current_config.into())
253 .insert_hook(mphooks::config_status_and_boot_attempts(|_| {
254 Ok((fpaver::ConfigurationStatus::Healthy, None))
255 }))
256 .build(),
257 );
258 let (p_internal, p_external) = EventPair::create();
259 let (unblocker, unblocker_recv) = oneshot::channel();
260 let (health_verification, health_verification_call_count) =
261 health_verification_and_call_count(zx::Status::OK);
262
263 put_metadata_in_happy_state_impl(
264 &paver.spawn_boot_manager_service(),
265 &p_internal,
266 unblocker,
267 &health_verification,
268 &CommitInspect::new(finspect::Node::default()),
269 )
270 .await
271 .unwrap();
272
273 assert_eq!(
274 paver.take_events(),
275 vec![
276 PaverEvent::QueryCurrentConfiguration,
277 PaverEvent::QueryConfigurationStatusAndBootAttempts {
278 configuration: current_config.into()
279 }
280 ]
281 );
282 assert_eq!(
283 p_external.wait_handle(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE_PAST),
284 Ok(zx::Signals::USER_0)
285 );
286 assert_eq!(unblocker_recv.await, Ok(()));
287 assert_eq!(health_verification_call_count.load(Ordering::SeqCst), 0);
288 }
289
290 #[fasync::run_singlethreaded(test)]
291 async fn test_does_not_change_metadata_when_current_is_healthy_a() {
292 test_does_not_change_metadata_when_current_is_healthy(&ConfigurationWithoutRecovery::A)
293 .await
294 }
295
296 #[fasync::run_singlethreaded(test)]
297 async fn test_does_not_change_metadata_when_current_is_healthy_b() {
298 test_does_not_change_metadata_when_current_is_healthy(&ConfigurationWithoutRecovery::B)
299 .await
300 }
301
302 async fn test_verifies_and_commits_when_current_is_pending(
304 current_config: &ConfigurationWithoutRecovery,
305 ) {
306 let paver = Arc::new(
307 MockPaverServiceBuilder::new()
308 .current_config(current_config.into())
309 .insert_hook(mphooks::config_status_and_boot_attempts(|_| {
310 Ok((fpaver::ConfigurationStatus::Pending, Some(1)))
311 }))
312 .build(),
313 );
314 let (p_internal, p_external) = EventPair::create();
315 let (unblocker, unblocker_recv) = oneshot::channel();
316 let (health_verification, health_verification_call_count) =
317 health_verification_and_call_count(zx::Status::OK);
318
319 put_metadata_in_happy_state_impl(
320 &paver.spawn_boot_manager_service(),
321 &p_internal,
322 unblocker,
323 &health_verification,
324 &CommitInspect::new(finspect::Node::default()),
325 )
326 .await
327 .unwrap();
328
329 assert_eq!(
330 paver.take_events(),
331 vec![
332 PaverEvent::QueryCurrentConfiguration,
333 PaverEvent::QueryConfigurationStatusAndBootAttempts {
334 configuration: current_config.into()
335 },
336 PaverEvent::SetConfigurationHealthy { configuration: current_config.into() },
337 PaverEvent::SetConfigurationUnbootable {
338 configuration: current_config.to_alternate().into()
339 },
340 PaverEvent::BootManagerFlush,
341 ]
342 );
343 assert_eq!(OnSignals::new(&p_external, zx::Signals::USER_0).await, Ok(zx::Signals::USER_0));
344 assert_eq!(unblocker_recv.await, Ok(()));
345 assert_eq!(health_verification_call_count.load(Ordering::SeqCst), 1);
346 }
347
348 #[fasync::run_singlethreaded(test)]
349 async fn test_verifies_and_commits_when_current_is_pending_a() {
350 test_verifies_and_commits_when_current_is_pending(&ConfigurationWithoutRecovery::A).await
351 }
352
353 #[fasync::run_singlethreaded(test)]
354 async fn test_verifies_and_commits_when_current_is_pending_b() {
355 test_verifies_and_commits_when_current_is_pending(&ConfigurationWithoutRecovery::B).await
356 }
357
358 #[fasync::run_singlethreaded(test)]
360 async fn test_commits_when_failed_verification_ignored() {
361 let paver = Arc::new(
362 MockPaverServiceBuilder::new()
363 .current_config(fpaver::Configuration::Recovery)
364 .insert_hook(mphooks::config_status_and_boot_attempts(|_| {
365 Ok((fpaver::ConfigurationStatus::Pending, Some(1)))
366 }))
367 .build(),
368 );
369 let (p_internal, p_external) = EventPair::create();
370 let (unblocker, unblocker_recv) = oneshot::channel();
371 let (health_verification, health_verification_call_count) =
372 health_verification_and_call_count(zx::Status::INTERNAL);
373
374 put_metadata_in_happy_state_impl(
375 &paver.spawn_boot_manager_service(),
376 &p_internal,
377 unblocker,
378 &health_verification,
379 &CommitInspect::new(finspect::Node::default()),
380 )
381 .await
382 .unwrap();
383
384 assert_eq!(paver.take_events(), vec![PaverEvent::QueryCurrentConfiguration,]);
385 assert_eq!(OnSignals::new(&p_external, zx::Signals::USER_0).await, Ok(zx::Signals::USER_0));
386 assert_eq!(unblocker_recv.await, Ok(()));
387 assert_eq!(health_verification_call_count.load(Ordering::SeqCst), 0);
388 }
389
390 async fn test_errors_when_failed_verification_not_ignored(
392 current_config: &ConfigurationWithoutRecovery,
393 ) {
394 let paver = Arc::new(
395 MockPaverServiceBuilder::new()
396 .current_config(current_config.into())
397 .insert_hook(mphooks::config_status_and_boot_attempts(|_| {
398 Ok((fpaver::ConfigurationStatus::Pending, Some(1)))
399 }))
400 .build(),
401 );
402 let (p_internal, p_external) = EventPair::create();
403 let (unblocker, unblocker_recv) = oneshot::channel();
404 let (health_verification, health_verification_call_count) =
405 health_verification_and_call_count(zx::Status::INTERNAL);
406
407 let result = put_metadata_in_happy_state_impl(
408 &paver.spawn_boot_manager_service(),
409 &p_internal,
410 unblocker,
411 &health_verification,
412 &CommitInspect::new(finspect::Node::default()),
413 )
414 .await;
415
416 assert_matches!(
417 result,
418 Err(MetadataError::HealthVerification(HealthVerificationError::Unhealthy(
419 Status::INTERNAL
420 )))
421 );
422
423 assert_eq!(
424 paver.take_events(),
425 vec![
426 PaverEvent::QueryCurrentConfiguration,
427 PaverEvent::QueryConfigurationStatusAndBootAttempts {
428 configuration: current_config.into()
429 },
430 ]
431 );
432 assert_eq!(
433 p_external.wait_handle(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE_PAST),
434 Err(zx::Status::TIMED_OUT)
435 );
436 assert_eq!(unblocker_recv.await, Ok(()));
437 assert_eq!(health_verification_call_count.load(Ordering::SeqCst), 1);
438 }
439
440 #[fasync::run_singlethreaded(test)]
441 async fn test_errors_when_failed_verification_not_ignored_a() {
442 test_errors_when_failed_verification_not_ignored(&ConfigurationWithoutRecovery::A).await
443 }
444
445 #[fasync::run_singlethreaded(test)]
446 async fn test_errors_when_failed_verification_not_ignored_b() {
447 test_errors_when_failed_verification_not_ignored(&ConfigurationWithoutRecovery::B).await
448 }
449
450 #[fasync::run_singlethreaded(test)]
451 async fn commit_inspect_handles_missing_count() {
452 let inspector = finspect::Inspector::default();
453 let commit_inspect = CommitInspect::new(inspector.root().create_child("commit"));
454
455 commit_inspect.record_boot_attempts(None);
456
457 diagnostics_assertions::assert_data_tree!(inspector, root: {
458 "commit": {
459 "boot_attempts_missing": 0u64
460 }
461 });
462 }
463}