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