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