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