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