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 (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
214        if !dst_rights.contains(fio::Rights::MODIFY_DIRECTORY) {
215            return Err(Status::ACCESS_DENIED);
216        }
217
218        let is_same_dir =
219            Arc::ptr_eq(&(self.base.directory.clone() as Arc<dyn MutableDirectory>), &dst_parent);
220
221        if !is_same_dir && !self.base.options.rights.contains(fio::RW_STAR_DIR) {
222            return Err(Status::ACCESS_DENIED);
223        }
224
225        dst_parent.clone().rename(self.base.directory.clone(), src, dst).await
226    }
227}
228
229impl<DirectoryType: MutableDirectory> ConnectionCreator<DirectoryType>
230    for MutableConnection<DirectoryType>
231{
232    fn create<'a>(
233        scope: ExecutionScope,
234        node: Arc<DirectoryType>,
235        protocols: impl ProtocolsExt,
236        object_request: ObjectRequestRef<'a>,
237    ) -> impl Future<Output = Result<(), Status>> + 'a {
238        Self::create(scope, node, protocols, object_request)
239    }
240}
241
242impl<DirectoryType: MutableDirectory> RequestHandler
243    for Tokenizable<MutableConnection<DirectoryType>>
244{
245    type Request = Result<fio::DirectoryRequest, fidl::Error>;
246
247    async fn handle_request(self: Pin<&mut Self>, request: Self::Request) -> ControlFlow<()> {
248        if let Some(_guard) = self.base.scope.try_active_guard() {
249            match request {
250                Ok(request) => {
251                    match MutableConnection::<DirectoryType>::handle_request(self, request).await {
252                        Ok(ConnectionState::Alive) => ControlFlow::Continue(()),
253                        Ok(ConnectionState::Closed) | Err(_) => ControlFlow::Break(()),
254                    }
255                }
256                Err(_) => ControlFlow::Break(()),
257            }
258        } else {
259            ControlFlow::Break(())
260        }
261    }
262}
263
264impl<DirectoryType: MutableDirectory> TokenInterface for MutableConnection<DirectoryType> {
265    fn get_node(&self) -> Arc<dyn MutableDirectory> {
266        self.base.directory.clone()
267    }
268
269    fn get_rights(&self) -> fio::Rights {
270        self.base.options.rights
271    }
272
273    fn token_registry(&self) -> &TokenRegistry {
274        self.base.scope.token_registry()
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281    use crate::ToObjectRequest;
282    use crate::directory::dirents_sink;
283    use crate::directory::entry::{EntryInfo, GetEntryInfo};
284    use crate::directory::entry_container::{Directory, DirectoryWatcher};
285    use crate::directory::traversal_position::TraversalPosition;
286    use crate::node::Node;
287    use fuchsia_sync::Mutex;
288    use futures::future::BoxFuture;
289    use std::any::Any;
290    use std::future::ready;
291    use std::sync::Weak;
292
293    #[derive(Debug, PartialEq)]
294    enum MutableDirectoryAction {
295        Link { id: u32, path: String },
296        Unlink { id: u32, name: String },
297        Rename { id: u32, src_name: String, dst_dir: u32, dst_name: String },
298        UpdateAttributes { id: u32, attributes: fio::MutableNodeAttributes },
299        Sync,
300        Close,
301    }
302
303    #[derive(Debug)]
304    struct MockDirectory {
305        id: u32,
306        fs: Arc<MockFilesystem>,
307    }
308
309    impl MockDirectory {
310        pub fn new(id: u32, fs: Arc<MockFilesystem>) -> Arc<Self> {
311            Arc::new(MockDirectory { id, fs })
312        }
313    }
314
315    impl PartialEq for MockDirectory {
316        fn eq(&self, other: &Self) -> bool {
317            self.id == other.id
318        }
319    }
320
321    impl GetEntryInfo for MockDirectory {
322        fn entry_info(&self) -> EntryInfo {
323            EntryInfo::new(0, fio::DirentType::Directory)
324        }
325    }
326
327    impl Node for MockDirectory {
328        async fn get_attributes(
329            &self,
330            _query: fio::NodeAttributesQuery,
331        ) -> Result<fio::NodeAttributes2, Status> {
332            unimplemented!("Not implemented");
333        }
334
335        fn close(self: Arc<Self>) {
336            let _ = self.fs.handle_event(MutableDirectoryAction::Close);
337        }
338    }
339
340    impl Directory for MockDirectory {
341        fn open(
342            self: Arc<Self>,
343            _scope: ExecutionScope,
344            _path: Path,
345            _flags: fio::Flags,
346            _object_request: ObjectRequestRef<'_>,
347        ) -> Result<(), Status> {
348            unimplemented!("Not implemented!");
349        }
350
351        async fn read_dirents(
352            &self,
353            _pos: &TraversalPosition,
354            _sink: Box<dyn dirents_sink::Sink>,
355        ) -> Result<(TraversalPosition, Box<dyn dirents_sink::Sealed>), Status> {
356            unimplemented!("Not implemented");
357        }
358
359        fn register_watcher(
360            self: Arc<Self>,
361            _scope: ExecutionScope,
362            _mask: fio::WatchMask,
363            _watcher: DirectoryWatcher,
364        ) -> Result<(), Status> {
365            unimplemented!("Not implemented");
366        }
367
368        fn unregister_watcher(self: Arc<Self>, _key: usize) {
369            unimplemented!("Not implemented");
370        }
371    }
372
373    impl MutableDirectory for MockDirectory {
374        fn link<'a>(
375            self: Arc<Self>,
376            path: String,
377            _source_dir: Arc<dyn Any + Send + Sync>,
378            _source_name: &'a str,
379        ) -> BoxFuture<'a, Result<(), Status>> {
380            let result = self.fs.handle_event(MutableDirectoryAction::Link { id: self.id, path });
381            Box::pin(ready(result))
382        }
383
384        async fn unlink(
385            self: Arc<Self>,
386            name: &str,
387            _must_be_directory: bool,
388        ) -> Result<(), Status> {
389            self.fs.handle_event(MutableDirectoryAction::Unlink {
390                id: self.id,
391                name: name.to_string(),
392            })
393        }
394
395        async fn update_attributes(
396            &self,
397            attributes: fio::MutableNodeAttributes,
398        ) -> Result<(), Status> {
399            self.fs
400                .handle_event(MutableDirectoryAction::UpdateAttributes { id: self.id, attributes })
401        }
402
403        async fn sync(&self) -> Result<(), Status> {
404            self.fs.handle_event(MutableDirectoryAction::Sync)
405        }
406
407        fn rename(
408            self: Arc<Self>,
409            src_dir: Arc<dyn MutableDirectory>,
410            src_name: Path,
411            dst_name: Path,
412        ) -> BoxFuture<'static, Result<(), Status>> {
413            let src_dir = src_dir.into_any().downcast::<MockDirectory>().unwrap();
414            let result = self.fs.handle_event(MutableDirectoryAction::Rename {
415                id: src_dir.id,
416                src_name: src_name.into_string(),
417                dst_dir: self.id,
418                dst_name: dst_name.into_string(),
419            });
420            Box::pin(ready(result))
421        }
422    }
423
424    struct Events(Mutex<Vec<MutableDirectoryAction>>);
425
426    impl Events {
427        fn new() -> Arc<Self> {
428            Arc::new(Events(Mutex::new(vec![])))
429        }
430    }
431
432    struct MockFilesystem {
433        cur_id: Mutex<u32>,
434        scope: ExecutionScope,
435        events: Weak<Events>,
436    }
437
438    impl MockFilesystem {
439        pub fn new(events: &Arc<Events>) -> Self {
440            #[cfg(feature = "fdomain")]
441            let scope =
442                crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
443            #[cfg(not(feature = "fdomain"))]
444            let scope = crate::execution_scope::ExecutionScope::new();
445            MockFilesystem { cur_id: Mutex::new(0), scope, events: Arc::downgrade(events) }
446        }
447
448        pub fn handle_event(&self, event: MutableDirectoryAction) -> Result<(), Status> {
449            self.events.upgrade().map(|x| x.0.lock().push(event));
450            Ok(())
451        }
452
453        pub fn make_connection(
454            self: &Arc<Self>,
455            flags: fio::Flags,
456        ) -> (Arc<MockDirectory>, fio::DirectoryProxy) {
457            let mut cur_id = self.cur_id.lock();
458            let dir = MockDirectory::new(*cur_id, self.clone());
459            *cur_id += 1;
460            let (proxy, server_end) = self.scope.domain().create_proxy::<fio::DirectoryMarker>();
461            flags.to_object_request(server_end).create_connection_sync::<MutableConnection<_>, _>(
462                self.scope.clone(),
463                dir.clone(),
464                flags,
465            );
466            (dir, proxy)
467        }
468    }
469
470    impl std::fmt::Debug for MockFilesystem {
471        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
472            f.debug_struct("MockFilesystem").field("cur_id", &self.cur_id).finish()
473        }
474    }
475
476    #[cfg(not(feature = "fdomain"))]
477    #[fuchsia::test]
478    async fn test_rename() {
479        let events = Events::new();
480        let fs = Arc::new(MockFilesystem::new(&events));
481
482        let (_dir, proxy) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
483        let (dir2, proxy2) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
484
485        let (status, token) = proxy2.get_token().await.unwrap();
486        assert_eq!(status, zx_status::sys::ZX_OK);
487
488        let status = proxy.rename("src", token.unwrap().into(), "dest").await.unwrap();
489        assert!(status.is_ok());
490
491        let events = events.0.lock();
492        assert_eq!(
493            *events,
494            vec![MutableDirectoryAction::Rename {
495                id: 0,
496                src_name: "src".to_owned(),
497                dst_dir: dir2.id,
498                dst_name: "dest".to_owned(),
499            },]
500        );
501    }
502
503    #[fuchsia::test]
504    async fn test_update_attributes() {
505        let events = Events::new();
506        let fs = Arc::new(MockFilesystem::new(&events));
507        let (_dir, proxy) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
508        let attributes = fio::MutableNodeAttributes {
509            creation_time: Some(30),
510            modification_time: Some(100),
511            mode: Some(200),
512            ..Default::default()
513        };
514        proxy
515            .update_attributes(&attributes)
516            .await
517            .expect("FIDL call failed")
518            .map_err(Status::err_from_raw)
519            .expect("update attributes failed");
520
521        let events = events.0.lock();
522        assert_eq!(*events, vec![MutableDirectoryAction::UpdateAttributes { id: 0, attributes }]);
523    }
524
525    #[cfg(not(feature = "fdomain"))]
526    #[fuchsia::test]
527    async fn test_link() {
528        let events = Events::new();
529        let fs = Arc::new(MockFilesystem::new(&events));
530        let (_dir, proxy) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
531        let (_dir2, proxy2) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
532
533        let (status, token) = proxy2.get_token().await.unwrap();
534        assert_eq!(status, zx_status::sys::ZX_OK);
535
536        let status = proxy.link("src", token.unwrap(), "dest").await.unwrap();
537        assert_eq!(status, zx_status::sys::ZX_OK);
538        let events = events.0.lock();
539        assert_eq!(*events, vec![MutableDirectoryAction::Link { id: 1, path: "dest".to_owned() },]);
540    }
541
542    #[fuchsia::test]
543    async fn test_unlink() {
544        let events = Events::new();
545        let fs = Arc::new(MockFilesystem::new(&events));
546        let (_dir, proxy) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
547        proxy
548            .unlink("test", &fio::UnlinkOptions::default())
549            .await
550            .expect("fidl call failed")
551            .expect("unlink failed");
552        let events = events.0.lock();
553        assert_eq!(
554            *events,
555            vec![MutableDirectoryAction::Unlink { id: 0, name: "test".to_string() },]
556        );
557    }
558
559    #[fuchsia::test]
560    async fn test_sync() {
561        let events = Events::new();
562        let fs = Arc::new(MockFilesystem::new(&events));
563        let (_dir, proxy) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
564        let () = proxy.sync().await.unwrap().map_err(Status::err_from_raw).unwrap();
565        let events = events.0.lock();
566        assert_eq!(*events, vec![MutableDirectoryAction::Sync]);
567    }
568
569    #[fuchsia::test]
570    async fn test_close() {
571        let events = Events::new();
572        let fs = Arc::new(MockFilesystem::new(&events));
573        let (_dir, proxy) = fs.clone().make_connection(fio::PERM_READABLE | fio::PERM_WRITABLE);
574        let () = proxy.close().await.unwrap().map_err(Status::err_from_raw).unwrap();
575        let events = events.0.lock();
576        assert_eq!(*events, vec![MutableDirectoryAction::Close]);
577    }
578
579    #[fuchsia::test]
580    async fn test_implicit_close() {
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
585        fs.scope.shutdown();
586        fs.scope.wait().await;
587
588        let events = events.0.lock();
589        assert_eq!(*events, vec![MutableDirectoryAction::Close]);
590    }
591}