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