1use crate::fs::fuchsia::{RemoteFs, RemoteNode};
6use crate::task::dynamic_thread_spawner::SpawnRequestBuilder;
7use crate::task::{CurrentTask, LockedAndTask};
8use crate::vfs::{
9 CacheConfig, CacheMode, FileSystem, FileSystemHandle, FileSystemOps, FileSystemOptions,
10 FsNodeHandle, FsStr,
11};
12use fidl::endpoints::{DiscoverableProtocolMarker, SynchronousProxy, create_sync_proxy};
13use fidl_fuchsia_fshost::StarnixVolumeProviderMarker;
14use fidl_fuchsia_fxfs::CryptMarker;
15use fidl_fuchsia_io as fio;
16use starnix_crypt::CryptService;
17use starnix_logging::{log_error, log_info};
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 syncio::{Zxio, zxio_node_attr_has_t, zxio_node_attributes_t};
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 {
125 metadata: [u8; 32],
126 data: [u8; 32],
127 use_lblk32_identifiers: bool,
128}
129
130impl VolumeKeys {
131 const KEYS_SIZE: usize = 64;
134
135 const V1_FILE_SIZE: usize = Self::KEYS_SIZE;
137
138 const FILE_SIZE: usize = 2 + Self::KEYS_SIZE;
140
141 const LATEST_VERSION: u16 = 2;
142
143 fn get_or_create(
145 data: &fio::DirectorySynchronousProxy,
146 key_path: &str,
147 ) -> Result<(Self, bool), Errno> {
148 if let Some(keys) = Self::get(data, key_path)? {
149 Ok((keys, false))
150 } else {
151 log_info!("Creating key file at {key_path}");
152 Ok((Self::create(data, key_path)?, true))
153 }
154 }
155
156 fn get(data: &fio::DirectorySynchronousProxy, key_path: &str) -> Result<Option<Self>, Errno> {
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 {
164 return Ok(None);
165 }
166 Ok(Some(Self {
167 metadata: bytes[2..34].try_into().unwrap(),
168 data: bytes[34..66].try_into().unwrap(),
169 use_lblk32_identifiers: true,
170 }))
171 } else if bytes.len() == Self::V1_FILE_SIZE {
172 Ok(Some(Self {
174 metadata: bytes[..32].try_into().unwrap(),
175 data: bytes[32..].try_into().unwrap(),
176 use_lblk32_identifiers: false,
177 }))
178 } else {
179 Ok(None)
180 }
181 }
182 Err(zx::Status::NOT_FOUND) => Ok(None),
183 Err(status) => {
184 log_error!("Failed to read key file: {status:?}");
185 Err(from_status_like_fdio!(status))
186 }
187 }
188 }
189
190 fn create(data: &fio::DirectorySynchronousProxy, key_path: &str) -> Result<Self, Errno> {
192 let mut bytes = [0; Self::FILE_SIZE];
193 bytes[..2].copy_from_slice(&Self::LATEST_VERSION.to_le_bytes());
194 zx::cprng_draw(&mut bytes[2..]);
195 let tmp_file = syncio::directory_create_tmp_file(
196 data,
197 fio::PERM_READABLE,
198 zx::MonotonicInstant::INFINITE,
199 )
200 .map_err(|e| {
201 let err = from_status_like_fdio!(e);
202 log_error!("Failed to create tmp file with error: {:?}", err);
203 err
204 })?;
205 tmp_file
206 .write(&bytes, zx::MonotonicInstant::INFINITE)
207 .map_err(|e| {
208 log_error!("FIDL transport error on File.Write {:?}", e);
209 errno!(ENOENT)
210 })?
211 .map_err(|e| {
212 let err = from_status_like_fdio!(zx::Status::from_raw(e));
213 log_error!("File.Write failed with {:?}", err);
214 err
215 })?;
216 tmp_file
217 .sync(zx::MonotonicInstant::INFINITE)
218 .map_err(|e| {
219 log_error!("FIDL transport error on File.Sync {:?}", e);
220 errno!(ENOENT)
221 })?
222 .map_err(|e| {
223 let err = from_status_like_fdio!(zx::Status::from_raw(e));
224 log_error!("File.Sync failed with {:?}", err);
225 err
226 })?;
227 let (status, token) = data.get_token(zx::MonotonicInstant::INFINITE).map_err(|e| {
228 log_error!("transport error on get_token for the data directory, error: {:?}", e);
229 errno!(ENOENT)
230 })?;
231 zx::Status::ok(status).map_err(|e| {
232 let err = from_status_like_fdio!(e);
233 log_error!("Failed to get_token for the data directory, error: {:?}", err);
234 err
235 })?;
236
237 tmp_file
238 .link_into(
239 zx::Event::from(token.ok_or_else(|| errno!(ENOENT))?),
240 key_path,
241 zx::MonotonicInstant::INFINITE,
242 )
243 .map_err(|e| {
244 log_error!("FIDL transport error on File.LinkInto {:?}", e);
245 errno!(EIO)
246 })?
247 .map_err(|e| {
248 let err = from_status_like_fdio!(zx::Status::from_raw(e));
249 log_error!("File.LinkInto failed with {:?}", err);
250 err
251 })?;
252 Ok(Self {
253 metadata: bytes[2..34].try_into().unwrap(),
254 data: bytes[34..].try_into().unwrap(),
255 use_lblk32_identifiers: true,
256 })
257 }
258}
259
260pub fn new_remote_vol(
261 locked: &mut Locked<Unlocked>,
262 current_task: &CurrentTask,
263 options: FileSystemOptions,
264) -> Result<FileSystemHandle, Errno> {
265 let kernel = current_task.kernel();
266 let volume_provider = current_task
268 .kernel()
269 .connect_to_protocol_at_container_svc::<StarnixVolumeProviderMarker>()
270 .map_err(|_| errno!(ENOENT))?
271 .into_sync_proxy();
272
273 let (crypt_client_end, crypt_proxy) = fidl::endpoints::create_endpoints::<CryptMarker>();
274
275 let key_location = match options.params.get(FsStr::new(b"keylocation")) {
276 Some(path) => str::from_utf8(path.as_bytes()).map_err(|_| errno!(EINVAL))?,
277 None => {
278 log_error!(
280 "TODO(b/460156877): Starnix is unable to mount remote volumes without encryption. \
281 Encrypted volumes should specify a keylocation in the mount flags."
282 );
283 return Err(errno!(EINVAL));
284 }
285 };
286
287 let open_flags =
288 fio::PERM_READABLE | fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::PROTOCOL_DIRECTORY;
289 let (root, subdir) = kernel.open_ns_dir(key_location, open_flags)?;
290
291 let open_rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
292 let subdir = if subdir.is_empty() { ".".to_string() } else { subdir };
293 let key_location_proxy = syncio::directory_open_directory_async(&root, &subdir, open_rights)
294 .map_err(|e| errno!(EIO, format!("Failed to open proxy for keylocation: {e}")))?;
295
296 let (keys, created_key_file) = VolumeKeys::get_or_create(&key_location_proxy, KEY_FILE_PATH)?;
297
298 let crypt_service =
299 Arc::new(CryptService::new(&keys.metadata, &keys.data, keys.use_lblk32_identifiers, None));
300
301 let (exposed_dir_client_end, exposed_dir_server) =
302 fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
303
304 {
305 let crypt_service = Arc::clone(&crypt_service);
306 let closure = async move |_: LockedAndTask<'_>| {
307 if let Err(e) = crypt_service.handle_connection(crypt_proxy.into_stream()).await {
308 log_error!("Error while handling a Crypt request {e}");
309 }
310 };
311 let req = SpawnRequestBuilder::new()
312 .with_debug_name("remote-volume-crypt")
313 .with_role(CRYPT_THREAD_ROLE)
314 .with_async_closure(closure)
315 .build();
316 kernel.kthreads.spawner().spawn_from_request(req);
317 }
318
319 let mode = if created_key_file {
320 fidl_fuchsia_fshost::MountMode::AlwaysCreate
321 } else {
322 fidl_fuchsia_fshost::MountMode::MaybeCreate
323 };
324 let guid = volume_provider
325 .mount(crypt_client_end, mode, exposed_dir_server, zx::MonotonicInstant::INFINITE)
326 .map_err(|e| {
327 log_error!("FIDL transport error on StarnixVolumeProvider.Mount {:?}", e);
328 errno!(ENOENT)
329 })?
330 .map_err(|e| {
331 let error = from_status_like_fdio!(zx::Status::from_raw(e));
332 log_error!(error:?; "StarnixVolumeProvider.Mount failed");
333 error
334 })?;
335
336 crypt_service.set_uuid(guid);
337
338 let exposed_dir_proxy = exposed_dir_client_end.into_sync_proxy();
339
340 let root = syncio::directory_open_directory_async(
341 &exposed_dir_proxy,
342 "root",
343 fio::PERM_READABLE | fio::PERM_WRITABLE,
344 )
345 .map_err(|e| errno!(EIO, format!("Failed to open root: {e}")))?;
346
347 let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
348
349 let (client_end, server_end) = zx::Channel::create();
350 let remotefs = RemoteFs::new(root.into_channel(), server_end)?;
351 let mut attrs = zxio_node_attributes_t {
352 has: zxio_node_attr_has_t { id: true, ..Default::default() },
353 ..Default::default()
354 };
355 let (remote_node, node_id) =
356 match Zxio::create_with_on_representation(client_end.into(), Some(&mut attrs)) {
357 Err(status) => return Err(from_status_like_fdio!(status)),
358 Ok(zxio) => (RemoteNode::new(zxio, rights), attrs.id),
359 };
360
361 let use_remote_ids = remotefs.use_remote_ids();
362 let remotevol = RemoteVolume { remotefs, exposed_dir_proxy, crypt_service };
363 let fs = FileSystem::new(
364 locked,
365 kernel,
366 CacheMode::Cached(CacheConfig::default()),
367 remotevol,
368 options,
369 )?;
370 if use_remote_ids {
371 fs.create_root(node_id, remote_node);
372 } else {
373 let root_ino = fs.allocate_ino();
374 fs.create_root(root_ino, remote_node);
375 }
376 Ok(fs)
377}