Skip to main content

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}