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::{
8    decode_extended_attribute_value, encode_extended_attribute_value, extended_attributes_sender,
9    io1_to_io2_attrs,
10};
11use crate::directory::connection::{BaseConnection, ConnectionState};
12use crate::directory::entry_container::MutableDirectory;
13use crate::execution_scope::ExecutionScope;
14use crate::name::validate_name;
15use crate::node::OpenNode;
16use crate::object_request::ConnectionCreator;
17use crate::path::Path;
18use crate::request_handler::{RequestHandler, RequestListener};
19use crate::token_registry::{TokenInterface, TokenRegistry, Tokenizable};
20use crate::{ObjectRequestRef, ProtocolsExt};
21
22use anyhow::Error;
23use fidl::endpoints::ServerEnd;
24use fidl::Handle;
25use fidl_fuchsia_io as fio;
26use std::ops::ControlFlow;
27use std::pin::Pin;
28use std::sync::Arc;
29use storage_trace::{self as trace, TraceFutureExt};
30use zx_status::Status;
31
32pub struct MutableConnection<DirectoryType: MutableDirectory> {
33    base: BaseConnection<DirectoryType>,
34}
35
36impl<DirectoryType: MutableDirectory> MutableConnection<DirectoryType> {
37    /// Creates a new connection to serve the mutable directory. The directory will be served from a
38    /// new async `Task`, not from the current `Task`. Errors in constructing the connection are not
39    /// guaranteed to be returned, they may be sent directly to the client end of the connection.
40    /// This method should be called from within an `ObjectRequest` handler to ensure that errors
41    /// are sent to the client end of the connection.
42    pub async fn create(
43        scope: ExecutionScope,
44        directory: Arc<DirectoryType>,
45        protocols: impl ProtocolsExt,
46        object_request: ObjectRequestRef<'_>,
47    ) -> Result<(), Status> {
48        // Ensure we close the directory if we fail to prepare the connection.
49        let directory = OpenNode::new(directory);
50
51        let connection = MutableConnection {
52            base: BaseConnection::new(scope.clone(), directory, protocols.to_directory_options()?),
53        };
54
55        if let Ok(requests) = object_request.take().into_request_stream(&connection.base).await {
56            scope.spawn(RequestListener::new(requests, Tokenizable::new(connection)));
57        }
58        Ok(())
59    }
60
61    async fn handle_request(
62        this: Pin<&mut Tokenizable<Self>>,
63        request: fio::DirectoryRequest,
64    ) -> Result<ConnectionState, Error> {
65        match request {
66            fio::DirectoryRequest::Unlink { name, options, responder } => {
67                let result = this.handle_unlink(name, options).await;
68                responder.send(result.map_err(Status::into_raw))?;
69            }
70            fio::DirectoryRequest::GetToken { responder } => {
71                let (status, token) = match Self::handle_get_token(this.into_ref()) {
72                    Ok(token) => (Status::OK, Some(token)),
73                    Err(status) => (status, None),
74                };
75                responder.send(status.into_raw(), token)?;
76            }
77            fio::DirectoryRequest::Rename { src, dst_parent_token, dst, responder } => {
78                let result = this.handle_rename(src, Handle::from(dst_parent_token), dst).await;
79                responder.send(result.map_err(Status::into_raw))?;
80            }
81            fio::DirectoryRequest::SetAttr { flags, attributes, responder } => {
82                let status = match this
83                    .handle_update_attributes(io1_to_io2_attrs(flags, attributes))
84                    .await
85                {
86                    Ok(()) => Status::OK,
87                    Err(status) => status,
88                };
89                responder.send(status.into_raw())?;
90            }
91            fio::DirectoryRequest::Sync { responder } => {
92                responder.send(this.base.directory.sync().await.map_err(Status::into_raw))?;
93            }
94            fio::DirectoryRequest::CreateSymlink {
95                responder, name, target, connection, ..
96            } => {
97                if !this.base.options.rights.contains(fio::Operations::MODIFY_DIRECTORY) {
98                    responder.send(Err(Status::ACCESS_DENIED.into_raw()))?;
99                } else if validate_name(&name).is_err() {
100                    responder.send(Err(Status::INVALID_ARGS.into_raw()))?;
101                } else {
102                    responder.send(
103                        this.base
104                            .directory
105                            .create_symlink(name, target, connection)
106                            .await
107                            .map_err(Status::into_raw),
108                    )?;
109                }
110            }
111            fio::DirectoryRequest::ListExtendedAttributes { iterator, control_handle: _ } => {
112                this.handle_list_extended_attribute(iterator)
113                    .trace(trace::trace_future_args!(
114                        c"storage",
115                        c"Directory::ListExtendedAttributes"
116                    ))
117                    .await;
118            }
119            fio::DirectoryRequest::GetExtendedAttribute { name, responder } => {
120                async move {
121                    let res =
122                        this.handle_get_extended_attribute(name).await.map_err(Status::into_raw);
123                    responder.send(res)
124                }
125                .trace(trace::trace_future_args!(c"storage", c"Directory::GetExtendedAttribute"))
126                .await?;
127            }
128            fio::DirectoryRequest::SetExtendedAttribute { name, value, mode, responder } => {
129                async move {
130                    let res = this
131                        .handle_set_extended_attribute(name, value, mode)
132                        .await
133                        .map_err(Status::into_raw);
134                    responder.send(res)
135                }
136                .trace(trace::trace_future_args!(c"storage", c"Directory::SetExtendedAttribute"))
137                .await?;
138            }
139            fio::DirectoryRequest::RemoveExtendedAttribute { name, responder } => {
140                async move {
141                    let res =
142                        this.handle_remove_extended_attribute(name).await.map_err(Status::into_raw);
143                    responder.send(res)
144                }
145                .trace(trace::trace_future_args!(c"storage", c"Directory::RemoveExtendedAttribute"))
146                .await?;
147            }
148            fio::DirectoryRequest::UpdateAttributes { payload, responder } => {
149                async move {
150                    responder.send(
151                        this.handle_update_attributes(payload).await.map_err(Status::into_raw),
152                    )
153                }
154                .trace(trace::trace_future_args!(c"storage", c"Directory::UpdateAttributes"))
155                .await?;
156            }
157            request => {
158                return this.as_mut().base.handle_request(request).await;
159            }
160        }
161        Ok(ConnectionState::Alive)
162    }
163
164    async fn handle_update_attributes(
165        &self,
166        attributes: fio::MutableNodeAttributes,
167    ) -> Result<(), Status> {
168        if !self.base.options.rights.contains(fio::Operations::UPDATE_ATTRIBUTES) {
169            return Err(Status::BAD_HANDLE);
170        }
171        // TODO(jfsulliv): Consider always permitting attributes to be deferrable. The risk with
172        // this is that filesystems would require a background flush of dirty attributes to disk.
173        self.base.directory.update_attributes(attributes).await
174    }
175
176    async fn handle_unlink(&self, name: String, options: fio::UnlinkOptions) -> Result<(), Status> {
177        if !self.base.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
178            return Err(Status::BAD_HANDLE);
179        }
180
181        if name.is_empty() || name.contains('/') || name == "." || name == ".." {
182            return Err(Status::INVALID_ARGS);
183        }
184
185        self.base
186            .directory
187            .clone()
188            .unlink(
189                &name,
190                options
191                    .flags
192                    .map(|f| f.contains(fio::UnlinkFlags::MUST_BE_DIRECTORY))
193                    .unwrap_or(false),
194            )
195            .await
196    }
197
198    fn handle_get_token(this: Pin<&Tokenizable<Self>>) -> Result<Handle, Status> {
199        // GetToken exists to support linking, so we must make sure the connection has the
200        // permission to modify the directory.
201        if !this.base.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
202            return Err(Status::BAD_HANDLE);
203        }
204        Ok(TokenRegistry::get_token(this)?)
205    }
206
207    async fn handle_rename(
208        &self,
209        src: String,
210        dst_parent_token: Handle,
211        dst: String,
212    ) -> Result<(), Status> {
213        if !self.base.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
214            return Err(Status::BAD_HANDLE);
215        }
216
217        let src = Path::validate_and_split(src)?;
218        let dst = Path::validate_and_split(dst)?;
219
220        if !src.is_single_component() || !dst.is_single_component() {
221            return Err(Status::INVALID_ARGS);
222        }
223
224        let dst_parent = match self.base.scope.token_registry().get_owner(dst_parent_token)? {
225            None => return Err(Status::NOT_FOUND),
226            Some(entry) => entry,
227        };
228
229        dst_parent.clone().rename(self.base.directory.clone(), src, dst).await
230    }
231
232    async fn handle_list_extended_attribute(
233        &self,
234        iterator: ServerEnd<fio::ExtendedAttributeIteratorMarker>,
235    ) {
236        let attributes = match self.base.directory.list_extended_attributes().await {
237            Ok(attributes) => attributes,
238            Err(status) => {
239                #[cfg(any(test, feature = "use_log"))]
240                log::error!(status:?; "list extended attributes failed");
241                #[allow(clippy::unnecessary_lazy_evaluations)]
242                iterator.close_with_epitaph(status).unwrap_or_else(|_error| {
243                    #[cfg(any(test, feature = "use_log"))]
244                    log::error!(_error:?; "failed to send epitaph")
245                });
246                return;
247            }
248        };
249        self.base.scope.spawn(extended_attributes_sender(iterator, attributes));
250    }
251
252    async fn handle_get_extended_attribute(
253        &self,
254        name: Vec<u8>,
255    ) -> Result<fio::ExtendedAttributeValue, Status> {
256        let value = self.base.directory.get_extended_attribute(name).await?;
257        encode_extended_attribute_value(value)
258    }
259
260    async fn handle_set_extended_attribute(
261        &self,
262        name: Vec<u8>,
263        value: fio::ExtendedAttributeValue,
264        mode: fio::SetExtendedAttributeMode,
265    ) -> Result<(), Status> {
266        if name.contains(&0) {
267            return Err(Status::INVALID_ARGS);
268        }
269        let val = decode_extended_attribute_value(value)?;
270        self.base.directory.set_extended_attribute(name, val, mode).await
271    }
272
273    async fn handle_remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), Status> {
274        self.base.directory.remove_extended_attribute(name).await
275    }
276}
277
278impl<DirectoryType: MutableDirectory> ConnectionCreator<DirectoryType>
279    for MutableConnection<DirectoryType>
280{
281    async fn create<'a>(
282        scope: ExecutionScope,
283        node: Arc<DirectoryType>,
284        protocols: impl ProtocolsExt,
285        object_request: ObjectRequestRef<'a>,
286    ) -> Result<(), Status> {
287        Self::create(scope, node, protocols, object_request).await
288    }
289}
290
291impl<DirectoryType: MutableDirectory> RequestHandler
292    for Tokenizable<MutableConnection<DirectoryType>>
293{
294    type Request = Result<fio::DirectoryRequest, fidl::Error>;
295
296    async fn handle_request(self: Pin<&mut Self>, request: Self::Request) -> ControlFlow<()> {
297        let _guard = self.base.scope.active_guard();
298        match request {
299            Ok(request) => {
300                match MutableConnection::<DirectoryType>::handle_request(self, request).await {
301                    Ok(ConnectionState::Alive) => ControlFlow::Continue(()),
302                    Ok(ConnectionState::Closed) | Err(_) => ControlFlow::Break(()),
303                }
304            }
305            Err(_) => ControlFlow::Break(()),
306        }
307    }
308}
309
310impl<DirectoryType: MutableDirectory> TokenInterface for MutableConnection<DirectoryType> {
311    fn get_node(&self) -> Arc<dyn MutableDirectory> {
312        self.base.directory.clone()
313    }
314
315    fn token_registry(&self) -> &TokenRegistry {
316        self.base.scope.token_registry()
317    }
318}
319
320#[cfg(test)]
321mod tests {
322    use super::*;
323    use crate::directory::dirents_sink;
324    use crate::directory::entry::{EntryInfo, GetEntryInfo};
325    use crate::directory::entry_container::{Directory, DirectoryWatcher};
326    use crate::directory::traversal_position::TraversalPosition;
327    use crate::node::Node;
328    use crate::ToObjectRequest;
329    use fuchsia_sync::Mutex;
330    use futures::future::BoxFuture;
331    use std::any::Any;
332    use std::future::ready;
333    use std::sync::Weak;
334
335    #[derive(Debug, PartialEq)]
336    enum MutableDirectoryAction {
337        Link { id: u32, path: String },
338        Unlink { id: u32, name: String },
339        Rename { id: u32, src_name: String, dst_dir: u32, dst_name: String },
340        UpdateAttributes { id: u32, attributes: fio::MutableNodeAttributes },
341        Sync,
342        Close,
343    }
344
345    #[derive(Debug)]
346    struct MockDirectory {
347        id: u32,
348        fs: Arc<MockFilesystem>,
349    }
350
351    impl MockDirectory {
352        pub fn new(id: u32, fs: Arc<MockFilesystem>) -> Arc<Self> {
353            Arc::new(MockDirectory { id, fs })
354        }
355    }
356
357    impl PartialEq for MockDirectory {
358        fn eq(&self, other: &Self) -> bool {
359            self.id == other.id
360        }
361    }
362
363    impl GetEntryInfo for MockDirectory {
364        fn entry_info(&self) -> EntryInfo {
365            EntryInfo::new(0, fio::DirentType::Directory)
366        }
367    }
368
369    impl Node for MockDirectory {
370        async fn get_attributes(
371            &self,
372            _query: fio::NodeAttributesQuery,
373        ) -> Result<fio::NodeAttributes2, Status> {
374            unimplemented!("Not implemented");
375        }
376
377        fn close(self: Arc<Self>) {
378            let _ = self.fs.handle_event(MutableDirectoryAction::Close);
379        }
380    }
381
382    impl Directory for MockDirectory {
383        fn deprecated_open(
384            self: Arc<Self>,
385            _scope: ExecutionScope,
386            _flags: fio::OpenFlags,
387            _path: Path,
388            _server_end: ServerEnd<fio::NodeMarker>,
389        ) {
390            unimplemented!("Not implemented!");
391        }
392
393        fn open(
394            self: Arc<Self>,
395            _scope: ExecutionScope,
396            _path: Path,
397            _flags: fio::Flags,
398            _object_request: ObjectRequestRef<'_>,
399        ) -> Result<(), Status> {
400            unimplemented!("Not implemented!");
401        }
402
403        async fn read_dirents<'a>(
404            &'a self,
405            _pos: &'a TraversalPosition,
406            _sink: Box<dyn dirents_sink::Sink>,
407        ) -> Result<(TraversalPosition, Box<dyn dirents_sink::Sealed>), Status> {
408            unimplemented!("Not implemented");
409        }
410
411        fn register_watcher(
412            self: Arc<Self>,
413            _scope: ExecutionScope,
414            _mask: fio::WatchMask,
415            _watcher: DirectoryWatcher,
416        ) -> Result<(), Status> {
417            unimplemented!("Not implemented");
418        }
419
420        fn unregister_watcher(self: Arc<Self>, _key: usize) {
421            unimplemented!("Not implemented");
422        }
423    }
424
425    impl MutableDirectory for MockDirectory {
426        fn link<'a>(
427            self: Arc<Self>,
428            path: String,
429            _source_dir: Arc<dyn Any + Send + Sync>,
430            _source_name: &'a str,
431        ) -> BoxFuture<'a, Result<(), Status>> {
432            let result = self.fs.handle_event(MutableDirectoryAction::Link { id: self.id, path });
433            Box::pin(ready(result))
434        }
435
436        async fn unlink(
437            self: Arc<Self>,
438            name: &str,
439            _must_be_directory: bool,
440        ) -> Result<(), Status> {
441            self.fs.handle_event(MutableDirectoryAction::Unlink {
442                id: self.id,
443                name: name.to_string(),
444            })
445        }
446
447        async fn update_attributes(
448            &self,
449            attributes: fio::MutableNodeAttributes,
450        ) -> Result<(), Status> {
451            self.fs
452                .handle_event(MutableDirectoryAction::UpdateAttributes { id: self.id, attributes })
453        }
454
455        async fn sync(&self) -> Result<(), Status> {
456            self.fs.handle_event(MutableDirectoryAction::Sync)
457        }
458
459        fn rename(
460            self: Arc<Self>,
461            src_dir: Arc<dyn MutableDirectory>,
462            src_name: Path,
463            dst_name: Path,
464        ) -> BoxFuture<'static, Result<(), Status>> {
465            let src_dir = src_dir.into_any().downcast::<MockDirectory>().unwrap();
466            let result = self.fs.handle_event(MutableDirectoryAction::Rename {
467                id: src_dir.id,
468                src_name: src_name.into_string(),
469                dst_dir: self.id,
470                dst_name: dst_name.into_string(),
471            });
472            Box::pin(ready(result))
473        }
474    }
475
476    struct Events(Mutex<Vec<MutableDirectoryAction>>);
477
478    impl Events {
479        fn new() -> Arc<Self> {
480            Arc::new(Events(Mutex::new(vec![])))
481        }
482    }
483
484    struct MockFilesystem {
485        cur_id: Mutex<u32>,
486        scope: ExecutionScope,
487        events: Weak<Events>,
488    }
489
490    impl MockFilesystem {
491        pub fn new(events: &Arc<Events>) -> Self {
492            let scope = ExecutionScope::new();
493            MockFilesystem { cur_id: Mutex::new(0), scope, events: Arc::downgrade(events) }
494        }
495
496        pub fn handle_event(&self, event: MutableDirectoryAction) -> Result<(), Status> {
497            self.events.upgrade().map(|x| x.0.lock().push(event));
498            Ok(())
499        }
500
501        pub fn make_connection(
502            self: &Arc<Self>,
503            flags: fio::OpenFlags,
504        ) -> (Arc<MockDirectory>, fio::DirectoryProxy) {
505            let mut cur_id = self.cur_id.lock();
506            let dir = MockDirectory::new(*cur_id, self.clone());
507            *cur_id += 1;
508            let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
509            flags.to_object_request(server_end).create_connection_sync::<MutableConnection<_>, _>(
510                self.scope.clone(),
511                dir.clone(),
512                flags,
513            );
514            (dir, proxy)
515        }
516    }
517
518    impl std::fmt::Debug for MockFilesystem {
519        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
520            f.debug_struct("MockFilesystem").field("cur_id", &self.cur_id).finish()
521        }
522    }
523
524    #[fuchsia::test]
525    async fn test_rename() {
526        use fidl::Event;
527
528        let events = Events::new();
529        let fs = Arc::new(MockFilesystem::new(&events));
530
531        let (_dir, proxy) = fs
532            .clone()
533            .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
534        let (dir2, proxy2) = fs
535            .clone()
536            .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
537
538        let (status, token) = proxy2.get_token().await.unwrap();
539        assert_eq!(Status::from_raw(status), Status::OK);
540
541        let status = proxy.rename("src", Event::from(token.unwrap()), "dest").await.unwrap();
542        assert!(status.is_ok());
543
544        let events = events.0.lock();
545        assert_eq!(
546            *events,
547            vec![MutableDirectoryAction::Rename {
548                id: 0,
549                src_name: "src".to_owned(),
550                dst_dir: dir2.id,
551                dst_name: "dest".to_owned(),
552            },]
553        );
554    }
555
556    #[fuchsia::test]
557    async fn test_update_attributes() {
558        let events = Events::new();
559        let fs = Arc::new(MockFilesystem::new(&events));
560        let (_dir, proxy) = fs
561            .clone()
562            .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
563        let attributes = fio::MutableNodeAttributes {
564            creation_time: Some(30),
565            modification_time: Some(100),
566            mode: Some(200),
567            ..Default::default()
568        };
569        proxy
570            .update_attributes(&attributes)
571            .await
572            .expect("FIDL call failed")
573            .map_err(Status::from_raw)
574            .expect("update attributes failed");
575
576        let events = events.0.lock();
577        assert_eq!(*events, vec![MutableDirectoryAction::UpdateAttributes { id: 0, attributes }]);
578    }
579
580    #[fuchsia::test]
581    async fn test_link() {
582        let events = Events::new();
583        let fs = Arc::new(MockFilesystem::new(&events));
584        let (_dir, proxy) = fs
585            .clone()
586            .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
587        let (_dir2, proxy2) = fs
588            .clone()
589            .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
590
591        let (status, token) = proxy2.get_token().await.unwrap();
592        assert_eq!(Status::from_raw(status), Status::OK);
593
594        let status = proxy.link("src", token.unwrap(), "dest").await.unwrap();
595        assert_eq!(Status::from_raw(status), Status::OK);
596        let events = events.0.lock();
597        assert_eq!(*events, vec![MutableDirectoryAction::Link { id: 1, path: "dest".to_owned() },]);
598    }
599
600    #[fuchsia::test]
601    async fn test_unlink() {
602        let events = Events::new();
603        let fs = Arc::new(MockFilesystem::new(&events));
604        let (_dir, proxy) = fs
605            .clone()
606            .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
607        proxy
608            .unlink("test", &fio::UnlinkOptions::default())
609            .await
610            .expect("fidl call failed")
611            .expect("unlink failed");
612        let events = events.0.lock();
613        assert_eq!(
614            *events,
615            vec![MutableDirectoryAction::Unlink { id: 0, name: "test".to_string() },]
616        );
617    }
618
619    #[fuchsia::test]
620    async fn test_sync() {
621        let events = Events::new();
622        let fs = Arc::new(MockFilesystem::new(&events));
623        let (_dir, proxy) = fs
624            .clone()
625            .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
626        let () = proxy.sync().await.unwrap().map_err(Status::from_raw).unwrap();
627        let events = events.0.lock();
628        assert_eq!(*events, vec![MutableDirectoryAction::Sync]);
629    }
630
631    #[fuchsia::test]
632    async fn test_close() {
633        let events = Events::new();
634        let fs = Arc::new(MockFilesystem::new(&events));
635        let (_dir, proxy) = fs
636            .clone()
637            .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
638        let () = proxy.close().await.unwrap().map_err(Status::from_raw).unwrap();
639        let events = events.0.lock();
640        assert_eq!(*events, vec![MutableDirectoryAction::Close]);
641    }
642
643    #[fuchsia::test]
644    async fn test_implicit_close() {
645        let events = Events::new();
646        let fs = Arc::new(MockFilesystem::new(&events));
647        let (_dir, _proxy) = fs
648            .clone()
649            .make_connection(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
650
651        fs.scope.shutdown();
652        fs.scope.wait().await;
653
654        let events = events.0.lock();
655        assert_eq!(*events, vec![MutableDirectoryAction::Close]);
656    }
657}