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, FsStr, RenameContext,
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 context: &mut RenameContext<'_>,
65 old_name: &FsStr,
66 new_name: &FsStr,
67 ) -> Result<(), Errno> {
68 self.remotefs.rename(locked, fs, current_task, context, old_name, new_name)
69 }
70
71 fn unmount(&self) {
72 let (proxy, server_end) = create_sync_proxy::<fidl_fuchsia_fs::AdminMarker>();
73 if let Err(e) = fdio::service_connect_at(
74 self.exposed_dir_proxy.as_channel(),
75 &format!("svc/{}", fidl_fuchsia_fs::AdminMarker::PROTOCOL_NAME),
76 server_end.into(),
77 ) {
78 log_error!(e:%; "StarnixVolumeProvider.Unmount failed to connect to fuchsia.fs.Admin");
79 return;
80 }
81
82 if let Err(e) = proxy.shutdown(zx::MonotonicInstant::INFINITE) {
83 log_error!(e:%; "StarnixVolumeProvider.Unmount failed at FIDL layer");
84 }
85 }
86
87 fn crypt_service(&self) -> Option<Arc<CryptService>> {
88 Some(self.crypt_service.clone())
89 }
90}
91
92struct VolumeKeys {
106 metadata: [u8; 32],
107 data: [u8; 32],
108}
109
110#[derive(Error, Debug, Eq, PartialEq)]
111enum KeyFileError {
112 #[error("key file not found")]
113 NotFound,
114 #[error("failed to read key file")]
115 ReadError(#[from] zx::Status),
116 #[error("unsupported key file version")]
117 UnsupportedVersion,
118 #[error("unexpected content")]
119 UnexpectedContent,
120}
121
122impl VolumeKeys {
123 const KEYS_SIZE: usize = 64;
126
127 const FILE_SIZE: usize = 2 + Self::KEYS_SIZE;
129
130 const LATEST_VERSION: u16 = 2;
131
132 fn get_or_create(
134 data: &fio::DirectorySynchronousProxy,
135 key_path: &str,
136 ) -> Result<(Self, bool), Errno> {
137 match Self::get(data, key_path) {
138 Ok(keys) => Ok((keys, false)),
139 Err(KeyFileError::ReadError(status)) => {
140 Err(from_status_like_fdio!(status))
144 }
145 Err(e) => {
146 log!(
147 if e == KeyFileError::NotFound { Level::Info } else { Level::Warn },
148 "Creating key file at {key_path} (reason={e:?}) which will \
149 cause existing data to be *wiped* if it exists."
150 );
151 Ok((Self::create(data, key_path)?, true))
152 }
153 }
154 }
155
156 fn get(data: &fio::DirectorySynchronousProxy, key_path: &str) -> Result<Self, KeyFileError> {
159 match syncio::directory_read_file(data, key_path, zx::MonotonicInstant::INFINITE) {
160 Ok(bytes) => {
161 if bytes.len() == Self::FILE_SIZE {
162 if u16::from_le_bytes(bytes[0..2].try_into().unwrap()) != Self::LATEST_VERSION {
163 Err(KeyFileError::UnsupportedVersion)
164 } else {
165 Ok(Self {
166 metadata: bytes[2..34].try_into().unwrap(),
167 data: bytes[34..66].try_into().unwrap(),
168 })
169 }
170 } else {
171 Err(KeyFileError::UnexpectedContent)
172 }
173 }
174 Err(zx::Status::NOT_FOUND) => {
175 Err(KeyFileError::NotFound)
177 }
178 Err(status) => {
179 log_error!(status:?; "Failed to read key file");
180 Err(status.into())
181 }
182 }
183 }
184
185 fn create(data: &fio::DirectorySynchronousProxy, key_path: &str) -> Result<Self, Errno> {
187 let mut bytes = [0; Self::FILE_SIZE];
188 bytes[..2].copy_from_slice(&Self::LATEST_VERSION.to_le_bytes());
189 starnix_crypto::cprng_draw(&mut bytes[2..]);
190 let tmp_file = syncio::directory_create_tmp_file(
191 data,
192 fio::PERM_READABLE,
193 zx::MonotonicInstant::INFINITE,
194 )
195 .map_err(|e| {
196 let err = from_status_like_fdio!(e);
197 log_error!("Failed to create tmp file with error: {:?}", err);
198 err
199 })?;
200 tmp_file
201 .write(&bytes, zx::MonotonicInstant::INFINITE)
202 .map_err(|e| {
203 log_error!("FIDL transport error on File.Write {:?}", e);
204 errno!(ENOENT)
205 })?
206 .map_err(|e| {
207 let err = from_status_like_fdio!(zx::Status::from_raw(e));
208 log_error!("File.Write failed with {:?}", err);
209 err
210 })?;
211 tmp_file
212 .sync(zx::MonotonicInstant::INFINITE)
213 .map_err(|e| {
214 log_error!("FIDL transport error on File.Sync {:?}", e);
215 errno!(ENOENT)
216 })?
217 .map_err(|e| {
218 let err = from_status_like_fdio!(zx::Status::from_raw(e));
219 log_error!("File.Sync failed with {:?}", err);
220 err
221 })?;
222 let (status, token) = data.get_token(zx::MonotonicInstant::INFINITE).map_err(|e| {
223 log_error!("transport error on get_token for the data directory, error: {:?}", e);
224 errno!(ENOENT)
225 })?;
226 zx::Status::ok(status).map_err(|e| {
227 let err = from_status_like_fdio!(e);
228 log_error!("Failed to get_token for the data directory, error: {:?}", err);
229 err
230 })?;
231
232 tmp_file
233 .link_into(
234 zx::Event::from(token.ok_or_else(|| errno!(ENOENT))?),
235 key_path,
236 zx::MonotonicInstant::INFINITE,
237 )
238 .map_err(|e| {
239 log_error!("FIDL transport error on File.LinkInto {:?}", e);
240 errno!(EIO)
241 })?
242 .map_err(|e| {
243 let err = from_status_like_fdio!(zx::Status::from_raw(e));
244 log_error!("File.LinkInto failed with {:?}", err);
245 err
246 })?;
247 Ok(Self {
248 metadata: bytes[2..34].try_into().unwrap(),
249 data: bytes[34..].try_into().unwrap(),
250 })
251 }
252}
253
254pub fn new_remote_vol(
255 locked: &mut Locked<Unlocked>,
256 current_task: &CurrentTask,
257 options: FileSystemOptions,
258) -> Result<FileSystemHandle, Errno> {
259 let kernel = current_task.kernel();
260 let volume_provider = current_task
262 .kernel()
263 .connect_to_protocol_at_container_svc::<StarnixVolumeProviderMarker>()
264 .map_err(|_| errno!(ENOENT))?
265 .into_sync_proxy();
266
267 let (crypt_client_end, crypt_proxy) = fidl::endpoints::create_endpoints::<CryptMarker>();
269 let (crypt2_client_end, crypt2_proxy) = fidl::endpoints::create_endpoints::<CryptMarker>();
270
271 let key_location = match options.params.get(FsStr::new(b"keylocation")) {
272 Some(path) => str::from_utf8(path.as_bytes()).map_err(|_| errno!(EINVAL))?,
273 None => {
274 log_error!(
276 "TODO(b/460156877): Starnix is unable to mount remote volumes without encryption. \
277 Encrypted volumes should specify a keylocation in the mount flags."
278 );
279 return Err(errno!(EINVAL));
280 }
281 };
282
283 let open_flags =
284 fio::PERM_READABLE | fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::PROTOCOL_DIRECTORY;
285 let (root, subdir) = kernel.open_ns_dir(key_location, open_flags)?;
286
287 let open_rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
288 let subdir = if subdir.is_empty() { ".".to_string() } else { subdir };
289 let key_location_proxy = syncio::directory_open_directory_async(&root, &subdir, open_rights)
290 .map_err(|e| errno!(EIO, format!("Failed to open proxy for keylocation: {e}")))?;
291
292 let (keys, created_key_file) = VolumeKeys::get_or_create(&key_location_proxy, KEY_FILE_PATH)?;
293
294 let inline_encryption_provider = if options.params.get(FsStr::new(b"inlinecrypt")).is_some() {
296 match current_task
297 .kernel()
298 .connect_to_protocol_at_container_svc::<InlineEncryptionDeviceMarker>()
299 {
300 Ok(client_end) => Some(client_end.into_sync_proxy()),
301 Err(error) => {
302 log_error!(error:?; "Error connecting to inline encryption device");
303 return Err(error);
304 }
305 }
306 } else {
307 None
308 };
309 let crypt_service =
310 Arc::new(CryptService::new(&keys.metadata, &keys.data, inline_encryption_provider));
311
312 let (exposed_dir_client_end, exposed_dir_server) =
313 fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
314
315 let crypt_service_clone = Arc::clone(&crypt_service);
316 let closure = async move |_: LockedAndTask<'_>| {
317 let (r1, r2) = futures::join!(
318 crypt_service_clone.handle_connection(crypt_proxy.into_stream()),
319 crypt_service_clone.handle_connection(crypt2_proxy.into_stream())
320 );
321 match (r1, r2) {
322 (Err(e1), Err(e2)) => {
323 log_error!("Error while handling Crypt connections: {e1:?}, {e2:?}")
324 }
325 (Err(e), Ok(())) => log_error!("Error while handling Crypt connection: {e:?}"),
326 (Ok(()), Err(e)) => log_error!("Error while handling Crypt2 connection: {e:?}"),
327 (Ok(()), Ok(())) => {}
328 }
329 };
330 let req = SpawnRequestBuilder::new()
331 .with_debug_name("remote-volume-crypt")
332 .with_role(CRYPT_THREAD_ROLE)
333 .with_async_closure(closure)
334 .build();
335 kernel.kthreads.spawner().spawn_from_request(req);
336
337 let mode = if created_key_file {
338 fidl_fuchsia_fshost::MountMode::AlwaysCreate
339 } else {
340 volume_provider
343 .check(crypt2_client_end, zx::MonotonicInstant::INFINITE)
344 .map_err(|e| {
345 log_error!("FIDL transport error on StarnixVolumeProvider.Check {:?}", e);
346 errno!(ENOENT)
347 })?
348 .map_err(|e| {
349 let error = from_status_like_fdio!(zx::Status::from_raw(e));
350 log_error!(
351 error:?;
352 "Volume check failed. The filesystem might be corrupt!");
353 error
354 })?;
355 fidl_fuchsia_fshost::MountMode::MaybeCreate
356 };
357 let guid = volume_provider
358 .mount(crypt_client_end, mode, exposed_dir_server, zx::MonotonicInstant::INFINITE)
359 .map_err(|e| {
360 log_error!("FIDL transport error on StarnixVolumeProvider.Mount {:?}", e);
361 errno!(ENOENT)
362 })?
363 .map_err(|e| {
364 let error = from_status_like_fdio!(zx::Status::from_raw(e));
365 log_error!(error:?; "StarnixVolumeProvider.Mount failed");
366 error
367 })?;
368
369 crypt_service.set_uuid(guid);
370
371 let exposed_dir_proxy = exposed_dir_client_end.into_sync_proxy();
372
373 let root = syncio::directory_open_directory_async(
374 &exposed_dir_proxy,
375 "root",
376 fio::PERM_READABLE | fio::PERM_WRITABLE,
377 )
378 .map_err(|e| errno!(EIO, format!("Failed to open root: {e}")))?;
379
380 let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
381
382 let (remotefs, root_node, info, node_id) = RemoteFs::new(root.into_channel(), rights)?;
383
384 let use_remote_ids = remotefs.use_remote_ids();
385 let remotevol = RemoteVolume { remotefs, exposed_dir_proxy, crypt_service };
386 let fs = FileSystem::new(
387 locked,
388 kernel,
389 CacheMode::Cached(kernel.fs_cache_config()),
390 remotevol,
391 options,
392 )?;
393
394 if use_remote_ids {
395 fs.create_root_with_info(node_id, root_node, info);
396 } else {
397 let root_ino = fs.allocate_ino();
398 fs.create_root_with_info(root_ino, root_node, info);
399 }
400
401 Ok(fs)
402}