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
205 .wait_handle(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE_PAST)
206 .to_result(),
207 Ok(zx::Signals::USER_0)
208 );
209 assert_eq!(unblocker_recv.await, Ok(()));
210 assert_eq!(health_verification_call_count.load(Ordering::SeqCst), 0);
211 }
212
213 #[fasync::run_singlethreaded(test)]
216 async fn test_does_not_change_metadata_when_device_in_recovery() {
217 let paver = Arc::new(
218 MockPaverServiceBuilder::new()
219 .current_config(fpaver::Configuration::Recovery)
220 .insert_hook(mphooks::config_status(|_| Ok(fpaver::ConfigurationStatus::Healthy)))
221 .build(),
222 );
223 let (p_internal, p_external) = EventPair::create();
224 let (unblocker, unblocker_recv) = oneshot::channel();
225 let (health_verification, health_verification_call_count) =
226 health_verification_and_call_count(zx::Status::OK);
227
228 put_metadata_in_happy_state_impl(
229 &paver.spawn_boot_manager_service(),
230 &p_internal,
231 unblocker,
232 &health_verification,
233 &CommitInspect::new(finspect::Node::default()),
234 )
235 .await
236 .unwrap();
237
238 assert_eq!(paver.take_events(), vec![PaverEvent::QueryCurrentConfiguration]);
239 assert_eq!(
240 p_external
241 .wait_handle(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE_PAST)
242 .to_result(),
243 Ok(zx::Signals::USER_0)
244 );
245 assert_eq!(unblocker_recv.await, Ok(()));
246 assert_eq!(health_verification_call_count.load(Ordering::SeqCst), 0);
247 }
248
249 async fn test_does_not_change_metadata_when_current_is_healthy(
252 current_config: &ConfigurationWithoutRecovery,
253 ) {
254 let paver = Arc::new(
255 MockPaverServiceBuilder::new()
256 .current_config(current_config.into())
257 .insert_hook(mphooks::config_status_and_boot_attempts(|_| {
258 Ok((fpaver::ConfigurationStatus::Healthy, None))
259 }))
260 .build(),
261 );
262 let (p_internal, p_external) = EventPair::create();
263 let (unblocker, unblocker_recv) = oneshot::channel();
264 let (health_verification, health_verification_call_count) =
265 health_verification_and_call_count(zx::Status::OK);
266
267 put_metadata_in_happy_state_impl(
268 &paver.spawn_boot_manager_service(),
269 &p_internal,
270 unblocker,
271 &health_verification,
272 &CommitInspect::new(finspect::Node::default()),
273 )
274 .await
275 .unwrap();
276
277 assert_eq!(
278 paver.take_events(),
279 vec![
280 PaverEvent::QueryCurrentConfiguration,
281 PaverEvent::QueryConfigurationStatusAndBootAttempts {
282 configuration: current_config.into()
283 }
284 ]
285 );
286 assert_eq!(
287 p_external
288 .wait_handle(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE_PAST)
289 .to_result(),
290 Ok(zx::Signals::USER_0)
291 );
292 assert_eq!(unblocker_recv.await, Ok(()));
293 assert_eq!(health_verification_call_count.load(Ordering::SeqCst), 0);
294 }
295
296 #[fasync::run_singlethreaded(test)]
297 async fn test_does_not_change_metadata_when_current_is_healthy_a() {
298 test_does_not_change_metadata_when_current_is_healthy(&ConfigurationWithoutRecovery::A)
299 .await
300 }
301
302 #[fasync::run_singlethreaded(test)]
303 async fn test_does_not_change_metadata_when_current_is_healthy_b() {
304 test_does_not_change_metadata_when_current_is_healthy(&ConfigurationWithoutRecovery::B)
305 .await
306 }
307
308 async fn test_verifies_and_commits_when_current_is_pending(
310 current_config: &ConfigurationWithoutRecovery,
311 ) {
312 let paver = Arc::new(
313 MockPaverServiceBuilder::new()
314 .current_config(current_config.into())
315 .insert_hook(mphooks::config_status_and_boot_attempts(|_| {
316 Ok((fpaver::ConfigurationStatus::Pending, Some(1)))
317 }))
318 .build(),
319 );
320 let (p_internal, p_external) = EventPair::create();
321 let (unblocker, unblocker_recv) = oneshot::channel();
322 let (health_verification, health_verification_call_count) =
323 health_verification_and_call_count(zx::Status::OK);
324
325 put_metadata_in_happy_state_impl(
326 &paver.spawn_boot_manager_service(),
327 &p_internal,
328 unblocker,
329 &health_verification,
330 &CommitInspect::new(finspect::Node::default()),
331 )
332 .await
333 .unwrap();
334
335 assert_eq!(
336 paver.take_events(),
337 vec![
338 PaverEvent::QueryCurrentConfiguration,
339 PaverEvent::QueryConfigurationStatusAndBootAttempts {
340 configuration: current_config.into()
341 },
342 PaverEvent::SetConfigurationHealthy { configuration: current_config.into() },
343 PaverEvent::SetConfigurationUnbootable {
344 configuration: current_config.to_alternate().into()
345 },
346 PaverEvent::BootManagerFlush,
347 ]
348 );
349 assert_eq!(OnSignals::new(&p_external, zx::Signals::USER_0).await, Ok(zx::Signals::USER_0));
350 assert_eq!(unblocker_recv.await, Ok(()));
351 assert_eq!(health_verification_call_count.load(Ordering::SeqCst), 1);
352 }
353
354 #[fasync::run_singlethreaded(test)]
355 async fn test_verifies_and_commits_when_current_is_pending_a() {
356 test_verifies_and_commits_when_current_is_pending(&ConfigurationWithoutRecovery::A).await
357 }
358
359 #[fasync::run_singlethreaded(test)]
360 async fn test_verifies_and_commits_when_current_is_pending_b() {
361 test_verifies_and_commits_when_current_is_pending(&ConfigurationWithoutRecovery::B).await
362 }
363
364 #[fasync::run_singlethreaded(test)]
366 async fn test_commits_when_failed_verification_ignored() {
367 let paver = Arc::new(
368 MockPaverServiceBuilder::new()
369 .current_config(fpaver::Configuration::Recovery)
370 .insert_hook(mphooks::config_status_and_boot_attempts(|_| {
371 Ok((fpaver::ConfigurationStatus::Pending, Some(1)))
372 }))
373 .build(),
374 );
375 let (p_internal, p_external) = EventPair::create();
376 let (unblocker, unblocker_recv) = oneshot::channel();
377 let (health_verification, health_verification_call_count) =
378 health_verification_and_call_count(zx::Status::INTERNAL);
379
380 put_metadata_in_happy_state_impl(
381 &paver.spawn_boot_manager_service(),
382 &p_internal,
383 unblocker,
384 &health_verification,
385 &CommitInspect::new(finspect::Node::default()),
386 )
387 .await
388 .unwrap();
389
390 assert_eq!(paver.take_events(), vec![PaverEvent::QueryCurrentConfiguration,]);
391 assert_eq!(OnSignals::new(&p_external, zx::Signals::USER_0).await, Ok(zx::Signals::USER_0));
392 assert_eq!(unblocker_recv.await, Ok(()));
393 assert_eq!(health_verification_call_count.load(Ordering::SeqCst), 0);
394 }
395
396 async fn test_errors_when_failed_verification_not_ignored(
398 current_config: &ConfigurationWithoutRecovery,
399 ) {
400 let paver = Arc::new(
401 MockPaverServiceBuilder::new()
402 .current_config(current_config.into())
403 .insert_hook(mphooks::config_status_and_boot_attempts(|_| {
404 Ok((fpaver::ConfigurationStatus::Pending, Some(1)))
405 }))
406 .build(),
407 );
408 let (p_internal, p_external) = EventPair::create();
409 let (unblocker, unblocker_recv) = oneshot::channel();
410 let (health_verification, health_verification_call_count) =
411 health_verification_and_call_count(zx::Status::INTERNAL);
412
413 let result = put_metadata_in_happy_state_impl(
414 &paver.spawn_boot_manager_service(),
415 &p_internal,
416 unblocker,
417 &health_verification,
418 &CommitInspect::new(finspect::Node::default()),
419 )
420 .await;
421
422 assert_matches!(
423 result,
424 Err(MetadataError::HealthVerification(HealthVerificationError::Unhealthy(
425 Status::INTERNAL
426 )))
427 );
428
429 assert_eq!(
430 paver.take_events(),
431 vec![
432 PaverEvent::QueryCurrentConfiguration,
433 PaverEvent::QueryConfigurationStatusAndBootAttempts {
434 configuration: current_config.into()
435 },
436 ]
437 );
438 assert_eq!(
439 p_external
440 .wait_handle(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE_PAST)
441 .to_result(),
442 Err(zx::Status::TIMED_OUT)
443 );
444 assert_eq!(unblocker_recv.await, Ok(()));
445 assert_eq!(health_verification_call_count.load(Ordering::SeqCst), 1);
446 }
447
448 #[fasync::run_singlethreaded(test)]
449 async fn test_errors_when_failed_verification_not_ignored_a() {
450 test_errors_when_failed_verification_not_ignored(&ConfigurationWithoutRecovery::A).await
451 }
452
453 #[fasync::run_singlethreaded(test)]
454 async fn test_errors_when_failed_verification_not_ignored_b() {
455 test_errors_when_failed_verification_not_ignored(&ConfigurationWithoutRecovery::B).await
456 }
457
458 #[fasync::run_singlethreaded(test)]
459 async fn commit_inspect_handles_missing_count() {
460 let inspector = finspect::Inspector::default();
461 let commit_inspect = CommitInspect::new(inspector.root().create_child("commit"));
462
463 commit_inspect.record_boot_attempts(None);
464
465 diagnostics_assertions::assert_data_tree!(inspector, root: {
466 "commit": {
467 "boot_attempts_missing": 0u64
468 }
469 });
470 }
471}