Skip to main content

fxfs_platform_testing/fuchsia/
component.rs

1// Copyright 2022 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 crate::fuchsia::debug::{create_debug_directory, handle_debug_request};
6use crate::fuchsia::errors::map_to_status;
7use crate::fuchsia::memory_pressure::MemoryPressureMonitor;
8use crate::fuchsia::power::FuchsiaPowerManager;
9use crate::fuchsia::volume::MemoryPressureConfig;
10use crate::fuchsia::volumes_directory::VolumesDirectory;
11use anyhow::{Context, Error, bail};
12use async_trait::async_trait;
13use block_client::{BlockClient as _, RemoteBlockClient};
14use fidl::endpoints::{ClientEnd, DiscoverableProtocolMarker, RequestStream};
15use fidl_fuchsia_fs::{AdminMarker, AdminRequest, AdminRequestStream};
16use fidl_fuchsia_fs_startup::{
17    CheckOptions, StartOptions, StartupMarker, StartupRequest, StartupRequestStream, VolumesMarker,
18    VolumesRequest, VolumesRequestStream,
19};
20use fidl_fuchsia_fxfs::{
21    DebugMarker, DebugRequestStream, VolumeInstallerMarker, VolumeInstallerRequest,
22    VolumeInstallerRequestStream,
23};
24use fidl_fuchsia_io as fio;
25use fidl_fuchsia_memory_attribution as fattribution;
26use fidl_fuchsia_process_lifecycle::{LifecycleRequest, LifecycleRequestStream};
27use fidl_fuchsia_storage_block::BlockMarker;
28use fs_inspect::{FsInspect, FsInspectTree, InfoData, UsageData};
29use fuchsia_async as fasync;
30use fuchsia_component_client::connect_to_protocol;
31use futures::TryStreamExt;
32use futures::lock::Mutex;
33use fxfs::filesystem::{FxFilesystem, FxFilesystemBuilder, MIN_BLOCK_SIZE, OpenFxFilesystem, mkfs};
34use fxfs::log::*;
35use fxfs::object_store::volume::root_volume;
36use fxfs::serialized_types::LATEST_VERSION;
37use fxfs::{fsck, metrics};
38use fxfs_trace::{TraceFutureExt, trace_future_args};
39use refaults_vmo::PageRefaultCounter;
40use std::ops::Deref;
41use std::sync::{Arc, Weak};
42use storage_device::DeviceHolder;
43use storage_device::block_device::BlockDevice;
44use vfs::directory::helper::DirectlyMutable;
45use vfs::execution_scope::ExecutionScope;
46
47const FXFS_INFO_NAME: &'static str = "fxfs";
48
49pub fn map_to_raw_status(e: Error) -> zx::sys::zx_status_t {
50    map_to_status(e).into_raw()
51}
52
53pub async fn new_block_client(remote: ClientEnd<BlockMarker>) -> Result<RemoteBlockClient, Error> {
54    Ok(RemoteBlockClient::new(remote.into_proxy()).await?)
55}
56
57/// Runs Fxfs as a component.
58pub struct Component {
59    state: futures::lock::Mutex<State>,
60
61    // The execution scope of the pseudo filesystem.
62    scope: ExecutionScope,
63
64    // The root of the pseudo filesystem for the component.
65    export_dir: Arc<vfs::directory::immutable::Simple>,
66}
67
68// Wrapper type to add `FsInspect` support to an `OpenFxFilesystem`.
69struct InspectedFxFilesystem(OpenFxFilesystem, /*fs_id=*/ u64);
70
71impl From<OpenFxFilesystem> for InspectedFxFilesystem {
72    fn from(fs: OpenFxFilesystem) -> Self {
73        Self(fs, zx::Event::create().koid().unwrap().raw_koid())
74    }
75}
76
77impl Deref for InspectedFxFilesystem {
78    type Target = Arc<FxFilesystem>;
79    fn deref(&self) -> &Self::Target {
80        &self.0.deref()
81    }
82}
83
84#[async_trait]
85impl FsInspect for InspectedFxFilesystem {
86    fn get_info_data(&self) -> InfoData {
87        let earliest_version = self.0.super_block_header().earliest_version;
88        InfoData {
89            id: self.1,
90            fs_type: fidl_fuchsia_fs::VfsType::Fxfs.into_primitive().into(),
91            name: FXFS_INFO_NAME.into(),
92            version_major: LATEST_VERSION.major.into(),
93            version_minor: LATEST_VERSION.minor.into(),
94            block_size: self.0.block_size() as u64,
95            max_filename_length: fio::MAX_NAME_LENGTH,
96            oldest_version: Some(format!("{}.{}", earliest_version.major, earliest_version.minor)),
97        }
98    }
99
100    async fn get_usage_data(&self) -> UsageData {
101        let info = self.0.get_info();
102        UsageData {
103            total_bytes: info.total_bytes,
104            used_bytes: info.used_bytes,
105            // TODO(https://fxbug.dev/42175930): Should these be moved to per-volume nodes?
106            total_nodes: 0,
107            used_nodes: 0,
108        }
109    }
110}
111
112struct RunningState {
113    // We have to wrap this in an Arc, even though it itself basically just wraps an Arc, so that
114    // FsInspectTree can reference `fs` as a Weak<dyn FsInspect>`.
115    fs: Arc<InspectedFxFilesystem>,
116    volumes: Arc<VolumesDirectory>,
117    _inspect_tree: Arc<FsInspectTree>,
118}
119
120enum State {
121    ComponentStarted,
122    Running(RunningState),
123}
124
125impl State {
126    async fn stop(&mut self, outgoing_dir: &vfs::directory::immutable::Simple) {
127        if let State::Running(RunningState { fs, volumes, .. }) =
128            std::mem::replace(self, State::ComponentStarted)
129        {
130            info!("Stopping Fxfs runtime; remaining connections will be forcibly closed");
131            let _ = outgoing_dir.remove_entry("volumes", /* must_be_directory: */ false);
132            let _ = outgoing_dir.remove_entry("debug", /* must_be_directory: */ false);
133            volumes.terminate().await;
134            let _ = fs.deref().close().await;
135        }
136    }
137}
138
139impl Component {
140    pub fn new() -> Arc<Self> {
141        Arc::new(Self {
142            state: Mutex::new(State::ComponentStarted),
143            scope: ExecutionScope::new(),
144            export_dir: vfs::directory::immutable::simple(),
145        })
146    }
147
148    /// Runs Fxfs as a component.
149    pub async fn run(
150        self: Arc<Self>,
151        outgoing_dir: zx::Channel,
152        lifecycle_channel: Option<zx::Channel>,
153    ) -> Result<(), Error> {
154        let svc_dir = vfs::directory::immutable::simple();
155        self.export_dir.add_entry("svc", svc_dir.clone()).expect("Unable to create svc dir");
156
157        let weak = Arc::downgrade(&self);
158        svc_dir.add_entry(
159            StartupMarker::PROTOCOL_NAME,
160            vfs::service::host(move |requests| {
161                let weak = weak.clone();
162                async move {
163                    if let Some(me) = weak.upgrade() {
164                        let _ = me.handle_startup_requests(requests).await;
165                    }
166                }
167            }),
168        )?;
169        let weak = Arc::downgrade(&self);
170        svc_dir.add_entry(
171            VolumesMarker::PROTOCOL_NAME,
172            vfs::service::host(move |requests| {
173                let weak = weak.clone();
174                async move {
175                    if let Some(me) = weak.upgrade() {
176                        me.handle_volumes_requests(requests).await;
177                    }
178                }
179            }),
180        )?;
181
182        let weak = Arc::downgrade(&self);
183        svc_dir.add_entry(
184            AdminMarker::PROTOCOL_NAME,
185            vfs::service::host(move |requests| {
186                let weak = weak.clone();
187                async move {
188                    if let Some(me) = weak.upgrade() {
189                        let _ = me.handle_admin_requests(requests).await;
190                    }
191                }
192            }),
193        )?;
194
195        let weak = Arc::downgrade(&self);
196        svc_dir.add_entry(
197            VolumeInstallerMarker::PROTOCOL_NAME,
198            vfs::service::host(move |requests| {
199                let weak = weak.clone();
200                async move {
201                    if let Some(me) = weak.upgrade() {
202                        if let Err(error) = me.handle_volume_installer_requests(requests).await {
203                            error!(error:?; "failed to handle VolumeInstaller requests");
204                        }
205                    }
206                }
207            }),
208        )?;
209
210        // TODO(b/315704445): Only enable in debug builds?
211        let weak = Arc::downgrade(&self);
212        svc_dir.add_entry(
213            DebugMarker::PROTOCOL_NAME,
214            vfs::service::host(move |requests| {
215                let weak = weak.clone();
216                async move {
217                    if let Some(me) = weak.upgrade() {
218                        let _ = me.handle_debug_requests(requests).await;
219                    }
220                }
221            }),
222        )?;
223
224        vfs::directory::serve_on(
225            self.export_dir.clone(),
226            fio::PERM_READABLE | fio::PERM_WRITABLE | fio::PERM_EXECUTABLE,
227            self.scope.clone(),
228            fidl::endpoints::ServerEnd::new(outgoing_dir),
229        );
230
231        if let Some(channel) = lifecycle_channel {
232            let me = self.clone();
233            self.scope.spawn(async move {
234                if let Err(error) = me.handle_lifecycle_requests(channel).await {
235                    warn!(error:?; "handle_lifecycle_requests");
236                }
237            });
238        }
239
240        self.scope.wait().await;
241
242        Ok(())
243    }
244
245    async fn handle_startup_requests(&self, mut stream: StartupRequestStream) -> Result<(), Error> {
246        while let Some(request) = stream.try_next().await? {
247            match request {
248                StartupRequest::Start { responder, device, options } => {
249                    async move {
250                        responder.send(self.handle_start(device, options).await.map_err(|error| {
251                            error!(error:?; "handle_start failed");
252                            map_to_raw_status(error)
253                        }))
254                    }
255                    .trace(trace_future_args!("Startup::Start"))
256                    .await?
257                }
258                StartupRequest::Format { responder, device, .. } => {
259                    async move {
260                        responder.send(self.handle_format(device).await.map_err(|error| {
261                            error!(error:?; "handle_format failed");
262                            map_to_raw_status(error)
263                        }))
264                    }
265                    .trace(trace_future_args!("Startup::Format"))
266                    .await?
267                }
268                StartupRequest::Check { responder, device, options } => {
269                    async move {
270                        responder.send(self.handle_check(device, options).await.map_err(|error| {
271                            error!(error:?; "handle_check failed");
272                            map_to_raw_status(error)
273                        }))
274                    }
275                    .trace(trace_future_args!("Startup::Check"))
276                    .await?
277                }
278            }
279        }
280        Ok(())
281    }
282
283    async fn handle_start(
284        &self,
285        device: ClientEnd<BlockMarker>,
286        options: StartOptions,
287    ) -> Result<(), Error> {
288        info!(options:?; "Received start request");
289        let mut state = self.state.lock().await;
290        // TODO(https://fxbug.dev/42174810): This is not very graceful.  It would be better for the client to
291        // explicitly shut down all volumes first, and make this fail if there are remaining active
292        // connections.  Fix the bug in fs_test which requires this.
293        state.stop(&self.export_dir).await;
294        let client = new_block_client(device).await?;
295
296        // TODO(https://fxbug.dev/42063349) Add support for block sizes greater than the page size.
297        assert!(client.block_size() <= zx::system_get_page_size());
298        assert!((zx::system_get_page_size() as u64) == MIN_BLOCK_SIZE);
299
300        let fs = FxFilesystemBuilder::new()
301            .fsck_after_every_transaction(options.fsck_after_every_transaction.unwrap_or(false))
302            .read_only(options.read_only.unwrap_or(false))
303            .inline_crypto_enabled(options.inline_crypto_enabled.unwrap_or(false))
304            .barriers_enabled(options.barriers_enabled.unwrap_or(false))
305            .power_manager(FuchsiaPowerManager::new())
306            .open(DeviceHolder::new(
307                BlockDevice::new(client, options.read_only.unwrap_or(false)).await?,
308            ))
309            .await?;
310        let root_volume = root_volume(fs.clone()).await?;
311        let fs: Arc<InspectedFxFilesystem> = Arc::new(fs.into());
312        let weak_fs = Arc::downgrade(&fs) as Weak<dyn FsInspect + Send + Sync>;
313        let inspect_tree =
314            Arc::new(FsInspectTree::new(weak_fs, fuchsia_inspect::component::inspector().root()));
315
316        let blob_resupplied_count = Arc::new(PageRefaultCounter::new()?);
317        connect_to_protocol::<fattribution::PageRefaultSinkMarker>()?
318            .send_page_refault_count(blob_resupplied_count.readonly_vmo()?)?;
319
320        let mem_monitor = match MemoryPressureMonitor::start() {
321            Ok(v) => Some(v),
322            Err(error) => {
323                warn!(
324                    error:?;
325                    "Failed to connect to memory pressure monitor. Running \
326                     without pressure awareness."
327                );
328                None
329            }
330        };
331
332        let volumes = VolumesDirectory::new(
333            root_volume,
334            Arc::downgrade(&inspect_tree),
335            mem_monitor,
336            blob_resupplied_count,
337            MemoryPressureConfig::default(),
338        )
339        .await?;
340
341        self.export_dir.add_entry_may_overwrite(
342            "volumes",
343            volumes.directory_node().clone(),
344            /* overwrite: */ true,
345        )?;
346
347        let debug = create_debug_directory(&**fs, &volumes);
348        self.export_dir.add_entry_may_overwrite("debug", debug, true)?;
349
350        fs.allocator().track_statistics(&metrics::detail(), "allocator");
351        fs.journal().track_statistics(&metrics::detail(), "journal");
352        fs.object_manager().track_statistics(&metrics::detail(), "object_manager");
353
354        let info = fs.get_info();
355        info!(
356            device_size = info.total_bytes,
357            used = info.used_bytes,
358            free = info.total_bytes - info.used_bytes;
359            "Mounted"
360        );
361
362        if let Some(profile_time) = options.startup_profiling_seconds {
363            // Unwrap ok, shouldn't have anything else recording or replaying this early in startup.
364            volumes
365                .record_and_replay_profile(None, ".boot".to_owned(), profile_time)
366                .await
367                .unwrap();
368        }
369
370        *state = State::Running(RunningState { fs, volumes, _inspect_tree: inspect_tree });
371
372        Ok(())
373    }
374
375    async fn handle_format(&self, device: ClientEnd<BlockMarker>) -> Result<(), Error> {
376        let device = DeviceHolder::new(
377            BlockDevice::new(new_block_client(device).await?, /* read_only: */ false).await?,
378        );
379        mkfs(device).await?;
380        info!("Formatted filesystem");
381        Ok(())
382    }
383
384    async fn handle_check(
385        &self,
386        device: ClientEnd<BlockMarker>,
387        _options: CheckOptions,
388    ) -> Result<(), Error> {
389        let state = self.state.lock().await;
390        let (fs_container, fs) = match *state {
391            State::ComponentStarted => {
392                let client = new_block_client(device).await?;
393                let fs_container = FxFilesystemBuilder::new()
394                    .read_only(true)
395                    .open(DeviceHolder::new(BlockDevice::new(client, /* read_only: */ true).await?))
396                    .await?;
397                let fs = fs_container.clone();
398                (Some(fs_container), fs)
399            }
400            State::Running(RunningState { ref fs, .. }) => (None, fs.deref().deref().clone()),
401        };
402        let res = fsck::fsck(fs.clone()).await;
403        if let Some(fs_container) = fs_container {
404            let _ = fs_container.close().await;
405        }
406        // TODO(b/311550633): Stash ok res in inspect.
407        info!("handle_check for fs: {:?}", res?);
408        Ok(())
409    }
410
411    async fn handle_admin_requests(&self, mut stream: AdminRequestStream) -> Result<(), Error> {
412        while let Some(request) = stream.try_next().await.context("Reading request")? {
413            if self.handle_admin(request).await? {
414                break;
415            }
416        }
417        Ok(())
418    }
419
420    // Returns true if we should close the connection.
421    async fn handle_admin(&self, req: AdminRequest) -> Result<bool, Error> {
422        match req {
423            AdminRequest::Shutdown { responder } => {
424                async move {
425                    info!("Received shutdown request");
426                    self.shutdown().await;
427                    responder
428                        .send()
429                        .unwrap_or_else(|error| warn!(error:?; "Failed to send shutdown response"));
430                }
431                .trace(trace_future_args!("Admin::Shutdown"))
432                .await;
433                return Ok(true);
434            }
435        }
436    }
437
438    /// Handles fuchsia.fxfs.Debug requests, providing live debugging internals of the running
439    /// filesystem.
440    async fn handle_debug_requests(&self, mut stream: DebugRequestStream) -> Result<(), Error> {
441        while let Some(request) = stream.try_next().await.context("Reading request")? {
442            let state = self.state.lock().await;
443            let (fs, volumes) = match &*state {
444                State::ComponentStarted => {
445                    info!("Debug commands are not valid unless component is started.");
446                    bail!("Component not started");
447                }
448                State::Running(RunningState { fs, volumes, .. }) => {
449                    (fs.deref().deref().clone(), volumes.clone())
450                }
451            };
452            handle_debug_request(fs, volumes, request).await?;
453        }
454        Ok(())
455    }
456
457    async fn shutdown(&self) {
458        self.state.lock().await.stop(&self.export_dir).await;
459        info!("Filesystem terminated");
460    }
461
462    async fn handle_volumes_requests(&self, mut stream: VolumesRequestStream) {
463        let volumes =
464            if let State::Running(RunningState { volumes, .. }) = &*self.state.lock().await {
465                volumes.clone()
466            } else {
467                let _ = stream.into_inner().0.shutdown_with_epitaph(zx::Status::BAD_STATE);
468                return;
469            };
470        while let Ok(Some(request)) = stream.try_next().await {
471            match request {
472                VolumesRequest::Create {
473                    name,
474                    outgoing_directory,
475                    create_options,
476                    mount_options,
477                    responder,
478                } => {
479                    async {
480                        info!(
481                            name = name.as_str();
482                            "Create {}volume",
483                            if mount_options.crypt.is_some() { "encrypted " } else { "" }
484                        );
485                        responder
486                            .send(
487                                volumes
488                                    .create_and_serve_volume(
489                                        &name,
490                                        outgoing_directory.into_channel().into(),
491                                        mount_options,
492                                        create_options,
493                                    )
494                                    .await
495                                    .map_err(map_to_raw_status),
496                            )
497                            .unwrap_or_else(
498                                |error| warn!(error:?; "Failed to send volume creation response"),
499                            );
500                    }
501                    .trace(trace_future_args!("Volumes::Create"))
502                    .await;
503                }
504                VolumesRequest::Remove { name, responder } => {
505                    async {
506                        info!(name = name.as_str(); "Remove volume");
507                        responder
508                            .send(volumes.remove_volume(&name).await.map_err(map_to_raw_status))
509                            .unwrap_or_else(
510                                |error| warn!(error:?; "Failed to send volume removal response"),
511                            );
512                    }
513                    .trace(trace_future_args!("Volumes::Remove"))
514                    .await;
515                }
516                VolumesRequest::GetInfo { responder } => {
517                    async move {
518                        responder.send(Err(zx::Status::NOT_SUPPORTED.into_raw())).unwrap_or_else(
519                            |error| warn!(error:?; "Failed to send volume removal response"),
520                        )
521                    }
522                    .trace(trace_future_args!("Volumes::GetInfo"))
523                    .await;
524                }
525            }
526        }
527    }
528
529    async fn handle_lifecycle_requests(&self, lifecycle_channel: zx::Channel) -> Result<(), Error> {
530        let mut stream =
531            LifecycleRequestStream::from_channel(fasync::Channel::from_channel(lifecycle_channel));
532        match stream.try_next().await.context("Reading request")? {
533            Some(LifecycleRequest::Stop { .. }) => {
534                info!("Received Lifecycle::Stop request");
535                self.shutdown().await;
536            }
537            None => {}
538        }
539        Ok(())
540    }
541
542    async fn handle_volume_installer_requests(
543        &self,
544        mut stream: VolumeInstallerRequestStream,
545    ) -> Result<(), Error> {
546        let volumes =
547            if let State::Running(RunningState { volumes, .. }) = &*self.state.lock().await {
548                volumes.clone()
549            } else {
550                let _ = stream.into_inner().0.shutdown_with_epitaph(zx::Status::BAD_STATE);
551                return Err(zx::Status::BAD_STATE).context("fxfs component not running");
552            };
553        while let Some(request) = stream.try_next().await.context("reading request")? {
554            match request {
555                VolumeInstallerRequest::Install { src, image_file, dst, responder } => {
556                    async {
557                        let response = volumes.install_volume(&src, &image_file, &dst).await;
558                        responder.send(response.map_err(|error| {
559                            error!(error:?; "install failed");
560                            map_to_raw_status(error)
561                        }))
562                    }
563                    .trace(trace_future_args!("VolumeInstaller::Install"))
564                    .await?;
565                }
566            }
567        }
568        Ok(())
569    }
570}
571
572#[cfg(test)]
573mod tests {
574    use super::{Component, new_block_client};
575    use fidl::endpoints::Proxy;
576    use fidl_fuchsia_fs::AdminMarker;
577    use fidl_fuchsia_fs_startup::{
578        CreateOptions, MountOptions, StartOptions, StartupMarker, VolumesMarker,
579    };
580    use fidl_fuchsia_io as fio;
581    use fidl_fuchsia_process_lifecycle::{LifecycleMarker, LifecycleProxy};
582    use fuchsia_async as fasync;
583    use fuchsia_component_client::connect_to_protocol_at_dir_svc;
584    use fuchsia_fs::directory::readdir;
585    use futures::future::{BoxFuture, FusedFuture, FutureExt};
586    use futures::{pin_mut, select};
587    use fxfs::filesystem::FxFilesystem;
588    use fxfs::object_store::volume::root_volume;
589    use fxfs::object_store::{NewChildStoreOptions, StoreOptions};
590    use std::pin::Pin;
591    use std::sync::Arc;
592    use storage_device::DeviceHolder;
593    use storage_device::block_device::BlockDevice;
594    use test_vmo_backed_block_server::VmoBackedServer;
595
596    async fn run_test(
597        callback: impl Fn(&fio::DirectoryProxy, LifecycleProxy) -> BoxFuture<'static, ()>,
598    ) -> (Pin<Box<impl FusedFuture>>, Arc<VmoBackedServer>) {
599        const BLOCK_SIZE: u32 = 512;
600        let block_server = Arc::new(
601            VmoBackedServer::new(16384, BLOCK_SIZE, &[]).expect("Failed to create VmoBackedServer"),
602        );
603
604        {
605            let fs = FxFilesystem::new_empty(DeviceHolder::new(
606                BlockDevice::new(
607                    new_block_client(block_server.connect())
608                        .await
609                        .expect("Unable to create block client"),
610                    false,
611                )
612                .await
613                .unwrap(),
614            ))
615            .await
616            .expect("FxFilesystem::new_empty failed");
617            {
618                let root_volume = root_volume(fs.clone()).await.expect("Open root_volume failed");
619                root_volume
620                    .new_volume("default", NewChildStoreOptions::default())
621                    .await
622                    .expect("Create volume failed");
623            }
624            fs.close().await.expect("close failed");
625        }
626
627        let (client_end, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
628
629        let (lifecycle_client, lifecycle_server) =
630            fidl::endpoints::create_proxy::<LifecycleMarker>();
631
632        let mut component_task = Box::pin(
633            async {
634                Component::new()
635                    .run(server_end.into_channel(), Some(lifecycle_server.into_channel()))
636                    .await
637                    .expect("Failed to run component");
638            }
639            .fuse(),
640        );
641
642        let startup_proxy = connect_to_protocol_at_dir_svc::<StartupMarker>(&client_end)
643            .expect("Unable to connect to Startup protocol");
644        let block_server_connection = block_server.connect();
645        let task = async {
646            startup_proxy
647                .start(block_server_connection, &StartOptions::default())
648                .await
649                .expect("Start failed (FIDL)")
650                .expect("Start failed");
651            callback(&client_end, lifecycle_client).await;
652        }
653        .fuse();
654
655        pin_mut!(task);
656
657        loop {
658            select! {
659                () = component_task => {},
660                () = task => break,
661            }
662        }
663
664        (component_task, block_server)
665    }
666
667    #[fuchsia::test(threads = 2)]
668    async fn test_shutdown() {
669        let (component_task, _block_server) = run_test(|client, _| {
670            let admin_proxy = connect_to_protocol_at_dir_svc::<AdminMarker>(client)
671                .expect("Unable to connect to Admin protocol");
672            async move {
673                admin_proxy.shutdown().await.expect("shutdown failed");
674            }
675            .boxed()
676        })
677        .await;
678        assert!(!component_task.is_terminated());
679    }
680
681    #[fuchsia::test(threads = 2)]
682    async fn test_lifecycle_stop() {
683        let (component_task, _block_server) = run_test(|_, lifecycle_client| {
684            lifecycle_client.stop().expect("Stop failed");
685            async move {
686                fasync::OnSignals::new(
687                    &lifecycle_client.into_channel().expect("into_channel failed"),
688                    zx::Signals::CHANNEL_PEER_CLOSED,
689                )
690                .await
691                .expect("OnSignals failed");
692            }
693            .boxed()
694        })
695        .await;
696        component_task.await;
697    }
698
699    #[fuchsia::test(threads = 2)]
700    async fn test_create_and_remove() {
701        let (component_task, _block_server) = run_test(|client, _| {
702            let volumes_proxy = connect_to_protocol_at_dir_svc::<VolumesMarker>(client)
703                .expect("Unable to connect to Volumes protocol");
704
705            let fs_admin_proxy = connect_to_protocol_at_dir_svc::<AdminMarker>(client)
706                .expect("Unable to connect to Admin protocol");
707
708            async move {
709                let (dir_proxy, server_end) =
710                    fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
711                volumes_proxy
712                    .create("test", server_end, CreateOptions::default(), MountOptions::default())
713                    .await
714                    .expect("fidl failed")
715                    .expect("create failed");
716
717                // This should fail whilst the volume is mounted.
718                volumes_proxy
719                    .remove("test")
720                    .await
721                    .expect("fidl failed")
722                    .expect_err("remove succeeded");
723
724                let volume_admin_proxy = connect_to_protocol_at_dir_svc::<AdminMarker>(&dir_proxy)
725                    .expect("Unable to connect to Admin protocol");
726                volume_admin_proxy.shutdown().await.expect("shutdown failed");
727
728                // Creating another volume with the same name should fail.
729                let (_dir_proxy, server_end) =
730                    fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
731                volumes_proxy
732                    .create("test", server_end, CreateOptions::default(), MountOptions::default())
733                    .await
734                    .expect("fidl failed")
735                    .expect_err("create succeeded");
736
737                volumes_proxy.remove("test").await.expect("fidl failed").expect("remove failed");
738
739                // Removing a non-existent volume should fail.
740                volumes_proxy
741                    .remove("test")
742                    .await
743                    .expect("fidl failed")
744                    .expect_err("remove failed");
745
746                // Create the same volume again and it should now succeed.
747                let (_dir_proxy, server_end) =
748                    fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
749                volumes_proxy
750                    .create("test", server_end, CreateOptions::default(), MountOptions::default())
751                    .await
752                    .expect("fidl failed")
753                    .expect("create failed");
754
755                fs_admin_proxy.shutdown().await.expect("shutdown failed");
756            }
757            .boxed()
758        })
759        .await;
760        component_task.await;
761    }
762
763    #[fuchsia::test(threads = 2)]
764    async fn test_volumes_enumeration() {
765        let (component_task, _block_server) = run_test(|client, _| {
766            let volumes_proxy = connect_to_protocol_at_dir_svc::<VolumesMarker>(client)
767                .expect("Unable to connect to Volumes protocol");
768
769            let (volumes_dir_proxy, server_end) =
770                fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
771            client
772                .open("volumes", fio::PERM_READABLE, &Default::default(), server_end.into_channel())
773                .expect("open failed");
774
775            let fs_admin_proxy = connect_to_protocol_at_dir_svc::<AdminMarker>(client)
776                .expect("Unable to connect to Admin protocol");
777
778            async move {
779                let (_dir_proxy, server_end) =
780                    fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
781                volumes_proxy
782                    .create("test", server_end, CreateOptions::default(), MountOptions::default())
783                    .await
784                    .expect("fidl failed")
785                    .expect("create failed");
786
787                let entries = readdir(&volumes_dir_proxy).await.expect("readdir failed");
788                let mut entry_names = entries.iter().map(|d| d.name.as_str()).collect::<Vec<_>>();
789                entry_names.sort();
790                assert_eq!(entry_names, ["default", "test"]);
791
792                fs_admin_proxy.shutdown().await.expect("shutdown failed");
793            }
794            .boxed()
795        })
796        .await;
797        component_task.await;
798    }
799
800    #[fuchsia::test(threads = 2)]
801    async fn test_create_with_guid() {
802        let (component_task, block_server) = run_test(|client, _| {
803            let volumes_proxy = connect_to_protocol_at_dir_svc::<VolumesMarker>(client)
804                .expect("Unable to connect to Volumes protocol");
805            let admin_proxy = connect_to_protocol_at_dir_svc::<AdminMarker>(client)
806                .expect("Unable to connect to Admin protocol");
807            async move {
808                let (_dir_proxy, server_end) =
809                    fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
810                let guid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
811                volumes_proxy
812                    .create(
813                        "test_guid",
814                        server_end,
815                        CreateOptions { guid: Some(guid), ..Default::default() },
816                        MountOptions::default(),
817                    )
818                    .await
819                    .expect("fidl failed")
820                    .expect("create failed");
821                admin_proxy.shutdown().await.expect("shutdown failed");
822            }
823            .boxed()
824        })
825        .await;
826        component_task.await;
827
828        // Verify the GUID
829        let fs = FxFilesystem::open(DeviceHolder::new(
830            BlockDevice::new(
831                new_block_client(block_server.connect())
832                    .await
833                    .expect("Unable to create block client"),
834                false,
835            )
836            .await
837            .unwrap(),
838        ))
839        .await
840        .expect("FxFilesystem::open failed");
841        {
842            let root_volume = root_volume(fs.clone()).await.expect("Open root_volume failed");
843            let vol = root_volume
844                .volume("test_guid", StoreOptions::default())
845                .await
846                .expect("Open volume failed");
847            assert_eq!(vol.guid(), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
848        }
849        fs.close().await.expect("close failed");
850    }
851}