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