1mod 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
32pub const PROGRAM_NAME: &str = "persistence";
34pub const PERSIST_NODE_NAME: &str = "persist";
35pub const PUBLISHED_TIME_KEY: &str = "published";
37
38#[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 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 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 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
124async 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 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 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}