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