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