block_adapter/
lib.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
5// This launches the binary specified in the arguments and takes the block device that is passed via
6// the usual startup handle and makes it appear to the child process as an object that will work
7// with POSIX I/O.  The object will exist in the child's namespace under /device/block.  At the time
8// of writing, this is used to run the fsck-msdosfs and mkfs-msdosfs tools which use POSIX I/O to
9// interact with block devices.
10
11use anyhow::Error;
12use block_client::{BlockClient as _, BufferSlice, MutableBufferSlice, RemoteBlockClient};
13use fidl::endpoints::create_endpoints;
14use std::sync::Arc;
15use vfs::directory::entry::{DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest};
16use vfs::execution_scope::ExecutionScope;
17use vfs::file::{FidlIoConnection, File, FileIo, FileLike, FileOptions, SyncMode};
18use vfs::node::Node;
19use vfs::{ObjectRequestRef, immutable_attributes, pseudo_directory};
20use {fidl_fuchsia_io as fio, fidl_fuchsia_storage_block as fblock, fuchsia_async as fasync};
21
22struct BlockFile {
23    block_client: RemoteBlockClient,
24}
25
26impl DirectoryEntry for BlockFile {
27    fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
28        request.open_file(self)
29    }
30}
31
32impl GetEntryInfo for BlockFile {
33    fn entry_info(&self) -> EntryInfo {
34        EntryInfo::new(0, fio::DirentType::File)
35    }
36}
37
38impl Node for BlockFile {
39    async fn get_attributes(
40        &self,
41        requested_attributes: fio::NodeAttributesQuery,
42    ) -> Result<fio::NodeAttributes2, zx::Status> {
43        let block_size = self.block_client.block_size();
44        let block_count = self.block_client.block_count();
45        let device_size = block_count.checked_mul(block_size.into()).unwrap();
46        Ok(immutable_attributes!(
47            requested_attributes,
48            Immutable {
49                protocols: fio::NodeProtocolKinds::FILE,
50                abilities: fio::Operations::GET_ATTRIBUTES
51                    | fio::Operations::UPDATE_ATTRIBUTES
52                    | fio::Operations::READ_BYTES
53                    | fio::Operations::WRITE_BYTES,
54                content_size: device_size,
55                storage_size: device_size,
56            }
57        ))
58    }
59}
60
61impl File for BlockFile {
62    fn writable(&self) -> bool {
63        true
64    }
65
66    async fn open_file(&self, _options: &FileOptions) -> Result<(), zx::Status> {
67        Ok(())
68    }
69
70    async fn truncate(&self, _length: u64) -> Result<(), zx::Status> {
71        Err(zx::Status::NOT_SUPPORTED)
72    }
73
74    async fn get_size(&self) -> Result<u64, zx::Status> {
75        let block_size = self.block_client.block_size();
76        let block_count = self.block_client.block_count();
77        Ok(block_count.checked_mul(block_size.into()).unwrap())
78    }
79
80    async fn update_attributes(
81        &self,
82        _attributes: fio::MutableNodeAttributes,
83    ) -> Result<(), zx::Status> {
84        Err(zx::Status::NOT_SUPPORTED)
85    }
86
87    async fn sync(&self, _mode: SyncMode) -> Result<(), zx::Status> {
88        self.block_client.flush().await
89    }
90}
91
92impl FileIo for BlockFile {
93    async fn read_at(&self, offset: u64, buffer: &mut [u8]) -> Result<u64, zx::Status> {
94        let () = self.block_client.read_at(MutableBufferSlice::Memory(buffer), offset).await?;
95        Ok(buffer.len().try_into().unwrap())
96    }
97
98    async fn write_at(&self, offset: u64, content: &[u8]) -> Result<u64, zx::Status> {
99        let () = self.block_client.write_at(BufferSlice::Memory(content), offset).await?;
100        Ok(content.len().try_into().unwrap())
101    }
102
103    async fn append(&self, _content: &[u8]) -> Result<(u64, u64), zx::Status> {
104        Err(zx::Status::NOT_SUPPORTED)
105    }
106}
107
108impl FileLike for BlockFile {
109    fn open(
110        self: Arc<Self>,
111        scope: ExecutionScope,
112        options: FileOptions,
113        object_request: ObjectRequestRef<'_>,
114    ) -> Result<(), zx::Status> {
115        FidlIoConnection::create_sync(scope, self, options, object_request.take());
116        Ok(())
117    }
118}
119
120// This launches the binary specified in the arguments and takes a block device and makes it appear
121// to the child process as an object that will work with POSIX I/O.  The object will exist in the
122// child's namespace under /device/block.  At the time of writing, this is used to run the
123// fsck-msdosfs and mkfs-msdosfs tools which use POSIX I/O to interact with block devices.
124pub async fn run(
125    block_proxy: fblock::BlockProxy,
126    binary: &str,
127    args: impl Iterator<Item = String>,
128) -> Result<i64, Error> {
129    let (client, server) = create_endpoints::<fio::DirectoryMarker>();
130
131    let server_fut = {
132        let block_client = RemoteBlockClient::new(block_proxy).await?;
133        let dir = pseudo_directory! {
134            "block" => Arc::new(BlockFile {
135                block_client,
136            }),
137        };
138
139        let scope = ExecutionScope::new();
140        vfs::directory::serve_on(
141            dir,
142            fio::PERM_READABLE | fio::PERM_WRITABLE,
143            scope.clone(),
144            server,
145        );
146        async move { scope.wait().await }
147    };
148
149    let client_fut = {
150        let mut builder = fdio::SpawnBuilder::new()
151            .options(fdio::SpawnOptions::CLONE_ALL)
152            .add_directory_to_namespace("/device", client)?
153            .arg(binary)?;
154        for arg in args {
155            builder = builder.arg(arg)?;
156        }
157        builder = builder.arg("/device/block")?;
158        let process = builder.spawn_from_path(binary, &fuchsia_runtime::job_default())?;
159
160        async move {
161            let _: zx::Signals =
162                fasync::OnSignals::new(&process, zx::Signals::PROCESS_TERMINATED).await?;
163            let info = process.info()?;
164            Ok::<_, Error>(info.return_code)
165        }
166    };
167
168    futures::future::join(server_fut, client_fut).await.1
169}