1use crate::common::{
8 decode_extended_attribute_value, encode_extended_attribute_value, extended_attributes_sender,
9 io1_to_io2_attrs,
10};
11use crate::directory::connection::{BaseConnection, ConnectionState};
12use crate::directory::entry_container::MutableDirectory;
13use crate::execution_scope::ExecutionScope;
14use crate::name::validate_name;
15use crate::node::OpenNode;
16use crate::object_request::ConnectionCreator;
17use crate::path::Path;
18use crate::request_handler::{RequestHandler, RequestListener};
19use crate::token_registry::{TokenInterface, TokenRegistry, Tokenizable};
20use crate::{ObjectRequestRef, ProtocolsExt};
21
22use anyhow::Error;
23use fidl::endpoints::ServerEnd;
24use fidl::Handle;
25use fidl_fuchsia_io as fio;
26use std::ops::ControlFlow;
27use std::pin::Pin;
28use std::sync::Arc;
29use storage_trace::{self as trace, TraceFutureExt};
30use zx_status::Status;
31
32pub struct MutableConnection<DirectoryType: MutableDirectory> {
33 base: BaseConnection<DirectoryType>,
34}
35
36impl<DirectoryType: MutableDirectory> MutableConnection<DirectoryType> {
37 pub async fn create(
43 scope: ExecutionScope,
44 directory: Arc<DirectoryType>,
45 protocols: impl ProtocolsExt,
46 object_request: ObjectRequestRef<'_>,
47 ) -> Result<(), Status> {
48 let directory = OpenNode::new(directory);
50
51 let connection = MutableConnection {
52 base: BaseConnection::new(scope.clone(), directory, protocols.to_directory_options()?),
53 };
54
55 if let Ok(requests) = object_request.take().into_request_stream(&connection.base).await {
56 scope.spawn(RequestListener::new(requests, Tokenizable::new(connection)));
57 }
58 Ok(())
59 }
60
61 async fn handle_request(
62 this: Pin<&mut Tokenizable<Self>>,
63 request: fio::DirectoryRequest,
64 ) -> Result<ConnectionState, Error> {
65 match request {
66 fio::DirectoryRequest::Unlink { name, options, responder } => {
67 let result = this.handle_unlink(name, options).await;
68 responder.send(result.map_err(Status::into_raw))?;
69 }
70 fio::DirectoryRequest::GetToken { responder } => {
71 let (status, token) = match Self::handle_get_token(this.into_ref()) {
72 Ok(token) => (Status::OK, Some(token)),
73 Err(status) => (status, None),
74 };
75 responder.send(status.into_raw(), token)?;
76 }
77 fio::DirectoryRequest::Rename { src, dst_parent_token, dst, responder } => {
78 let result = this.handle_rename(src, Handle::from(dst_parent_token), dst).await;
79 responder.send(result.map_err(Status::into_raw))?;
80 }
81 fio::DirectoryRequest::SetAttr { flags, attributes, responder } => {
82 let status = match this
83 .handle_update_attributes(io1_to_io2_attrs(flags, attributes))
84 .await
85 {
86 Ok(()) => Status::OK,
87 Err(status) => status,
88 };
89 responder.send(status.into_raw())?;
90 }
91 fio::DirectoryRequest::Sync { responder } => {
92 responder.send(this.base.directory.sync().await.map_err(Status::into_raw))?;
93 }
94 fio::DirectoryRequest::CreateSymlink {
95 responder, name, target, connection, ..
96 } => {
97 if !this.base.options.rights.contains(fio::Operations::MODIFY_DIRECTORY) {
98 responder.send(Err(Status::ACCESS_DENIED.into_raw()))?;
99 } else if validate_name(&name).is_err() {
100 responder.send(Err(Status::INVALID_ARGS.into_raw()))?;
101 } else {
102 responder.send(
103 this.base
104 .directory
105 .create_symlink(name, target, connection)
106 .await
107 .map_err(Status::into_raw),
108 )?;
109 }
110 }
111 fio::DirectoryRequest::ListExtendedAttributes { iterator, control_handle: _ } => {
112 this.handle_list_extended_attribute(iterator)
113 .trace(trace::trace_future_args!(
114 c"storage",
115 c"Directory::ListExtendedAttributes"
116 ))
117 .await;
118 }
119 fio::DirectoryRequest::GetExtendedAttribute { name, responder } => {
120 async move {
121 let res =
122 this.handle_get_extended_attribute(name).await.map_err(Status::into_raw);
123 responder.send(res)
124 }
125 .trace(trace::trace_future_args!(c"storage", c"Directory::GetExtendedAttribute"))
126 .await?;
127 }
128 fio::DirectoryRequest::SetExtendedAttribute { name, value, mode, responder } => {
129 async move {
130 let res = this
131 .handle_set_extended_attribute(name, value, mode)
132 .await
133 .map_err(Status::into_raw);
134 responder.send(res)
135 }
136 .trace(trace::trace_future_args!(c"storage", c"Directory::SetExtendedAttribute"))
137 .await?;
138 }
139 fio::DirectoryRequest::RemoveExtendedAttribute { name, responder } => {
140 async move {
141 let res =
142 this.handle_remove_extended_attribute(name).await.map_err(Status::into_raw);
143 responder.send(res)
144 }
145 .trace(trace::trace_future_args!(c"storage", c"Directory::RemoveExtendedAttribute"))
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!(c"storage", c"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<Handle, Status> {
199 if !this.base.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
202 return Err(Status::BAD_HANDLE);
203 }
204 Ok(TokenRegistry::get_token(this)?)
205 }
206
207 async fn handle_rename(
208 &self,
209 src: String,
210 dst_parent_token: Handle,
211 dst: String,
212 ) -> Result<(), Status> {
213 if !self.base.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
214 return Err(Status::BAD_HANDLE);
215 }
216
217 let src = Path::validate_and_split(src)?;
218 let dst = Path::validate_and_split(dst)?;
219
220 if !src.is_single_component() || !dst.is_single_component() {
221 return Err(Status::INVALID_ARGS);
222 }
223
224 let dst_parent = match self.base.scope.token_registry().get_owner(dst_parent_token)? {
225 None => return Err(Status::NOT_FOUND),
226 Some(entry) => entry,
227 };
228
229 dst_parent.clone().rename(self.base.directory.clone(), src, dst).await
230 }
231
232 async fn handle_list_extended_attribute(
233 &self,
234 iterator: ServerEnd<fio::ExtendedAttributeIteratorMarker>,
235 ) {
236 let attributes = match self.base.directory.list_extended_attributes().await {
237 Ok(attributes) => attributes,
238 Err(status) => {
239 #[cfg(any(test, feature = "use_log"))]
240 log::error!(status:?; "list extended attributes failed");
241 #[allow(clippy::unnecessary_lazy_evaluations)]
242 iterator.close_with_epitaph(status).unwrap_or_else(|_error| {
243 #[cfg(any(test, feature = "use_log"))]
244 log::error!(_error:?; "failed to send epitaph")
245 });
246 return;
247 }
248 };
249 self.base.scope.spawn(extended_attributes_sender(iterator, attributes));
250 }
251
252 async fn handle_get_extended_attribute(
253 &self,
254 name: Vec<u8>,
255 ) -> Result<fio::ExtendedAttributeValue, Status> {
256 let value = self.base.directory.get_extended_attribute(name).await?;
257 encode_extended_attribute_value(value)
258 }
259
260 async fn handle_set_extended_attribute(
261 &self,
262 name: Vec<u8>,
263 value: fio::ExtendedAttributeValue,
264 mode: fio::SetExtendedAttributeMode,
265 ) -> Result<(), Status> {
266 if name.contains(&0) {
267 return Err(Status::INVALID_ARGS);
268 }
269 let val = decode_extended_attribute_value(value)?;
270 self.base.directory.set_extended_attribute(name, val, mode).await
271 }
272
273 async fn handle_remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), Status> {
274 self.base.directory.remove_extended_attribute(name).await
275 }
276}
277
278impl<DirectoryType: MutableDirectory> ConnectionCreator<DirectoryType>
279 for MutableConnection<DirectoryType>
280{
281 async fn create<'a>(
282 scope: ExecutionScope,
283 node: Arc<DirectoryType>,
284 protocols: impl ProtocolsExt,
285 object_request: ObjectRequestRef<'a>,
286 ) -> Result<(), Status> {
287 Self::create(scope, node, protocols, object_request).await
288 }
289}
290
291impl<DirectoryType: MutableDirectory> RequestHandler
292 for Tokenizable<MutableConnection<DirectoryType>>
293{
294 type Request = Result<fio::DirectoryRequest, fidl::Error>;
295
296 async fn handle_request(self: Pin<&mut Self>, request: Self::Request) -> ControlFlow<()> {
297 let _guard = self.base.scope.active_guard();
298 match request {
299 Ok(request) => {
300 match MutableConnection::<DirectoryType>::handle_request(self, request).await {
301 Ok(ConnectionState::Alive) => ControlFlow::Continue(()),
302 Ok(ConnectionState::Closed) | Err(_) => ControlFlow::Break(()),
303 }
304 }
305 Err(_) => ControlFlow::Break(()),
306 }
307 }
308}
309
310impl<DirectoryType: MutableDirectory> TokenInterface for MutableConnection<DirectoryType> {
311 fn get_node(&self) -> Arc<dyn MutableDirectory> {
312 self.base.directory.clone()
313 }
314
315 fn token_registry(&self) -> &TokenRegistry {
316 self.base.scope.token_registry()
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use crate::directory::dirents_sink;
324 use crate::directory::entry::{EntryInfo, GetEntryInfo};
325 use crate::directory::entry_container::{Directory, DirectoryWatcher};
326 use crate::directory::traversal_position::TraversalPosition;
327 use crate::node::Node;
328 use crate::ToObjectRequest;
329 use fuchsia_sync::Mutex;
330 use futures::future::BoxFuture;
331 use std::any::Any;
332 use std::future::ready;
333 use std::sync::Weak;
334
335 #[derive(Debug, PartialEq)]
336 enum MutableDirectoryAction {
337 Link { id: u32, path: String },
338 Unlink { id: u32, name: String },
339 Rename { id: u32, src_name: String, dst_dir: u32, dst_name: String },
340 UpdateAttributes { id: u32, attributes: fio::MutableNodeAttributes },
341 Sync,
342 Close,
343 }
344
345 #[derive(Debug)]
346 struct MockDirectory {
347 id: u32,
348 fs: Arc<MockFilesystem>,
349 }
350
351 impl MockDirectory {
352 pub fn new(id: u32, fs: Arc<MockFilesystem>) -> Arc<Self> {
353 Arc::new(MockDirectory { id, fs })
354 }
355 }
356
357 impl PartialEq for MockDirectory {
358 fn eq(&self, other: &Self) -> bool {
359 self.id == other.id
360 }
361 }
362
363 impl GetEntryInfo for MockDirectory {
364 fn entry_info(&self) -> EntryInfo {
365 EntryInfo::new(0, fio::DirentType::Directory)
366 }
367 }
368
369 impl Node for MockDirectory {
370 async fn get_attributes(
371 &self,
372 _query: fio::NodeAttributesQuery,
373 ) -> Result<fio::NodeAttributes2, Status> {
374 unimplemented!("Not implemented");
375 }
376
377 fn close(self: Arc<Self>) {
378 let _ = self.fs.handle_event(MutableDirectoryAction::Close);
379 }
380 }
381
382 impl Directory for MockDirectory {
383 fn deprecated_open(
384 self: Arc<Self>,
385 _scope: ExecutionScope,
386 _flags: fio::OpenFlags,
387 _path: Path,
388 _server_end: ServerEnd<fio::NodeMarker>,
389 ) {
390 unimplemented!("Not implemented!");
391 }
392
393 fn open(
394 self: Arc<Self>,
395 _scope: ExecutionScope,
396 _path: Path,
397 _flags: fio::Flags,
398 _object_request: ObjectRequestRef<'_>,
399 ) -> Result<(), Status> {
400 unimplemented!("Not implemented!");
401 }
402
403 async fn read_dirents<'a>(
404 &'a self,
405 _pos: &'a TraversalPosition,
406 _sink: Box<dyn dirents_sink::Sink>,
407 ) -> Result<(TraversalPosition, Box<dyn dirents_sink::Sealed>), Status> {
408 unimplemented!("Not implemented");
409 }
410
411 fn register_watcher(
412 self: Arc<Self>,
413 _scope: ExecutionScope,
414 _mask: fio::WatchMask,
415 _watcher: DirectoryWatcher,
416 ) -> Result<(), Status> {
417 unimplemented!("Not implemented");
418 }
419
420 fn unregister_watcher(self: Arc<Self>, _key: usize) {
421 unimplemented!("Not implemented");
422 }
423 }
424
425 impl MutableDirectory for MockDirectory {
426 fn link<'a>(
427 self: Arc<Self>,
428 path: String,
429 _source_dir: Arc<dyn Any + Send + Sync>,
430 _source_name: &'a str,
431 ) -> BoxFuture<'a, Result<(), Status>> {
432 let result = self.fs.handle_event(MutableDirectoryAction::Link { id: self.id, path });
433 Box::pin(ready(result))
434 }
435
436 async fn unlink(
437 self: Arc<Self>,
438 name: &str,
439 _must_be_directory: bool,
440 ) -> Result<(), Status> {
441 self.fs.handle_event(MutableDirectoryAction::Unlink {
442 id: self.id,
443 name: name.to_string(),
444 })
445 }
446
447 async fn update_attributes(
448 &self,
449 attributes: fio::MutableNodeAttributes,
450 ) -> Result<(), Status> {
451 self.fs
452 .handle_event(MutableDirectoryAction::UpdateAttributes { id: self.id, attributes })
453 }
454
455 async fn sync(&self) -> Result<(), Status> {
456 self.fs.handle_event(MutableDirectoryAction::Sync)
457 }
458
459 fn rename(
460 self: Arc<Self>,
461 src_dir: Arc<dyn MutableDirectory>,
462 src_name: Path,
463 dst_name: Path,
464 ) -> BoxFuture<'static, Result<(), Status>> {
465 let src_dir = src_dir.into_any().downcast::<MockDirectory>().unwrap();
466 let result = self.fs.handle_event(MutableDirectoryAction::Rename {
467 id: src_dir.id,
468 src_name: src_name.into_string(),
469 dst_dir: self.id,
470 dst_name: dst_name.into_string(),
471 });
472 Box::pin(ready(result))
473 }
474 }
475
476 struct Events(Mutex<Vec<MutableDirectoryAction>>);
477
478 impl Events {
479 fn new() -> Arc<Self> {
480 Arc::new(Events(Mutex::new(vec![])))
481 }
482 }
483
484 struct MockFilesystem {
485 cur_id: Mutex<u32>,
486 scope: ExecutionScope,
487 events: Weak<Events>,
488 }
489
490 impl MockFilesystem {
491 pub fn new(events: &Arc<Events>) -> Self {
492 let scope = ExecutionScope::new();
493 MockFilesystem { cur_id: Mutex::new(0), scope, events: Arc::downgrade(events) }
494 }
495
496 pub fn handle_event(&self, event: MutableDirectoryAction) -> Result<(), Status> {
497 self.events.upgrade().map(|x| x.0.lock().push(event));
498 Ok(())
499 }
500
501 pub fn make_connection(
502 self: &Arc<Self>,
503 flags: fio::OpenFlags,
504 ) -> (Arc<MockDirectory>, fio::DirectoryProxy) {
505 let mut cur_id = self.cur_id.lock();
506 let dir = MockDirectory::new(*cur_id, self.clone());
507 *cur_id += 1;
508 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
509 flags.to_object_request(server_end).create_connection_sync::<MutableConnection<_>, _>(
510 self.scope.clone(),
511 dir.clone(),
512 flags,
513 );
514 (dir, proxy)
515 }
516 }
517
518 impl std::fmt::Debug for MockFilesystem {
519 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
520 f.debug_struct("MockFilesystem").field("cur_id", &self.cur_id).finish()
521 }
522 }
523
524 #[fuchsia::test]
525 async fn test_rename() {
526 use fidl::Event;
527
528 let events = Events::new();
529 let fs = Arc::new(MockFilesystem::new(&events));
530
531 let (_dir, proxy) = fs
532 .clone()
533 .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
534 let (dir2, proxy2) = fs
535 .clone()
536 .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
537
538 let (status, token) = proxy2.get_token().await.unwrap();
539 assert_eq!(Status::from_raw(status), Status::OK);
540
541 let status = proxy.rename("src", Event::from(token.unwrap()), "dest").await.unwrap();
542 assert!(status.is_ok());
543
544 let events = events.0.lock();
545 assert_eq!(
546 *events,
547 vec![MutableDirectoryAction::Rename {
548 id: 0,
549 src_name: "src".to_owned(),
550 dst_dir: dir2.id,
551 dst_name: "dest".to_owned(),
552 },]
553 );
554 }
555
556 #[fuchsia::test]
557 async fn test_update_attributes() {
558 let events = Events::new();
559 let fs = Arc::new(MockFilesystem::new(&events));
560 let (_dir, proxy) = fs
561 .clone()
562 .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
563 let attributes = fio::MutableNodeAttributes {
564 creation_time: Some(30),
565 modification_time: Some(100),
566 mode: Some(200),
567 ..Default::default()
568 };
569 proxy
570 .update_attributes(&attributes)
571 .await
572 .expect("FIDL call failed")
573 .map_err(Status::from_raw)
574 .expect("update attributes failed");
575
576 let events = events.0.lock();
577 assert_eq!(*events, vec![MutableDirectoryAction::UpdateAttributes { id: 0, attributes }]);
578 }
579
580 #[fuchsia::test]
581 async fn test_link() {
582 let events = Events::new();
583 let fs = Arc::new(MockFilesystem::new(&events));
584 let (_dir, proxy) = fs
585 .clone()
586 .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
587 let (_dir2, proxy2) = fs
588 .clone()
589 .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
590
591 let (status, token) = proxy2.get_token().await.unwrap();
592 assert_eq!(Status::from_raw(status), Status::OK);
593
594 let status = proxy.link("src", token.unwrap(), "dest").await.unwrap();
595 assert_eq!(Status::from_raw(status), Status::OK);
596 let events = events.0.lock();
597 assert_eq!(*events, vec![MutableDirectoryAction::Link { id: 1, path: "dest".to_owned() },]);
598 }
599
600 #[fuchsia::test]
601 async fn test_unlink() {
602 let events = Events::new();
603 let fs = Arc::new(MockFilesystem::new(&events));
604 let (_dir, proxy) = fs
605 .clone()
606 .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
607 proxy
608 .unlink("test", &fio::UnlinkOptions::default())
609 .await
610 .expect("fidl call failed")
611 .expect("unlink failed");
612 let events = events.0.lock();
613 assert_eq!(
614 *events,
615 vec![MutableDirectoryAction::Unlink { id: 0, name: "test".to_string() },]
616 );
617 }
618
619 #[fuchsia::test]
620 async fn test_sync() {
621 let events = Events::new();
622 let fs = Arc::new(MockFilesystem::new(&events));
623 let (_dir, proxy) = fs
624 .clone()
625 .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
626 let () = proxy.sync().await.unwrap().map_err(Status::from_raw).unwrap();
627 let events = events.0.lock();
628 assert_eq!(*events, vec![MutableDirectoryAction::Sync]);
629 }
630
631 #[fuchsia::test]
632 async fn test_close() {
633 let events = Events::new();
634 let fs = Arc::new(MockFilesystem::new(&events));
635 let (_dir, proxy) = fs
636 .clone()
637 .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
638 let () = proxy.close().await.unwrap().map_err(Status::from_raw).unwrap();
639 let events = events.0.lock();
640 assert_eq!(*events, vec![MutableDirectoryAction::Close]);
641 }
642
643 #[fuchsia::test]
644 async fn test_implicit_close() {
645 let events = Events::new();
646 let fs = Arc::new(MockFilesystem::new(&events));
647 let (_dir, _proxy) = fs
648 .clone()
649 .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
650
651 fs.scope.shutdown();
652 fs.scope.wait().await;
653
654 let events = events.0.lock();
655 assert_eq!(*events, vec![MutableDirectoryAction::Close]);
656 }
657}