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