| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| crate::{ |
| install_manager::InstallManagerControlHandle, |
| update::{Config, ControlRequest, RebootController, UpdateAttempt, UpdateHistory}, |
| }, |
| anyhow::{anyhow, Context, Error}, |
| event_queue::{ClosedClient, Notify}, |
| fidl_fuchsia_update_installer::{ |
| InstallerRequest, InstallerRequestStream, MonitorProxy, MonitorProxyInterface, |
| RebootControllerRequest, UpdateResult, |
| }, |
| fidl_fuchsia_update_installer_ext::State, |
| fuchsia_component::server::{ServiceFs, ServiceObjLocal}, |
| fuchsia_syslog::{fx_log_err, fx_log_info}, |
| futures::prelude::*, |
| parking_lot::Mutex, |
| std::{convert::TryInto, sync::Arc}, |
| }; |
| |
| pub enum IncomingService { |
| Installer(InstallerRequestStream), |
| } |
| |
| /// This type can be used to send update state events to monitor server ends. |
| #[derive(Clone)] |
| pub struct UpdateStateNotifier { |
| proxy: MonitorProxy, |
| } |
| |
| impl UpdateStateNotifier { |
| pub fn new(proxy: MonitorProxy) -> Self { |
| Self { proxy } |
| } |
| } |
| |
| impl Notify for UpdateStateNotifier { |
| type Event = State; |
| type NotifyFuture = futures::future::Map< |
| <MonitorProxy as MonitorProxyInterface>::OnStateResponseFut, |
| fn(Result<(), fidl::Error>) -> Result<(), ClosedClient>, |
| >; |
| |
| fn notify(&self, state: State) -> Self::NotifyFuture { |
| self.proxy.on_state(&mut state.into()).map(|result| result.map_err(|_| ClosedClient)) |
| } |
| } |
| |
| pub struct FidlServer { |
| history: Arc<Mutex<UpdateHistory>>, |
| install_manager_ch: InstallManagerControlHandle<UpdateStateNotifier>, |
| } |
| |
| impl FidlServer { |
| pub fn new( |
| history: Arc<Mutex<UpdateHistory>>, |
| install_manager_ch: InstallManagerControlHandle<UpdateStateNotifier>, |
| ) -> Self { |
| Self { history, install_manager_ch } |
| } |
| |
| /// Runs the FIDL Server. |
| pub async fn run(self, mut fs: ServiceFs<ServiceObjLocal<'_, IncomingService>>) { |
| fs.dir("svc").add_fidl_service(IncomingService::Installer); |
| |
| // Handles each client connection concurrently. |
| fs.for_each_concurrent(None, |incoming_service| { |
| self.handle_client(incoming_service).unwrap_or_else(|e| { |
| fx_log_err!("error encountered while handling client: {:#}", anyhow!(e)) |
| }) |
| }) |
| .await |
| } |
| |
| /// Handles an incoming FIDL connection from a client. |
| async fn handle_client(&self, incoming_service: IncomingService) -> Result<(), Error> { |
| match incoming_service { |
| IncomingService::Installer(mut stream) => { |
| while let Some(request) = |
| stream.try_next().await.context("error receiving Installer request")? |
| { |
| self.handle_installer_request(request).await?; |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| /// Handles fuchsia.update.update.Installer requests. |
| async fn handle_installer_request(&self, request: InstallerRequest) -> Result<(), Error> { |
| match request { |
| InstallerRequest::GetLastUpdateResult { responder } => { |
| let history = self.history.lock(); |
| let last_result = into_update_result(history.last_update_attempt()); |
| responder.send(last_result)?; |
| } |
| InstallerRequest::GetUpdateResult { attempt_id, responder } => { |
| let history = self.history.lock(); |
| let result = into_update_result(history.update_attempt(attempt_id)); |
| responder.send(result)?; |
| } |
| InstallerRequest::StartUpdate { |
| url, |
| options, |
| monitor, |
| reboot_controller, |
| responder, |
| } => { |
| let mut install_manager_ch = self.install_manager_ch.clone(); |
| |
| // Transform FIDL request params into types the install manager can understand. |
| let config = Config::from_url_and_options(url.url.parse()?, options.try_into()?); |
| let notifier = UpdateStateNotifier::new( |
| monitor |
| .into_proxy() |
| .context("while converting monitor ClientEnd into proxy")?, |
| ); |
| |
| // If a reboot controller is specified, set up a task that fowards reboot controller |
| // requests to the update attempt task. |
| let reboot_controller = if let Some(server_end) = reboot_controller { |
| let mut stream = server_end.into_stream()?; |
| Some(RebootController::spawn(async move { |
| match stream.next().await { |
| None => { |
| fx_log_info!("RebootController channel closed, unblocking reboot"); |
| ControlRequest::Unblock |
| } |
| Some(Err(e)) => { |
| fx_log_err!( |
| "error serving RebootController, unblocking reboot: {:#}", |
| anyhow!(e) |
| ); |
| ControlRequest::Unblock |
| } |
| Some(Ok(RebootControllerRequest::Unblock { .. })) => { |
| ControlRequest::Unblock |
| } |
| Some(Ok(RebootControllerRequest::Detach { .. })) => { |
| ControlRequest::Detach |
| } |
| } |
| })) |
| } else { |
| // Not providing a reboot controller is equivalent to immediately unblocking |
| // the reboot. |
| None |
| }; |
| |
| // Forward to the install manager to deal with this. |
| let mut response = |
| install_manager_ch.start_update(config, notifier, reboot_controller).await?; |
| responder.send(&mut response)?; |
| } |
| InstallerRequest::MonitorUpdate { attempt_id, monitor, responder } => { |
| let mut install_manager_ch = self.install_manager_ch.clone(); |
| let notifier = UpdateStateNotifier::new( |
| monitor |
| .into_proxy() |
| .context("while converting monitor ClientEnd into proxy")?, |
| ); |
| |
| // Forward to the install manager to deal with this. |
| let response = install_manager_ch.monitor_update(attempt_id, notifier).await?; |
| responder.send(response)?; |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| fn into_update_result(attempt: Option<&UpdateAttempt>) -> UpdateResult { |
| match attempt { |
| None => UpdateResult { attempt_id: None, url: None, options: None, state: None }, |
| Some(attempt) => attempt.into(), |
| } |
| } |