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