1use crate::{Disk, FatDirectory, FatFs, fatfs_error_to_status};
6use anyhow::{Context, Error, bail};
7use block_client::RemoteBlockClientSync;
8use fidl::endpoints::{ClientEnd, DiscoverableProtocolMarker, RequestStream, ServerEnd};
9use fidl_fuchsia_fs::{AdminMarker, AdminRequest, AdminRequestStream};
10use fidl_fuchsia_fs_startup::{
11 CheckOptions, FormatOptions, StartOptions, StartupMarker, StartupRequest, StartupRequestStream,
12};
13use fidl_fuchsia_io as fio;
14use fidl_fuchsia_process_lifecycle::{LifecycleRequest, LifecycleRequestStream};
15use fidl_fuchsia_storage_block::BlockMarker;
16use fragile::Fragile;
17use fuchsia_async as fasync;
18use futures::TryStreamExt;
19use log::{error, info, warn};
20use std::cell::RefCell;
21use std::rc::Rc;
22use std::sync::Arc;
23use vfs::directory::helper::DirectlyMutable;
24use vfs::execution_scope::ExecutionScope;
25use vfs::node::Node as _;
26
27fn map_to_raw_status(e: Error) -> zx::sys::zx_status_t {
28 map_to_status(e).into_raw()
29}
30
31fn map_to_status(error: Error) -> zx::Status {
32 match error.downcast::<zx::Status>() {
33 Ok(status) => status,
34 Err(error) => match error.downcast::<std::io::Error>() {
35 Ok(io_error) => fatfs_error_to_status(io_error),
36 Err(error) => {
37 warn!(error:?; "Internal error");
40 zx::Status::INTERNAL
41 }
42 },
43 }
44}
45
46enum State {
47 ComponentStarted,
48 Running(RunningState),
49}
50
51struct RunningState {
52 fs: FatFs,
55}
56
57impl State {
58 fn disconnect(&mut self, outgoing_dir: &vfs::directory::immutable::Simple) -> Option<FatFs> {
62 if let State::Running(RunningState { fs }) =
63 std::mem::replace(self, State::ComponentStarted)
64 {
65 if let Ok(Some(entry)) =
66 outgoing_dir.remove_entry("root", false)
67 {
68 let _ = entry.into_any().downcast::<FatDirectory>().unwrap().close();
69 }
70 Some(fs)
71 } else {
72 None
73 }
74 }
75}
76
77pub struct Component {
78 state: RefCell<State>,
79
80 scope: ExecutionScope,
83
84 admin_scope: ExecutionScope,
89
90 outgoing_dir: Arc<vfs::directory::immutable::Simple>,
92}
93
94impl Component {
95 pub fn new() -> Rc<Self> {
96 Rc::new(Self {
97 state: RefCell::new(State::ComponentStarted),
98 scope: ExecutionScope::new(),
99 admin_scope: ExecutionScope::new(),
100 outgoing_dir: vfs::directory::immutable::simple(),
101 })
102 }
103
104 pub async fn run(
106 self: Rc<Self>,
107 outgoing_dir: zx::Channel,
108 lifecycle_channel: Option<zx::Channel>,
109 ) -> Result<(), Error> {
110 let svc_dir = vfs::directory::immutable::simple();
111 self.outgoing_dir.add_entry("svc", svc_dir.clone()).expect("Unable to create svc dir");
112 let weak = Fragile::new(Rc::downgrade(&self));
113 let weak_startup = weak.clone();
114 svc_dir.add_entry(
115 StartupMarker::PROTOCOL_NAME,
116 vfs::service::endpoint(move |_scope, channel| {
117 let weak = weak_startup.clone();
118 let requests = StartupRequestStream::from_channel(channel);
119 if let Some(me) = weak.get().upgrade() {
120 let weak_task = weak.clone();
121 me.admin_scope.spawn_local(async move {
122 if let Some(me) = weak_task.get().upgrade() {
123 let _ = me.handle_startup_requests(requests).await;
124 }
125 });
126 }
127 }),
128 )?;
129
130 let weak_admin = weak.clone();
131 svc_dir.add_entry(
132 AdminMarker::PROTOCOL_NAME,
133 vfs::service::endpoint(move |_scope, channel| {
134 let weak = weak_admin.clone();
135 let requests = AdminRequestStream::from_channel(channel);
136 if let Some(me) = weak.get().upgrade() {
137 let weak_task = weak.clone();
138 me.admin_scope.spawn_local(async move {
139 if let Some(me) = weak_task.get().upgrade() {
140 let _ = me.handle_admin_requests(requests).await;
141 }
142 });
143 }
144 }),
145 )?;
146
147 vfs::directory::serve_on(
148 self.outgoing_dir.clone(),
149 fio::PERM_READABLE | fio::PERM_WRITABLE,
150 self.admin_scope.clone(),
151 ServerEnd::new(outgoing_dir),
152 );
153
154 if let Some(channel) = lifecycle_channel {
155 let weak = Fragile::new(Rc::downgrade(&self));
156 self.admin_scope.spawn_local(async move {
157 if let Some(me) = weak.get().upgrade() {
158 if let Err(error) = me.handle_lifecycle_requests(channel).await {
159 warn!(error:?; "handle_lifecycle_requests");
160 }
161 }
162 });
163 }
164
165 self.admin_scope.wait().await;
170 self.scope.wait().await;
171 self.stop_filesystem().await;
172
173 Ok(())
174 }
175
176 async fn handle_startup_requests(&self, mut stream: StartupRequestStream) -> Result<(), Error> {
177 while let Some(request) = stream.try_next().await? {
178 match request {
179 StartupRequest::Start { responder, device, options } => {
180 responder.send(self.handle_start(device, options).await.map_err(|e| {
181 error!(e:?; "handle_start failed");
182 map_to_raw_status(e)
183 }))?
184 }
185 StartupRequest::Format { responder, device, options } => {
186 responder.send(self.handle_format(device, options).await.map_err(|e| {
187 error!(e:?; "handle_format failed");
188 map_to_raw_status(e)
189 }))?
190 }
191 StartupRequest::Check { responder, device, options } => {
192 responder.send(self.handle_check(device, options).await.map_err(|e| {
193 error!(e:?; "handle_check failed");
194 map_to_raw_status(e)
195 }))?
196 }
197 }
198 }
199 Ok(())
200 }
201
202 async fn start_with_disk(&self, disk: Box<dyn Disk>) -> Result<(), Error> {
203 self.stop_filesystem().await;
204
205 self.scope.resurrect();
207
208 let fs = FatFs::new(disk, self.scope.clone()).map_err(|_| zx::Status::IO)?;
209 let root = fs.get_root()?;
210
211 self.outgoing_dir.add_entry("root", root)?;
212
213 *self.state.borrow_mut() = State::Running(RunningState { fs });
214
215 Ok(())
216 }
217
218 async fn handle_start(
219 &self,
220 device: ClientEnd<BlockMarker>,
221 options: StartOptions,
222 ) -> Result<(), Error> {
223 info!(options:?; "Received start request");
224
225 let remote_block_client = RemoteBlockClientSync::new(device)?;
226 let device = block_client::Cache::new(remote_block_client)?;
227
228 self.start_with_disk(Box::new(device)).await?;
229
230 info!("Mounted");
231 Ok(())
232 }
233
234 async fn handle_format(
235 &self,
236 device: ClientEnd<BlockMarker>,
237 options: FormatOptions,
238 ) -> Result<(), Error> {
239 let args: Box<dyn Iterator<Item = _> + Send> =
240 if let Some(spc) = options.sectors_per_cluster {
241 Box::new(["-c".to_string(), format!("{spc}")].into_iter())
242 } else {
243 Box::new(std::iter::empty())
244 };
245 if block_adapter::run(device.into_proxy(), "/pkg/bin/mkfs-msdosfs", args).await? == 0 {
246 Ok(())
247 } else {
248 bail!(zx::Status::IO)
249 }
250 }
251
252 async fn handle_check(
253 &self,
254 device: ClientEnd<BlockMarker>,
255 _options: CheckOptions,
256 ) -> Result<(), Error> {
257 if block_adapter::run(
260 device.into_proxy(),
261 "/pkg/bin/fsck-msdosfs",
262 ["-n".to_string()].into_iter(),
263 )
264 .await?
265 == 0
266 {
267 Ok(())
268 } else {
269 bail!(zx::Status::IO)
270 }
271 }
272
273 async fn handle_admin_requests(&self, mut stream: AdminRequestStream) -> Result<(), Error> {
274 while let Some(request) = stream.try_next().await.context("Reading request")? {
275 if self.handle_admin(request).await? {
276 break;
277 }
278 }
279 Ok(())
280 }
281
282 async fn handle_admin(&self, req: AdminRequest) -> Result<bool, Error> {
284 match req {
285 AdminRequest::Shutdown { responder } => {
286 info!("Received shutdown request");
287 self.stop_filesystem().await;
288 responder
289 .send()
290 .unwrap_or_else(|e| warn!("Failed to send shutdown response: {}", e));
291 return Ok(true);
292 }
293 }
294 }
295
296 async fn stop_filesystem(&self) {
297 info!("Stopping fatfs runtime; remaining connections will be forcibly closed");
298
299 let maybe_fs = self.state.borrow_mut().disconnect(&self.outgoing_dir);
303
304 self.scope.shutdown();
305 self.scope.wait().await;
306
307 if let Some(fs) = maybe_fs {
311 if let Err(error) = fs.shut_down() {
312 error!(error:?; "Failed to shutdown fatfs");
313 } else {
314 info!("Filesystem terminated");
315 }
316 }
317 }
318
319 async fn handle_lifecycle_requests(&self, lifecycle_channel: zx::Channel) -> Result<(), Error> {
320 let mut stream =
321 LifecycleRequestStream::from_channel(fasync::Channel::from_channel(lifecycle_channel));
322 match stream.try_next().await.context("Reading request")? {
323 Some(LifecycleRequest::Stop { .. }) => {
324 info!("Received Lifecycle::Stop request");
325 self.stop_filesystem().await;
326 }
327 None => {}
328 }
329 Ok(())
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336 use fidl::endpoints::{Proxy, create_proxy};
337 use fuchsia_async as fasync;
338
339 #[fuchsia::test]
340 async fn test_component_lifecycle_with_open_connections() {
341 let component = Component::new();
342
343 let mut buffer = vec![0u8; 2048 << 10];
345 let cursor = std::io::Cursor::new(buffer.as_mut_slice());
346 fatfs::format_volume(cursor, fatfs::FormatVolumeOptions::new()).unwrap();
347 let disk: Box<dyn Disk> = Box::new(std::io::Cursor::new(buffer));
348
349 component.start_with_disk(disk).await.unwrap();
350
351 let (outgoing_dir_client, outgoing_dir_server) = create_proxy::<fio::DirectoryMarker>();
352 let component_clone = component.clone();
353 let run_task = fasync::Task::local(async move {
354 component_clone.run(outgoing_dir_server.into_channel(), None).await.unwrap();
355 });
356
357 let (root_client, root_server) = create_proxy::<fio::NodeMarker>();
359 outgoing_dir_client
360 .open(
361 "root",
362 fio::PERM_READABLE | fio::PERM_WRITABLE,
363 &Default::default(),
364 root_server.into_channel(),
365 )
366 .unwrap();
367 let root_client = fio::DirectoryProxy::new(root_client.into_channel().unwrap());
368
369 let _ = root_client.query().await.unwrap();
371
372 drop(outgoing_dir_client);
374
375 use futures::FutureExt;
377 let mut run_task = run_task.fuse();
378 let mut delay = Box::pin(fasync::Timer::new(fasync::MonotonicInstant::after(
379 zx::MonotonicDuration::from_millis(100),
380 )))
381 .fuse();
382 futures::select! {
383 _ = run_task => panic!("Component exited prematurely!"),
384 _ = delay => {},
385 }
386
387 drop(root_client);
389
390 run_task.await;
392 }
393
394 #[fuchsia::test]
395 async fn test_component_restart_with_open_connections() {
396 let component = Component::new();
397
398 let create_disk = || {
400 let mut buffer = vec![0u8; 2048 << 10];
401 let cursor = std::io::Cursor::new(buffer.as_mut_slice());
402 fatfs::format_volume(cursor, fatfs::FormatVolumeOptions::new()).unwrap();
403 Box::new(std::io::Cursor::new(buffer)) as Box<dyn Disk>
404 };
405
406 component.start_with_disk(create_disk()).await.unwrap();
408
409 let (outgoing_dir_client, outgoing_dir_server) = create_proxy::<fio::DirectoryMarker>();
410 let component_clone = component.clone();
411 let _run_task = fasync::Task::local(async move {
412 component_clone.run(outgoing_dir_server.into_channel(), None).await.unwrap();
413 });
414
415 let (root_client1, root_server1) = create_proxy::<fio::NodeMarker>();
417 outgoing_dir_client
418 .open(
419 "root",
420 fio::PERM_READABLE | fio::PERM_WRITABLE,
421 &Default::default(),
422 root_server1.into_channel(),
423 )
424 .unwrap();
425 let root_client1 = fio::DirectoryProxy::new(root_client1.into_channel().unwrap());
426
427 let _ = root_client1.query().await.unwrap();
429
430 component.start_with_disk(create_disk()).await.unwrap();
432
433 assert!(root_client1.query().await.is_err());
435
436 let (root_client2, root_server2) = create_proxy::<fio::NodeMarker>();
438 outgoing_dir_client
439 .open(
440 "root",
441 fio::PERM_READABLE | fio::PERM_WRITABLE,
442 &Default::default(),
443 root_server2.into_channel(),
444 )
445 .unwrap();
446 let root_client2 = fio::DirectoryProxy::new(root_client2.into_channel().unwrap());
447
448 let _ = root_client2.query().await.unwrap();
450
451 drop(root_client2);
453 drop(outgoing_dir_client);
454 _run_task.await;
455 }
456}