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