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, 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            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        // TODO(jfsulliv): Consider always permitting attributes to be deferrable. The risk with
161        // this is that filesystems would require a background flush of dirty attributes to disk.
162        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        // TODO(https://fxbug.dev/503041342): The current GetToken method on directory requires
189        // specific rights to get the token in the first place. The new GetToken on Node will
190        // require no rights to get a token, but instead enforced on use of the token. When the old
191        // one is deleted this method (and therefore this check) will be deleted too.
192        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}