Skip to main content

vfs/directory/mutable/
connection.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Connection to a directory that can be modified by the client though a FIDL connection.
6
7use 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    /// Creates a new connection to serve the mutable directory. The directory will be served from a
34    /// new async `Task`, not from the current `Task`. Errors in constructing the connection are not
35    /// guaranteed to be returned, they may be sent directly to the client end of the connection.
36    /// This method should be called from within an `ObjectRequest` handler to ensure that errors
37    /// are sent to the client end of the connection.
38    pub async fn create(
39        scope: ExecutionScope,
40        directory: Arc<DirectoryType>,
41        protocols: impl ProtocolsExt,
42        object_request: ObjectRequestRef<'_>,
43    ) -> Result<(), Status> {
44        // Ensure we close the directory if we fail to prepare the connection.
45        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        // TODO(jfsulliv): Consider always permitting attributes to be deferrable. The risk with
172        // this is that filesystems would require a background flush of dirty attributes to disk.
173        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        // TODO(https://fxbug.dev/503041342): The current DeprecatedGetToken method on directory
200        // requires specific rights to get the token in the first place. The new GetToken on Node
201        // will require no rights to get a token, but instead enforced on use of the token. When
202        // the old one is deleted this method (and therefore this check) will be deleted too.
203        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}