Skip to main content

vfs/directory/
simple.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//! This is an implementation of "simple" pseudo directories.
6//! Use [`crate::directory::immutable::Simple::new()`]
7//! to construct actual instances.  See [`Simple`] for details.
8
9use crate::ObjectRequestRef;
10#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
11use crate::ToObjectRequest;
12use crate::common::CreationMode;
13use crate::directory::dirents_sink;
14use crate::directory::entry::{DirectoryEntry, EntryInfo, OpenRequest, RequestFlags};
15use crate::directory::entry_container::{Directory, DirectoryWatcher};
16use crate::directory::helper::{AlreadyExists, DirectlyMutable, NotDirectory};
17use crate::directory::immutable::connection::ImmutableConnection;
18use crate::directory::traversal_position::TraversalPosition;
19use crate::directory::watchers::Watchers;
20use crate::directory::watchers::event_producers::{
21    SingleNameEventProducer, StaticVecEventProducer,
22};
23use crate::execution_scope::ExecutionScope;
24use crate::name::Name;
25use crate::node::Node;
26use crate::path::Path;
27use crate::protocols::ProtocolsExt;
28#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
29use flex_client::fidl::ServerEnd;
30use flex_fuchsia_io as fio;
31use fuchsia_sync::Mutex;
32use std::collections::BTreeMap;
33use std::collections::btree_map::Entry;
34use std::iter;
35use std::sync::Arc;
36use zx_status::Status;
37
38use super::entry::GetEntryInfo;
39
40/// An implementation of a "simple" pseudo directory.  This directory holds a set of entries,
41/// allowing the server to add or remove entries via the
42/// [`crate::directory::helper::DirectlyMutable::add_entry()`] and
43/// [`crate::directory::helper::DirectlyMutable::remove_entry`] methods.
44pub struct Simple {
45    inner: Mutex<Inner>,
46
47    // The inode for this directory. This should either be unique within this VFS, or INO_UNKNOWN.
48    inode: u64,
49
50    not_found_handler: Option<Box<dyn Fn(&str) + Send + Sync + 'static>>,
51}
52
53struct Inner {
54    entries: BTreeMap<Name, Arc<dyn DirectoryEntry>>,
55
56    watchers: Watchers,
57}
58
59impl Simple {
60    pub fn new() -> Arc<Self> {
61        Self::new_with_inode(fio::INO_UNKNOWN)
62    }
63
64    pub(crate) fn new_with_inode(inode: u64) -> Arc<Self> {
65        Arc::new(Simple {
66            inner: Mutex::new(Inner { entries: BTreeMap::new(), watchers: Watchers::new() }),
67            inode,
68            not_found_handler: None,
69        })
70    }
71
72    /// Creates a new directory with the provided function that will be called whenever this VFS
73    /// receives an open request for a path that is not present in the directory. The handler is
74    /// invoked with the full path of the missing entry, relative to the root.
75    pub fn new_with_not_found_handler(handler: impl Fn(&str) + Send + Sync + 'static) -> Arc<Self> {
76        Arc::new(Simple {
77            inner: Mutex::new(Inner { entries: BTreeMap::new(), watchers: Watchers::new() }),
78            inode: fio::INO_UNKNOWN,
79            not_found_handler: Some(Box::new(handler)),
80        })
81    }
82
83    /// Returns the entry identified by `name`.
84    pub fn get_entry(&self, name: &str) -> Result<Arc<dyn DirectoryEntry>, Status> {
85        crate::name::validate_name(name)?;
86
87        let this = self.inner.lock();
88        match this.entries.get(name) {
89            Some(entry) => Ok(entry.clone()),
90            None => Err(Status::NOT_FOUND),
91        }
92    }
93
94    /// Gets or inserts an entry (as supplied by the callback `f`).
95    pub fn get_or_insert<T: DirectoryEntry>(
96        &self,
97        name: Name,
98        f: impl FnOnce() -> Arc<T>,
99    ) -> Arc<dyn DirectoryEntry> {
100        let mut guard = self.inner.lock();
101        let inner = &mut *guard;
102        match inner.entries.entry(name) {
103            Entry::Vacant(slot) => {
104                inner.watchers.send_event(&mut SingleNameEventProducer::added(slot.key()));
105                slot.insert(f()).clone()
106            }
107            Entry::Occupied(entry) => entry.get().clone(),
108        }
109    }
110
111    /// Removes all entries from the directory.
112    pub fn remove_all_entries(&self) {
113        let mut inner = self.inner.lock();
114        if !inner.entries.is_empty() {
115            let names = std::mem::take(&mut inner.entries)
116                .into_keys()
117                .map(String::from)
118                .collect::<Vec<String>>();
119            inner.watchers.send_event(&mut StaticVecEventProducer::removed(names));
120        }
121    }
122
123    fn open_impl<'a, P: ProtocolsExt + ToRequestFlags>(
124        self: Arc<Self>,
125        mut scope: ExecutionScope,
126        mut path: Path,
127        protocols: P,
128        object_request: ObjectRequestRef<'_>,
129    ) -> Result<(), Status> {
130        // See if the path has a next segment, if so we want to traverse down the directory.
131        // Otherwise we've arrived at the right directory.
132        let (name, path_ref) = match path.next_with_ref() {
133            (path_ref, Some(name)) => (name, path_ref),
134            (_, None) => {
135                if protocols.create_unnamed_temporary_in_directory_path() {
136                    // Creating an entry is not supported.
137                    return Err(Status::NOT_SUPPORTED);
138                }
139                object_request
140                    .take()
141                    .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, protocols);
142                return Ok(());
143            }
144        };
145
146        // Don't hold the inner lock while opening the entry in case the directory contains itself.
147        let _guard;
148        let entry = match self.inner.lock().entries.get(name) {
149            Some(entry) => {
150                // Whilst we are holding the lock, see if an alternative scope should be used.
151                if let Some(s) = entry.scope() {
152                    // Make sure we can get an active guard.
153                    let Some(g) = s.try_active_guard() else {
154                        return Err(Status::PEER_CLOSED);
155                    };
156                    scope = s;
157                    _guard = g;
158                }
159                Some(entry.clone())
160            }
161            None => None,
162        };
163
164        match (entry, path_ref.is_empty(), protocols.creation_mode()) {
165            (None, false, _) | (None, true, CreationMode::Never) => {
166                // Either:
167                //   - we're at an intermediate directory and the next entry doesn't exist, or
168                //   - we're at the last directory and the next entry doesn't exist and creating the
169                //     entry wasn't requested.
170                if let Some(not_found_handler) = &self.not_found_handler {
171                    not_found_handler(path_ref.as_str());
172                }
173                Err(Status::NOT_FOUND)
174            }
175            (
176                None,
177                true,
178                CreationMode::Always
179                | CreationMode::AllowExisting
180                | CreationMode::UnnamedTemporary
181                | CreationMode::UnlinkableUnnamedTemporary,
182            ) => {
183                // We're at the last directory and the entry doesn't exist and creating the entry
184                // was requested which isn't supported.
185                Err(Status::NOT_SUPPORTED)
186            }
187            (
188                Some(_),
189                true,
190                CreationMode::UnnamedTemporary | CreationMode::UnlinkableUnnamedTemporary,
191            ) => {
192                // We're at the last directory and the entry exists and it was requested to create
193                // an unnamed temporary object in this entry (this is not supported for simple
194                // pseudo directory).
195                Err(Status::NOT_SUPPORTED)
196            }
197            (Some(_), true, CreationMode::Always) => {
198                // We're at the last directory and the entry exists but creating the entry is
199                // required.
200                Err(Status::ALREADY_EXISTS)
201            }
202            (Some(entry), _, _) => entry.open_entry(OpenRequest::new(
203                scope,
204                protocols.to_request_flags(),
205                path,
206                object_request,
207            )),
208        }
209    }
210}
211
212impl GetEntryInfo for Simple {
213    fn entry_info(&self) -> EntryInfo {
214        EntryInfo::new(self.inode, fio::DirentType::Directory)
215    }
216}
217
218impl DirectoryEntry for Simple {
219    fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
220        request.open_dir(self)
221    }
222}
223
224impl Node for Simple {
225    async fn get_attributes(
226        &self,
227        requested_attributes: fio::NodeAttributesQuery,
228    ) -> Result<fio::NodeAttributes2, Status> {
229        Ok(immutable_attributes!(
230            requested_attributes,
231            Immutable {
232                protocols: fio::NodeProtocolKinds::DIRECTORY,
233                abilities: fio::Operations::GET_ATTRIBUTES
234                    | fio::Operations::ENUMERATE
235                    | fio::Operations::TRAVERSE,
236                id: self.inode,
237            }
238        ))
239    }
240}
241
242impl Directory for Simple {
243    #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
244    fn deprecated_open(
245        self: Arc<Self>,
246        scope: ExecutionScope,
247        flags: fio::OpenFlags,
248        path: Path,
249        server_end: ServerEnd<fio::NodeMarker>,
250    ) {
251        flags
252            .to_object_request(server_end)
253            .handle(|object_request| self.open_impl(scope, path, flags, object_request));
254    }
255
256    fn open(
257        self: Arc<Self>,
258        scope: ExecutionScope,
259        path: Path,
260        flags: fio::Flags,
261        object_request: ObjectRequestRef<'_>,
262    ) -> Result<(), Status> {
263        self.open_impl(scope, path, flags, object_request)
264    }
265
266    async fn read_dirents(
267        &self,
268        pos: &TraversalPosition,
269        sink: Box<dyn dirents_sink::Sink>,
270    ) -> Result<(TraversalPosition, Box<dyn dirents_sink::Sealed>), Status> {
271        use dirents_sink::AppendResult;
272
273        let this = self.inner.lock();
274
275        let (mut sink, entries_iter) = match pos {
276            TraversalPosition::Start => {
277                match sink.append(&EntryInfo::new(self.inode, fio::DirentType::Directory), ".") {
278                    AppendResult::Ok(sink) => (sink, this.entries.range::<Name, _>(..)),
279                    AppendResult::Sealed(sealed) => {
280                        return Ok((TraversalPosition::Start, sealed));
281                    }
282                }
283            }
284
285            TraversalPosition::Name(next_name) => {
286                // The only way to get a `TraversalPosition::Name` is if we returned it in the
287                // `AppendResult::Sealed` code path below. Therefore, the conversion from
288                // `next_name` to `Name` will never fail in practice.
289                let next: Name = next_name.to_owned().try_into().unwrap();
290                (sink, this.entries.range::<Name, _>(next..))
291            }
292
293            TraversalPosition::Bytes(_) | TraversalPosition::Index(_) => unreachable!(),
294
295            TraversalPosition::End => return Ok((TraversalPosition::End, sink.seal())),
296        };
297
298        for (name, entry) in entries_iter {
299            match sink.append(&entry.entry_info(), &name) {
300                AppendResult::Ok(new_sink) => sink = new_sink,
301                AppendResult::Sealed(sealed) => {
302                    return Ok((TraversalPosition::Name(name.clone().into()), sealed));
303                }
304            }
305        }
306
307        Ok((TraversalPosition::End, sink.seal()))
308    }
309
310    fn register_watcher(
311        self: Arc<Self>,
312        scope: ExecutionScope,
313        mask: fio::WatchMask,
314        watcher: DirectoryWatcher,
315    ) -> Result<(), Status> {
316        let mut this = self.inner.lock();
317
318        let mut names = StaticVecEventProducer::existing({
319            let entry_names = this.entries.keys();
320            iter::once(".".to_string()).chain(entry_names.map(|x| x.to_owned().into())).collect()
321        });
322
323        let controller = this.watchers.add(scope, self.clone(), mask, watcher);
324        controller.send_event(&mut names);
325        controller.send_event(&mut SingleNameEventProducer::idle());
326
327        Ok(())
328    }
329
330    fn unregister_watcher(self: Arc<Self>, key: usize) {
331        let mut this = self.inner.lock();
332        this.watchers.remove(key);
333    }
334}
335
336impl DirectlyMutable for Simple {
337    fn add_entry_impl(
338        &self,
339        name: Name,
340        entry: Arc<dyn DirectoryEntry>,
341        overwrite: bool,
342    ) -> Result<(), AlreadyExists> {
343        let mut this = self.inner.lock();
344
345        if !overwrite && this.entries.contains_key(&name) {
346            return Err(AlreadyExists);
347        }
348
349        this.watchers.send_event(&mut SingleNameEventProducer::added(&name));
350
351        let _ = this.entries.insert(name, entry);
352        Ok(())
353    }
354
355    fn remove_entry_impl(
356        &self,
357        name: Name,
358        must_be_directory: bool,
359    ) -> Result<Option<Arc<dyn DirectoryEntry>>, NotDirectory> {
360        let mut this = self.inner.lock();
361
362        match this.entries.entry(name) {
363            Entry::Vacant(_) => Ok(None),
364            Entry::Occupied(occupied) => {
365                if must_be_directory
366                    && occupied.get().entry_info().type_() != fio::DirentType::Directory
367                {
368                    Err(NotDirectory)
369                } else {
370                    let (key, value) = occupied.remove_entry();
371                    this.watchers.send_event(&mut SingleNameEventProducer::removed(&key));
372                    Ok(Some(value))
373                }
374            }
375        }
376    }
377}
378
379trait ToRequestFlags {
380    fn to_request_flags(&self) -> RequestFlags;
381}
382
383#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
384impl ToRequestFlags for fio::OpenFlags {
385    fn to_request_flags(&self) -> RequestFlags {
386        RequestFlags::Open1(*self)
387    }
388}
389
390impl ToRequestFlags for fio::Flags {
391    fn to_request_flags(&self) -> RequestFlags {
392        RequestFlags::Open3(*self)
393    }
394}
395
396#[cfg(test)]
397mod tests {
398    use super::*;
399    use crate::directory::immutable::Simple;
400    use crate::file;
401    use crate::object_request::ObjectRequest;
402
403    #[test]
404    fn add_entry_success() {
405        let dir = Simple::new();
406        assert_eq!(
407            dir.add_entry("path_without_separators", file::read_only(b"test")),
408            Ok(()),
409            "add entry with valid filename should succeed"
410        );
411    }
412
413    #[test]
414    fn add_entry_error_name_with_path_separator() {
415        let dir = Simple::new();
416        let status = dir
417            .add_entry("path/with/separators", file::read_only(b"test"))
418            .expect_err("add entry with path separator should fail");
419        assert_eq!(status, Status::INVALID_ARGS);
420    }
421
422    #[test]
423    fn add_entry_error_name_too_long() {
424        let dir = Simple::new();
425        let status = dir
426            .add_entry("a".repeat(10000), file::read_only(b"test"))
427            .expect_err("add entry whose name is too long should fail");
428        assert_eq!(status, Status::BAD_PATH);
429    }
430
431    #[fuchsia::test]
432    async fn not_found_handler() {
433        let path_mutex = Arc::new(Mutex::new(None));
434        let path_mutex_clone = path_mutex.clone();
435        let dir = Simple::new_with_not_found_handler(move |path| {
436            *path_mutex_clone.lock() = Some(path.to_string());
437        });
438
439        let path_mutex_clone = path_mutex.clone();
440        let sub_dir = Simple::new_with_not_found_handler(move |path| {
441            *path_mutex_clone.lock() = Some(path.to_string());
442        });
443        dir.add_entry("dir", sub_dir).expect("add entry with valid filename should succeed");
444
445        dir.add_entry("file", file::read_only(b"test"))
446            .expect("add entry with valid filename should succeed");
447
448        #[cfg(feature = "fdomain")]
449        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
450        #[cfg(not(feature = "fdomain"))]
451        let scope = crate::execution_scope::ExecutionScope::new();
452
453        for (path, expectation) in vec![
454            (".", None),
455            ("does-not-exist", Some("does-not-exist".to_string())),
456            ("file", None),
457            ("dir", None),
458            ("dir/does-not-exist", Some("dir/does-not-exist".to_string())),
459        ] {
460            log::info!("{path}");
461            #[cfg(feature = "fdomain")]
462            let (_proxy, server_end) = {
463                let client = scope.domain();
464                client.create_proxy::<fio::NodeMarker>()
465            };
466            #[cfg(not(feature = "fdomain"))]
467            let (_proxy, server_end) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
468            let flags = fio::Flags::PROTOCOL_NODE | fio::Flags::FLAG_SEND_REPRESENTATION;
469            let path = Path::validate_and_split(path).unwrap();
470            ObjectRequest::new(flags, &fio::Options::default(), server_end.into_channel().into())
471                .handle(|request| dir.clone().open(scope.clone(), path, flags, request));
472
473            assert_eq!(expectation, path_mutex.lock().take());
474        }
475    }
476
477    #[test]
478    fn remove_all_entries() {
479        let dir = Simple::new();
480
481        dir.add_entry("file", file::read_only(""))
482            .expect("add entry with valid filename should succeed");
483
484        dir.remove_all_entries();
485        assert_eq!(
486            dir.get_entry("file").err().expect("file should no longer exist"),
487            Status::NOT_FOUND
488        );
489    }
490
491    #[fuchsia::test]
492    async fn test_alternate_scope() {
493        struct MockEntry(ExecutionScope);
494
495        impl DirectoryEntry for MockEntry {
496            fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
497                assert_eq!(request.scope(), &self.0);
498                Ok(())
499            }
500
501            fn scope(&self) -> Option<ExecutionScope> {
502                Some(self.0.clone())
503            }
504        }
505
506        impl GetEntryInfo for MockEntry {
507            fn entry_info(&self) -> EntryInfo {
508                EntryInfo::new(1, fio::DirentType::Directory)
509            }
510        }
511
512        let dir = Simple::new();
513
514        #[cfg(feature = "fdomain")]
515        let dummy_scope =
516            crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
517        #[cfg(not(feature = "fdomain"))]
518        let dummy_scope = crate::execution_scope::ExecutionScope::new();
519
520        dir.add_entry("foo", Arc::new(MockEntry(dummy_scope))).expect("add_entry failed");
521
522        #[cfg(feature = "fdomain")]
523        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
524        #[cfg(not(feature = "fdomain"))]
525        let scope = crate::execution_scope::ExecutionScope::new();
526        #[cfg(feature = "fdomain")]
527        let (_proxy, server) = scope.domain().create_proxy::<fio::DirectoryMarker>();
528        #[cfg(not(feature = "fdomain"))]
529        let (_client, server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
530        let mut request = ObjectRequest::new(
531            fio::Flags::empty(),
532            &fio::Options::default(),
533            server.into_channel().into(),
534        );
535        dir.open(scope, Path::dot(), fio::Flags::empty(), &mut request).expect("open succeeded");
536    }
537}