1use crate::common::io1_to_io2_attrs;
8use crate::directory::connection::{BaseConnection, ConnectionState};
9use crate::directory::entry_container::MutableDirectory;
10use crate::execution_scope::ExecutionScope;
11use crate::name::validate_name;
12use crate::node::OpenNode;
13use crate::object_request::ConnectionCreator;
14use crate::path::Path;
15use crate::request_handler::{RequestHandler, RequestListener};
16use crate::token_registry::{TokenInterface, TokenRegistry, Tokenizable};
17use crate::{ObjectRequestRef, ProtocolsExt};
18
19use anyhow::Error;
20use flex_client::NullableHandle;
21use flex_fuchsia_io as fio;
22use std::ops::ControlFlow;
23use std::pin::Pin;
24use std::sync::Arc;
25use storage_trace::{self as trace, TraceFutureExt};
26use zx_status::Status;
27
28pub struct MutableConnection<DirectoryType: MutableDirectory> {
29 base: BaseConnection<DirectoryType>,
30}
31
32impl<DirectoryType: MutableDirectory> MutableConnection<DirectoryType> {
33 pub async fn create(
39 scope: ExecutionScope,
40 directory: Arc<DirectoryType>,
41 protocols: impl ProtocolsExt,
42 object_request: ObjectRequestRef<'_>,
43 ) -> Result<(), Status> {
44 let directory = OpenNode::new(directory);
46
47 let connection = MutableConnection {
48 base: BaseConnection::new(scope.clone(), directory, protocols.to_directory_options()?),
49 };
50
51 if let Ok(requests) = object_request.take().into_request_stream(&connection.base).await {
52 scope.spawn(RequestListener::new(requests, Tokenizable::new(connection)));
53 }
54 Ok(())
55 }
56
57 async fn handle_request(
58 this: Pin<&mut Tokenizable<Self>>,
59 request: fio::DirectoryRequest,
60 ) -> Result<ConnectionState, Error> {
61 match request {
62 fio::DirectoryRequest::Unlink { name, options, responder } => {
63 async move {
64 let result = this.handle_unlink(name, options).await;
65 responder.send(result.map_err(Status::into_raw))
66 }
67 .trace(trace::trace_future_args!("storage", "Directory::Unlink"))
68 .await?;
69 }
70 fio::DirectoryRequest::GetToken { responder } => {
71 trace::duration!("storage", "Directory::GetToken");
72 let (raw_status, token) = match Self::handle_get_token(this.into_ref()) {
73 Ok(token) => (0, Some(token)),
74 Err(status) => (status.into_raw(), None),
75 };
76 responder.send(raw_status, token)?;
77 }
78 fio::DirectoryRequest::Rename { src, dst_parent_token, dst, responder } => {
79 async move {
80 let result =
81 this.handle_rename(src, NullableHandle::from(dst_parent_token), dst).await;
82 responder.send(result.map_err(Status::into_raw))
83 }
84 .trace(trace::trace_future_args!("storage", "Directory::Rename"))
85 .await?;
86 }
87 #[cfg(fuchsia_api_level_at_least = "28")]
88 fio::DirectoryRequest::DeprecatedSetAttr { flags, attributes, responder } => {
89 let raw_status = Status::result_into_raw(
90 this.handle_update_attributes(io1_to_io2_attrs(flags, attributes)).await,
91 );
92 responder.send(raw_status)?;
93 }
94 #[cfg(not(fuchsia_api_level_at_least = "28"))]
95 fio::DirectoryRequest::SetAttr { flags, attributes, responder } => {
96 let raw_status = Status::result_into_raw(
97 this.handle_update_attributes(io1_to_io2_attrs(flags, attributes)).await,
98 );
99 responder.send(raw_status)?;
100 }
101 fio::DirectoryRequest::Sync { responder } => {
102 async move {
103 responder.send(this.base.directory.sync().await.map_err(Status::into_raw))
104 }
105 .trace(trace::trace_future_args!("storage", "Directory::Sync"))
106 .await?;
107 }
108 fio::DirectoryRequest::CreateSymlink {
109 responder, name, target, connection, ..
110 } => {
111 async move {
112 if !this.base.options.rights.contains(fio::Operations::MODIFY_DIRECTORY) {
113 responder.send(Err(Status::ACCESS_DENIED.into_raw()))
114 } else if validate_name(&name).is_err() {
115 responder.send(Err(Status::INVALID_ARGS.into_raw()))
116 } else {
117 responder.send(
118 this.base
119 .directory
120 .create_symlink(name, target, connection)
121 .await
122 .map_err(Status::into_raw),
123 )
124 }
125 }
126 .trace(trace::trace_future_args!("storage", "Directory::CreateSymlink"))
127 .await?;
128 }
129 fio::DirectoryRequest::UpdateAttributes { payload, responder } => {
130 async move {
131 responder.send(
132 this.handle_update_attributes(payload).await.map_err(Status::into_raw),
133 )
134 }
135 .trace(trace::trace_future_args!("storage", "Directory::UpdateAttributes"))
136 .await?;
137 }
138 request => {
139 return this.as_mut().base.handle_request(request).await;
140 }
141 }
142 Ok(ConnectionState::Alive)
143 }
144
145 async fn handle_update_attributes(
146 &self,
147 attributes: fio::MutableNodeAttributes,
148 ) -> Result<(), Status> {
149 if !self.base.options.rights.contains(fio::Operations::UPDATE_ATTRIBUTES) {
150 return Err(Status::BAD_HANDLE);
151 }
152 self.base.directory.update_attributes(attributes).await
155 }
156
157 async fn handle_unlink(&self, name: String, options: fio::UnlinkOptions) -> Result<(), Status> {
158 if !self.base.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
159 return Err(Status::BAD_HANDLE);
160 }
161
162 if name.is_empty() || name.contains('/') || name == "." || name == ".." {
163 return Err(Status::INVALID_ARGS);
164 }
165
166 self.base
167 .directory
168 .clone()
169 .unlink(
170 &name,
171 options
172 .flags
173 .map(|f| f.contains(fio::UnlinkFlags::MUST_BE_DIRECTORY))
174 .unwrap_or(false),
175 )
176 .await
177 }
178
179 fn handle_get_token(this: Pin<&Tokenizable<Self>>) -> Result<NullableHandle, Status> {
180 if !this.base.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
185 return Err(Status::BAD_HANDLE);
186 }
187 Ok(TokenRegistry::get_token(this)?)
188 }
189
190 async fn handle_rename(
191 &self,
192 src: String,
193 dst_parent_token: NullableHandle,
194 dst: String,
195 ) -> Result<(), Status> {
196 if !self.base.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
197 return Err(Status::ACCESS_DENIED);
198 }
199
200 let src = Path::validate_and_split(src)?;
201 let dst = Path::validate_and_split(dst)?;
202
203 if !src.is_single_component() || !dst.is_single_component() {
204 return Err(Status::INVALID_ARGS);
205 }
206
207 let (dst_parent, dst_rights) = self
208 .base
209 .scope
210 .token_registry()
211 .get_owner_and_rights(dst_parent_token)?
212 .ok_or(Status::NOT_FOUND)?;
213
214 if !dst_rights.contains(fio::Rights::MODIFY_DIRECTORY) {
215 return Err(Status::ACCESS_DENIED);
216 }
217
218 let is_same_dir =
219 Arc::ptr_eq(&(self.base.directory.clone() as Arc<dyn MutableDirectory>), &dst_parent);
220
221 if !is_same_dir && !self.base.options.rights.contains(fio::RW_STAR_DIR) {
222 return Err(Status::ACCESS_DENIED);
223 }
224
225 dst_parent.clone().rename(self.base.directory.clone(), src, dst).await
226 }
227}
228
229impl<DirectoryType: MutableDirectory> ConnectionCreator<DirectoryType>
230 for MutableConnection<DirectoryType>
231{
232 fn create<'a>(
233 scope: ExecutionScope,
234 node: Arc<DirectoryType>,
235 protocols: impl ProtocolsExt,
236 object_request: ObjectRequestRef<'a>,
237 ) -> impl Future<Output = Result<(), Status>> + 'a {
238 Self::create(scope, node, protocols, object_request)
239 }
240}
241
242impl<DirectoryType: MutableDirectory> RequestHandler
243 for Tokenizable<MutableConnection<DirectoryType>>
244{
245 type Request = Result<fio::DirectoryRequest, fidl::Error>;
246
247 async fn handle_request(self: Pin<&mut Self>, request: Self::Request) -> ControlFlow<()> {
248 if let Some(_guard) = self.base.scope.try_active_guard() {
249 match request {
250 Ok(request) => {
251 match MutableConnection::<DirectoryType>::handle_request(self, request).await {
252 Ok(ConnectionState::Alive) => ControlFlow::Continue(()),
253 Ok(ConnectionState::Closed) | Err(_) => ControlFlow::Break(()),
254 }
255 }
256 Err(_) => ControlFlow::Break(()),
257 }
258 } else {
259 ControlFlow::Break(())
260 }
261 }
262}
263
264impl<DirectoryType: MutableDirectory> TokenInterface for MutableConnection<DirectoryType> {
265 fn get_node(&self) -> Arc<dyn MutableDirectory> {
266 self.base.directory.clone()
267 }
268
269 fn get_rights(&self) -> fio::Rights {
270 self.base.options.rights
271 }
272
273 fn token_registry(&self) -> &TokenRegistry {
274 self.base.scope.token_registry()
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281 use crate::ToObjectRequest;
282 use crate::directory::dirents_sink;
283 use crate::directory::entry::{EntryInfo, GetEntryInfo};
284 use crate::directory::entry_container::{Directory, DirectoryWatcher};
285 use crate::directory::traversal_position::TraversalPosition;
286 use crate::node::Node;
287 use fuchsia_sync::Mutex;
288 use futures::future::BoxFuture;
289 use std::any::Any;
290 use std::future::ready;
291 use std::sync::Weak;
292
293 #[derive(Debug, PartialEq)]
294 enum MutableDirectoryAction {
295 Link { id: u32, path: String },
296 Unlink { id: u32, name: String },
297 Rename { id: u32, src_name: String, dst_dir: u32, dst_name: String },
298 UpdateAttributes { id: u32, attributes: fio::MutableNodeAttributes },
299 Sync,
300 Close,
301 }
302
303 #[derive(Debug)]
304 struct MockDirectory {
305 id: u32,
306 fs: Arc<MockFilesystem>,
307 }
308
309 impl MockDirectory {
310 pub fn new(id: u32, fs: Arc<MockFilesystem>) -> Arc<Self> {
311 Arc::new(MockDirectory { id, fs })
312 }
313 }
314
315 impl PartialEq for MockDirectory {
316 fn eq(&self, other: &Self) -> bool {
317 self.id == other.id
318 }
319 }
320
321 impl GetEntryInfo for MockDirectory {
322 fn entry_info(&self) -> EntryInfo {
323 EntryInfo::new(0, fio::DirentType::Directory)
324 }
325 }
326
327 impl Node for MockDirectory {
328 async fn get_attributes(
329 &self,
330 _query: fio::NodeAttributesQuery,
331 ) -> Result<fio::NodeAttributes2, Status> {
332 unimplemented!("Not implemented");
333 }
334
335 fn close(self: Arc<Self>) {
336 let _ = self.fs.handle_event(MutableDirectoryAction::Close);
337 }
338 }
339
340 impl Directory for MockDirectory {
341 fn open(
342 self: Arc<Self>,
343 _scope: ExecutionScope,
344 _path: Path,
345 _flags: fio::Flags,
346 _object_request: ObjectRequestRef<'_>,
347 ) -> Result<(), Status> {
348 unimplemented!("Not implemented!");
349 }
350
351 async fn read_dirents(
352 &self,
353 _pos: &TraversalPosition,
354 _sink: Box<dyn dirents_sink::Sink>,
355 ) -> Result<(TraversalPosition, Box<dyn dirents_sink::Sealed>), Status> {
356 unimplemented!("Not implemented");
357 }
358
359 fn register_watcher(
360 self: Arc<Self>,
361 _scope: ExecutionScope,
362 _mask: fio::WatchMask,
363 _watcher: DirectoryWatcher,
364 ) -> Result<(), Status> {
365 unimplemented!("Not implemented");
366 }
367
368 fn unregister_watcher(self: Arc<Self>, _key: usize) {
369 unimplemented!("Not implemented");
370 }
371 }
372
373 impl MutableDirectory for MockDirectory {
374 fn link<'a>(
375 self: Arc<Self>,
376 path: String,
377 _source_dir: Arc<dyn Any + Send + Sync>,
378 _source_name: &'a str,
379 ) -> BoxFuture<'a, Result<(), Status>> {
380 let result = self.fs.handle_event(MutableDirectoryAction::Link { id: self.id, path });
381 Box::pin(ready(result))
382 }
383
384 async fn unlink(
385 self: Arc<Self>,
386 name: &str,
387 _must_be_directory: bool,
388 ) -> Result<(), Status> {
389 self.fs.handle_event(MutableDirectoryAction::Unlink {
390 id: self.id,
391 name: name.to_string(),
392 })
393 }
394
395 async fn update_attributes(
396 &self,
397 attributes: fio::MutableNodeAttributes,
398 ) -> Result<(), Status> {
399 self.fs
400 .handle_event(MutableDirectoryAction::UpdateAttributes { id: self.id, attributes })
401 }
402
403 async fn sync(&self) -> Result<(), Status> {
404 self.fs.handle_event(MutableDirectoryAction::Sync)
405 }
406
407 fn rename(
408 self: Arc<Self>,
409 src_dir: Arc<dyn MutableDirectory>,
410 src_name: Path,
411 dst_name: Path,
412 ) -> BoxFuture<'static, Result<(), Status>> {
413 let src_dir = src_dir.into_any().downcast::<MockDirectory>().unwrap();
414 let result = self.fs.handle_event(MutableDirectoryAction::Rename {
415 id: src_dir.id,
416 src_name: src_name.into_string(),
417 dst_dir: self.id,
418 dst_name: dst_name.into_string(),
419 });
420 Box::pin(ready(result))
421 }
422 }
423
424 struct Events(Mutex<Vec<MutableDirectoryAction>>);
425
426 impl Events {
427 fn new() -> Arc<Self> {
428 Arc::new(Events(Mutex::new(vec![])))
429 }
430 }
431
432 struct MockFilesystem {
433 cur_id: Mutex<u32>,
434 scope: ExecutionScope,
435 events: Weak<Events>,
436 }
437
438 impl MockFilesystem {
439 pub fn new(events: &Arc<Events>) -> Self {
440 #[cfg(feature = "fdomain")]
441 let scope =
442 crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
443 #[cfg(not(feature = "fdomain"))]
444 let scope = crate::execution_scope::ExecutionScope::new();
445 MockFilesystem { cur_id: Mutex::new(0), scope, events: Arc::downgrade(events) }
446 }
447
448 pub fn handle_event(&self, event: MutableDirectoryAction) -> Result<(), Status> {
449 self.events.upgrade().map(|x| x.0.lock().push(event));
450 Ok(())
451 }
452
453 pub fn make_connection(
454 self: &Arc<Self>,
455 flags: fio::Flags,
456 ) -> (Arc<MockDirectory>, fio::DirectoryProxy) {
457 let mut cur_id = self.cur_id.lock();
458 let dir = MockDirectory::new(*cur_id, self.clone());
459 *cur_id += 1;
460 let (proxy, server_end) = self.scope.domain().create_proxy::<fio::DirectoryMarker>();
461 flags.to_object_request(server_end).create_connection_sync::<MutableConnection<_>, _>(
462 self.scope.clone(),
463 dir.clone(),
464 flags,
465 );
466 (dir, proxy)
467 }
468 }
469
470 impl std::fmt::Debug for MockFilesystem {
471 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
472 f.debug_struct("MockFilesystem").field("cur_id", &self.cur_id).finish()
473 }
474 }
475
476 #[cfg(not(feature = "fdomain"))]
477 #[fuchsia::test]
478 async fn test_rename() {
479 let events = Events::new();
480 let fs = Arc::new(MockFilesystem::new(&events));
481
482 let (_dir, proxy) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
483 let (dir2, proxy2) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
484
485 let (status, token) = proxy2.get_token().await.unwrap();
486 assert_eq!(status, zx_status::sys::ZX_OK);
487
488 let status = proxy.rename("src", token.unwrap().into(), "dest").await.unwrap();
489 assert!(status.is_ok());
490
491 let events = events.0.lock();
492 assert_eq!(
493 *events,
494 vec![MutableDirectoryAction::Rename {
495 id: 0,
496 src_name: "src".to_owned(),
497 dst_dir: dir2.id,
498 dst_name: "dest".to_owned(),
499 },]
500 );
501 }
502
503 #[fuchsia::test]
504 async fn test_update_attributes() {
505 let events = Events::new();
506 let fs = Arc::new(MockFilesystem::new(&events));
507 let (_dir, proxy) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
508 let attributes = fio::MutableNodeAttributes {
509 creation_time: Some(30),
510 modification_time: Some(100),
511 mode: Some(200),
512 ..Default::default()
513 };
514 proxy
515 .update_attributes(&attributes)
516 .await
517 .expect("FIDL call failed")
518 .map_err(Status::err_from_raw)
519 .expect("update attributes failed");
520
521 let events = events.0.lock();
522 assert_eq!(*events, vec![MutableDirectoryAction::UpdateAttributes { id: 0, attributes }]);
523 }
524
525 #[cfg(not(feature = "fdomain"))]
526 #[fuchsia::test]
527 async fn test_link() {
528 let events = Events::new();
529 let fs = Arc::new(MockFilesystem::new(&events));
530 let (_dir, proxy) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
531 let (_dir2, proxy2) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
532
533 let (status, token) = proxy2.get_token().await.unwrap();
534 assert_eq!(status, zx_status::sys::ZX_OK);
535
536 let status = proxy.link("src", token.unwrap(), "dest").await.unwrap();
537 assert_eq!(status, zx_status::sys::ZX_OK);
538 let events = events.0.lock();
539 assert_eq!(*events, vec![MutableDirectoryAction::Link { id: 1, path: "dest".to_owned() },]);
540 }
541
542 #[fuchsia::test]
543 async fn test_unlink() {
544 let events = Events::new();
545 let fs = Arc::new(MockFilesystem::new(&events));
546 let (_dir, proxy) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
547 proxy
548 .unlink("test", &fio::UnlinkOptions::default())
549 .await
550 .expect("fidl call failed")
551 .expect("unlink failed");
552 let events = events.0.lock();
553 assert_eq!(
554 *events,
555 vec![MutableDirectoryAction::Unlink { id: 0, name: "test".to_string() },]
556 );
557 }
558
559 #[fuchsia::test]
560 async fn test_sync() {
561 let events = Events::new();
562 let fs = Arc::new(MockFilesystem::new(&events));
563 let (_dir, proxy) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
564 let () = proxy.sync().await.unwrap().map_err(Status::err_from_raw).unwrap();
565 let events = events.0.lock();
566 assert_eq!(*events, vec![MutableDirectoryAction::Sync]);
567 }
568
569 #[fuchsia::test]
570 async fn test_close() {
571 let events = Events::new();
572 let fs = Arc::new(MockFilesystem::new(&events));
573 let (_dir, proxy) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
574 let () = proxy.close().await.unwrap().map_err(Status::err_from_raw).unwrap();
575 let events = events.0.lock();
576 assert_eq!(*events, vec![MutableDirectoryAction::Close]);
577 }
578
579 #[fuchsia::test]
580 async fn test_implicit_close() {
581 let events = Events::new();
582 let fs = Arc::new(MockFilesystem::new(&events));
583 let (_dir, _proxy) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
584
585 fs.scope.shutdown();
586 fs.scope.wait().await;
587
588 let events = events.0.lock();
589 assert_eq!(*events, vec![MutableDirectoryAction::Close]);
590 }
591}