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