persistence/
lib.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
5//! `diagnostics-persistence` component persists Inspect VMOs and serves them at the next boot.
6
7mod constants;
8mod fetcher;
9mod file_handler;
10mod inspect_server;
11mod persist_server;
12mod scheduler;
13
14use anyhow::{format_err, Context, Error};
15use argh::FromArgs;
16use fidl::endpoints;
17use fuchsia_component::client;
18use fuchsia_component::server::ServiceFs;
19use fuchsia_inspect::component;
20use fuchsia_inspect::health::Reporter;
21use futures::{StreamExt, TryStreamExt};
22use log::*;
23use persist_server::PersistServer;
24use persistence_config::Config;
25use scheduler::Scheduler;
26use zx::BootInstant;
27use {
28    fidl_fuchsia_component_sandbox as fsandbox, fidl_fuchsia_update as fupdate,
29    fuchsia_async as fasync,
30};
31
32/// The name of the subcommand and the logs-tag.
33pub const PROGRAM_NAME: &str = "persistence";
34pub const PERSIST_NODE_NAME: &str = "persist";
35/// Added after persisted data is fully published
36pub const PUBLISHED_TIME_KEY: &str = "published";
37
38/// Command line args
39#[derive(FromArgs, Debug, PartialEq)]
40#[argh(subcommand, name = "persistence")]
41pub struct CommandLine {}
42
43pub async fn main(_args: CommandLine) -> Result<(), Error> {
44    info!("Starting Diagnostics Persistence Service service");
45    let mut health = component::health();
46    let config = persistence_config::load_configuration_files().context("Error loading configs")?;
47    let inspector = component::inspector();
48    let _inspect_server_task =
49        inspect_runtime::publish(inspector, inspect_runtime::PublishOptions::default());
50
51    file_handler::forget_old_data(&config);
52
53    let scope = fasync::Scope::new();
54    let scheduler =
55        Scheduler::new(scope.to_handle(), &config).context("Error creating scheduler")?;
56
57    // Add a persistence fidl service for each service defined in the config files.
58    let scope = fasync::Scope::new();
59    let services_scope = scope.new_child_with_name("services");
60
61    let _service_scopes = spawn_persist_services(&config, scheduler, &services_scope)
62        .await
63        .expect("Error spawning persist services");
64
65    // Before serving previous data, wait until the post-boot system update check has finished.
66    // Note: We're already accepting persist requests. If we receive a request, store
67    // some data, and then cache is cleared after data is persisted, that data will be lost. This
68    // is correct behavior - we don't want to remember anything from before the cache was cleared.
69    scope.spawn(async move {
70        info!("Waiting for post-boot update check...");
71        let (notifier_client, mut notifier_request_stream) =
72            fidl::endpoints::create_request_stream::<fupdate::NotifierMarker>();
73        match fuchsia_component::client::connect_to_protocol::<fupdate::ListenerMarker>() {
74            Ok(proxy) => {
75                if let Err(e) = proxy.notify_on_first_update_check(
76                    fupdate::ListenerNotifyOnFirstUpdateCheckRequest {
77                        notifier: Some(notifier_client),
78                        ..Default::default()
79                    },
80                ) {
81                    error!(e:?; "Error subscribing to first update check; not publishing");
82                    return;
83                }
84            }
85            Err(e) => {
86                warn!(
87                    e:?;
88                    "Unable to connect to fuchsia.update.Listener; will publish immediately"
89                );
90            }
91        }
92
93        match notifier_request_stream.try_next().await {
94            Ok(Some(fupdate::NotifierRequest::Notify { control_handle: _ })) => {}
95            Ok(None) => {
96                warn!("Did not receive update notification; not publishing");
97                return;
98            }
99            Err(e) => {
100                error!("Error waiting for update notification; not publishing: {e}");
101                return;
102            }
103        }
104
105        // Start serving previous boot data
106        info!("...Update check has completed; publishing previous boot data");
107        inspector.root().record_child(PERSIST_NODE_NAME, |node| {
108            inspect_server::serve_persisted_data(node);
109            health.set_ok();
110            info!("Diagnostics Persistence Service ready");
111        });
112        inspector.root().record_int(PUBLISHED_TIME_KEY, BootInstant::get().into_nanos());
113    });
114
115    scope.await;
116
117    Ok(())
118}
119
120enum IncomingRequest {
121    Router(fsandbox::DictionaryRouterRequestStream),
122}
123
124// Serve a DataPersistence capability for each service defined in `config` using
125// a dynamic dictionary.
126async fn spawn_persist_services(
127    config: &Config,
128    scheduler: Scheduler,
129    scope: &fasync::Scope,
130) -> Result<Vec<fasync::Scope>, Error> {
131    let store = client::connect_to_protocol::<fsandbox::CapabilityStoreMarker>().unwrap();
132    let id_gen = sandbox::CapabilityIdGenerator::new();
133
134    let services_dict = id_gen.next();
135    store
136        .dictionary_create(services_dict)
137        .await
138        .context("Failed to send FIDL to create dictionary")?
139        .map_err(|e| format_err!("Failed to create dictionary: {e:?}"))?;
140
141    // Register each service with the exposed CFv2 dynamic dictionary.
142    let mut service_scopes = Vec::with_capacity(config.len());
143
144    for service_name in config.keys() {
145        let connector_id = id_gen.next();
146        let (receiver, receiver_stream) =
147            endpoints::create_request_stream::<fsandbox::ReceiverMarker>();
148
149        store
150            .connector_create(connector_id, receiver)
151            .await
152            .context("Failed to send FIDL to create connector")?
153            .map_err(|e| format_err!("Failed to create connector: {e:?}"))?;
154
155        store
156            .dictionary_insert(
157                services_dict,
158                &fsandbox::DictionaryItem {
159                    key: format!("{}-{}", constants::PERSIST_SERVICE_NAME_PREFIX, service_name),
160                    value: connector_id,
161                },
162            )
163            .await
164            .context(
165                "Failed to send FIDL to insert into diagnostics-persist-capabilities dictionary",
166            )?
167            .map_err(|e| {
168                format_err!(
169                    "Failed to insert into diagnostics-persist-capabilities dictionary: {e:?}"
170                )
171            })?;
172
173        let service_scope = scope.new_child_with_name(service_name.clone());
174        PersistServer::spawn(
175            service_name.clone(),
176            scheduler.clone(),
177            &service_scope,
178            receiver_stream,
179        );
180        service_scopes.push(service_scope);
181    }
182
183    // Expose the dynamic dictionary.
184    let mut fs = ServiceFs::new();
185    fs.dir("svc").add_fidl_service(IncomingRequest::Router);
186    fs.take_and_serve_directory_handle().expect("Failed to take service directory handle");
187    scope.spawn(fs.for_each_concurrent(None, move |IncomingRequest::Router(mut stream)| {
188        let store = store.clone();
189        let id_gen = id_gen.clone();
190        async move {
191            while let Ok(Some(request)) = stream.try_next().await {
192                match request {
193                    fsandbox::DictionaryRouterRequest::Route { payload: _, responder } => {
194                        let dup_dict_id = id_gen.next();
195                        store.duplicate(services_dict, dup_dict_id).await.unwrap().unwrap();
196                        let capability = store.export(dup_dict_id).await.unwrap().unwrap();
197                        let fsandbox::Capability::Dictionary(dict) = capability else {
198                            panic!("capability was not a dictionary? {capability:?}");
199                        };
200                        let _ = responder
201                            .send(Ok(fsandbox::DictionaryRouterRouteResponse::Dictionary(dict)));
202                    }
203                    fsandbox::DictionaryRouterRequest::_UnknownMethod { ordinal, .. } => {
204                        warn!(ordinal:%; "Unknown DictionaryRouter request");
205                    }
206                }
207            }
208        }
209    }));
210
211    Ok(service_scopes)
212}