system_update_committer/fidl.rs
1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::{Context, Error};
6use fidl_fuchsia_update as fupdate;
7use futures::prelude::*;
8use std::sync::Arc;
9
10pub struct FuchsiaUpdateFidlServer {
11 p_external: zx::EventPair,
12 wait_for_status_check: future::Shared<futures::future::BoxFuture<'static, Result<(), String>>>,
13 idle_timeout: zx::MonotonicDuration,
14}
15
16impl FuchsiaUpdateFidlServer {
17 pub fn new(
18 p_external: zx::EventPair,
19 wait_for_status_check: impl std::future::Future<Output = Result<(), String>> + Send + 'static,
20 idle_timeout: zx::MonotonicDuration,
21 ) -> Self {
22 Self {
23 p_external,
24 wait_for_status_check: wait_for_status_check.boxed().shared(),
25 idle_timeout,
26 }
27 }
28
29 /// Handle a fuchsia.update/CommitStatusProvider request stream.
30 pub async fn handle_commit_status_provider_stream(
31 self: Arc<Self>,
32 stream: fupdate::CommitStatusProviderRequestStream,
33 ) -> Result<(), Error> {
34 let (stream, unbind_if_stalled) = detect_stall::until_stalled(stream, self.idle_timeout);
35 let mut stream = std::pin::pin!(stream);
36 while let Some(request) =
37 stream.try_next().await.context("while receiving CommitStatusProvider request")?
38 {
39 let () = Self::handle_request(self.clone(), request).await?;
40 }
41
42 if let Ok(Some(server_end)) = unbind_if_stalled.await {
43 fuchsia_component::client::connect_channel_to_protocol_at::<
44 fupdate::CommitStatusProviderMarker,
45 >(server_end, "/escrow")
46 .context("escrowing stream")?;
47 }
48
49 Ok(())
50 }
51
52 async fn handle_request(
53 self: Arc<Self>,
54 req: fupdate::CommitStatusProviderRequest,
55 ) -> Result<(), Error> {
56 // The server should only unblock when either of these conditions are met:
57 // * The current configuration was already committed on boot and p_external already has
58 // `USER_0` asserted.
59 // * The current configuration was pending on boot (p_external may or may not have `USER_0`
60 // asserted depending on how quickly the system is committed).
61 //
62 // The common case is that the current configuration is committed on boot, so this
63 // arrangement avoids the race condition where a client (e.g. an update checker) queries
64 // the commit status right after boot, sees that `USER_0` is not yet asserted (because the
65 // system-update-committer itself is still obtaining the status from the paver) and then
66 // postpones its work (e.g. an update check), when it would have been able to continue if
67 // it had queried the commit status a couple seconds later (many clients check the
68 // instantaneous state of `USER_0` instead of waiting for it to be asserted so that they
69 // can report failure and retry later instead of blocking indefinitely).
70 //
71 // If there is an error with `put_metadata_in_happy_state`, the FIDL server will hang here
72 // indefinitely. This is acceptable because we'll Soon™ reboot on error.
73 let () = self
74 .wait_for_status_check
75 .clone()
76 .await
77 .map_err(|e| anyhow::anyhow!("while unblocking fidl server: {e}"))?;
78 let fupdate::CommitStatusProviderRequest::IsCurrentSystemCommitted { responder } = req;
79 responder
80 .send(
81 self.p_external
82 .duplicate_handle(zx::Rights::BASIC)
83 .context("while duplicating p_external")?,
84 )
85 .context("while sending IsCurrentSystemCommitted response")
86 }
87}