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