1use crate::fs::fuchsia::RemoteFs;
6use crate::task::dynamic_thread_spawner::SpawnRequestBuilder;
7use crate::task::{CurrentTask, LockedAndTask};
8use crate::vfs::{
9 CacheMode, FileSystem, FileSystemHandle, FileSystemOps, FileSystemOptions, FsNodeHandle, FsStr,
10};
11use fidl::endpoints::{DiscoverableProtocolMarker, SynchronousProxy, create_sync_proxy};
12use fidl_fuchsia_fshost::StarnixVolumeProviderMarker;
13use fidl_fuchsia_fxfs::CryptMarker;
14use fidl_fuchsia_hardware_inlineencryption::DeviceMarker as InlineEncryptionDeviceMarker;
15use fidl_fuchsia_io as fio;
16use starnix_crypt::CryptService;
17use starnix_logging::{Level, log, log_error};
18use starnix_sync::{FileOpsCore, Locked, Unlocked};
19use starnix_uapi::errors::Errno;
20use starnix_uapi::{errno, from_status_like_fdio, statfs};
21use std::sync::Arc;
22use thiserror::Error;
23
24const CRYPT_THREAD_ROLE: &str = "fuchsia.starnix.remotevol.crypt";
25const KEY_FILE_PATH: &str = "key_file";
28
29pub struct RemoteVolume {
30 remotefs: RemoteFs,
31 exposed_dir_proxy: fio::DirectorySynchronousProxy,
32 crypt_service: Arc<CryptService>,
33}
34
35impl RemoteVolume {
36 pub fn remotefs(&self) -> &RemoteFs {
37 &self.remotefs
38 }
39}
40
41impl FileSystemOps for RemoteVolume {
42 fn statfs(
43 &self,
44 locked: &mut Locked<FileOpsCore>,
45 fs: &FileSystem,
46 current_task: &CurrentTask,
47 ) -> Result<statfs, Errno> {
48 self.remotefs.statfs(locked, fs, current_task)
49 }
50
51 fn name(&self) -> &'static FsStr {
52 "remotevol".into()
53 }
54
55 fn uses_external_node_ids(&self) -> bool {
56 self.remotefs.uses_external_node_ids()
57 }
58
59 fn rename(
60 &self,
61 locked: &mut Locked<FileOpsCore>,
62 fs: &FileSystem,
63 current_task: &CurrentTask,
64 old_parent: &FsNodeHandle,
65 old_name: &FsStr,
66 new_parent: &FsNodeHandle,
67 new_name: &FsStr,
68 renamed: &FsNodeHandle,
69 replaced: Option<&FsNodeHandle>,
70 ) -> Result<(), Errno> {
71 self.remotefs.rename(
72 locked,
73 fs,
74 current_task,
75 old_parent,
76 old_name,
77 new_parent,
78 new_name,
79 renamed,
80 replaced,
81 )
82 }
83
84 fn unmount(&self) {
85 let (proxy, server_end) = create_sync_proxy::<fidl_fuchsia_fs::AdminMarker>();
86 if let Err(e) = fdio::service_connect_at(
87 self.exposed_dir_proxy.as_channel(),
88 &format!("svc/{}", fidl_fuchsia_fs::AdminMarker::PROTOCOL_NAME),
89 server_end.into(),
90 ) {
91 log_error!(e:%; "StarnixVolumeProvider.Unmount failed to connect to fuchsia.fs.Admin");
92 return;
93 }
94
95 if let Err(e) = proxy.shutdown(zx::MonotonicInstant::INFINITE) {
96 log_error!(e:%; "StarnixVolumeProvider.Unmount failed at FIDL layer");
97 }
98 }
99
100 fn crypt_service(&self) -> Option<Arc<CryptService>> {
101 Some(self.crypt_service.clone())
102 }
103}
104
105struct VolumeKeys {
119 metadata: [u8; 32],
120 data: [u8; 32],
121}
122
123#[derive(Error, Debug, Eq, PartialEq)]
124enum KeyFileError {
125 #[error("key file not found")]
126 NotFound,
127 #[error("failed to read key file")]
128 ReadError(#[from] zx::Status),
129 #[error("unsupported key file version")]
130 UnsupportedVersion,
131 #[error("unexpected content")]
132 UnexpectedContent,
133}
134
135impl VolumeKeys {
136 const KEYS_SIZE: usize = 64;
139
140 const FILE_SIZE: usize = 2 + Self::KEYS_SIZE;
142
143 const LATEST_VERSION: u16 = 2;
144
145 fn get_or_create(
147 data: &fio::DirectorySynchronousProxy,
148 key_path: &str,
149 ) -> Result<(Self, bool), Errno> {
150 match Self::get(data, key_path) {
151 Ok(keys) => Ok((keys, false)),
152 Err(KeyFileError::ReadError(status)) => {
153 Err(from_status_like_fdio!(status))
157 }
158 Err(e) => {
159 log!(
160 if e == KeyFileError::NotFound { Level::Info } else { Level::Warn },
161 "Creating key file at {key_path} (reason={e:?}) which will \
162 cause existing data to be *wiped* if it exists."
163 );
164 Ok((Self::create(data, key_path)?, true))
165 }
166 }
167 }
168
169 fn get(data: &fio::DirectorySynchronousProxy, key_path: &str) -> Result<Self, KeyFileError> {
172 match syncio::directory_read_file(data, key_path, zx::MonotonicInstant::INFINITE) {
173 Ok(bytes) => {
174 if bytes.len() == Self::FILE_SIZE {
175 if u16::from_le_bytes(bytes[0..2].try_into().unwrap()) != Self::LATEST_VERSION {
176 Err(KeyFileError::UnsupportedVersion)
177 } else {
178 Ok(Self {
179 metadata: bytes[2..34].try_into().unwrap(),
180 data: bytes[34..66].try_into().unwrap(),
181 })
182 }
183 } else {
184 Err(KeyFileError::UnexpectedContent)
185 }
186 }
187 Err(zx::Status::NOT_FOUND) => {
188 Err(KeyFileError::NotFound)
190 }
191 Err(status) => {
192 log_error!(status:?; "Failed to read key file");
193 Err(status.into())
194 }
195 }
196 }
197
198 fn create(data: &fio::DirectorySynchronousProxy, key_path: &str) -> Result<Self, Errno> {
200 let mut bytes = [0; Self::FILE_SIZE];
201 bytes[..2].copy_from_slice(&Self::LATEST_VERSION.to_le_bytes());
202 zx::cprng_draw(&mut bytes[2..]);
203 let tmp_file = syncio::directory_create_tmp_file(
204 data,
205 fio::PERM_READABLE,
206 zx::MonotonicInstant::INFINITE,
207 )
208 .map_err(|e| {
209 let err = from_status_like_fdio!(e);
210 log_error!("Failed to create tmp file with error: {:?}", err);
211 err
212 })?;
213 tmp_file
214 .write(&bytes, zx::MonotonicInstant::INFINITE)
215 .map_err(|e| {
216 log_error!("FIDL transport error on File.Write {:?}", e);
217 errno!(ENOENT)
218 })?
219 .map_err(|e| {
220 let err = from_status_like_fdio!(zx::Status::from_raw(e));
221 log_error!("File.Write failed with {:?}", err);
222 err
223 })?;
224 tmp_file
225 .sync(zx::MonotonicInstant::INFINITE)
226 .map_err(|e| {
227 log_error!("FIDL transport error on File.Sync {:?}", e);
228 errno!(ENOENT)
229 })?
230 .map_err(|e| {
231 let err = from_status_like_fdio!(zx::Status::from_raw(e));
232 log_error!("File.Sync failed with {:?}", err);
233 err
234 })?;
235 let (status, token) = data.get_token(zx::MonotonicInstant::INFINITE).map_err(|e| {
236 log_error!("transport error on get_token for the data directory, error: {:?}", e);
237 errno!(ENOENT)
238 })?;
239 zx::Status::ok(status).map_err(|e| {
240 let err = from_status_like_fdio!(e);
241 log_error!("Failed to get_token for the data directory, error: {:?}", err);
242 err
243 })?;
244
245 tmp_file
246 .link_into(
247 zx::Event::from(token.ok_or_else(|| errno!(ENOENT))?),
248 key_path,
249 zx::MonotonicInstant::INFINITE,
250 )
251 .map_err(|e| {
252 log_error!("FIDL transport error on File.LinkInto {:?}", e);
253 errno!(EIO)
254 })?
255 .map_err(|e| {
256 let err = from_status_like_fdio!(zx::Status::from_raw(e));
257 log_error!("File.LinkInto failed with {:?}", err);
258 err
259 })?;
260 Ok(Self {
261 metadata: bytes[2..34].try_into().unwrap(),
262 data: bytes[34..].try_into().unwrap(),
263 })
264 }
265}
266
267pub fn new_remote_vol(
268 locked: &mut Locked<Unlocked>,
269 current_task: &CurrentTask,
270 options: FileSystemOptions,
271) -> Result<FileSystemHandle, Errno> {
272 let kernel = current_task.kernel();
273 let volume_provider = current_task
275 .kernel()
276 .connect_to_protocol_at_container_svc::<StarnixVolumeProviderMarker>()
277 .map_err(|_| errno!(ENOENT))?
278 .into_sync_proxy();
279
280 let (crypt_client_end, crypt_proxy) = fidl::endpoints::create_endpoints::<CryptMarker>();
281
282 let key_location = match options.params.get(FsStr::new(b"keylocation")) {
283 Some(path) => str::from_utf8(path.as_bytes()).map_err(|_| errno!(EINVAL))?,
284 None => {
285 log_error!(
287 "TODO(b/460156877): Starnix is unable to mount remote volumes without encryption. \
288 Encrypted volumes should specify a keylocation in the mount flags."
289 );
290 return Err(errno!(EINVAL));
291 }
292 };
293
294 let open_flags =
295 fio::PERM_READABLE | fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::PROTOCOL_DIRECTORY;
296 let (root, subdir) = kernel.open_ns_dir(key_location, open_flags)?;
297
298 let open_rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
299 let subdir = if subdir.is_empty() { ".".to_string() } else { subdir };
300 let key_location_proxy = syncio::directory_open_directory_async(&root, &subdir, open_rights)
301 .map_err(|e| errno!(EIO, format!("Failed to open proxy for keylocation: {e}")))?;
302
303 let (keys, created_key_file) = VolumeKeys::get_or_create(&key_location_proxy, KEY_FILE_PATH)?;
304
305 let inline_encryption_provider = if options.params.get(FsStr::new(b"inlinecrypt")).is_some() {
307 match current_task
308 .kernel()
309 .connect_to_protocol_at_container_svc::<InlineEncryptionDeviceMarker>()
310 {
311 Ok(client_end) => Some(client_end.into_sync_proxy()),
312 Err(error) => {
313 log_error!(error:?; "Error connecting to inline encryption device");
314 return Err(error);
315 }
316 }
317 } else {
318 None
319 };
320 let crypt_service =
321 Arc::new(CryptService::new(&keys.metadata, &keys.data, inline_encryption_provider));
322
323 let (exposed_dir_client_end, exposed_dir_server) =
324 fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
325
326 {
327 let crypt_service = Arc::clone(&crypt_service);
328 let closure = async move |_: LockedAndTask<'_>| {
329 if let Err(e) = crypt_service.handle_connection(crypt_proxy.into_stream()).await {
330 log_error!("Error while handling a Crypt request {e}");
331 }
332 };
333 let req = SpawnRequestBuilder::new()
334 .with_debug_name("remote-volume-crypt")
335 .with_role(CRYPT_THREAD_ROLE)
336 .with_async_closure(closure)
337 .build();
338 kernel.kthreads.spawner().spawn_from_request(req);
339 }
340
341 let mode = if created_key_file {
342 fidl_fuchsia_fshost::MountMode::AlwaysCreate
343 } else {
344 fidl_fuchsia_fshost::MountMode::MaybeCreate
345 };
346 let guid = volume_provider
347 .mount(crypt_client_end, mode, exposed_dir_server, zx::MonotonicInstant::INFINITE)
348 .map_err(|e| {
349 log_error!("FIDL transport error on StarnixVolumeProvider.Mount {:?}", e);
350 errno!(ENOENT)
351 })?
352 .map_err(|e| {
353 let error = from_status_like_fdio!(zx::Status::from_raw(e));
354 log_error!(error:?; "StarnixVolumeProvider.Mount failed");
355 error
356 })?;
357
358 crypt_service.set_uuid(guid);
359
360 let exposed_dir_proxy = exposed_dir_client_end.into_sync_proxy();
361
362 let root = syncio::directory_open_directory_async(
363 &exposed_dir_proxy,
364 "root",
365 fio::PERM_READABLE | fio::PERM_WRITABLE,
366 )
367 .map_err(|e| errno!(EIO, format!("Failed to open root: {e}")))?;
368
369 let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
370
371 let (remotefs, root_node, info, node_id) = RemoteFs::new(root.into_channel(), rights)?;
372
373 let use_remote_ids = remotefs.use_remote_ids();
374 let remotevol = RemoteVolume { remotefs, exposed_dir_proxy, crypt_service };
375 let fs = FileSystem::new(
376 locked,
377 kernel,
378 CacheMode::Cached(kernel.fs_cache_config()),
379 remotevol,
380 options,
381 )?;
382
383 if use_remote_ids {
384 fs.create_root_with_info(node_id, root_node, info);
385 } else {
386 let root_ino = fs.allocate_ino();
387 fs.create_root_with_info(root_ino, root_node, info);
388 }
389
390 Ok(fs)
391}