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 fidl::endpoints::ServerEnd;
30use fidl_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    use fidl::endpoints::create_endpoints;
403
404    #[test]
405    fn add_entry_success() {
406        let dir = Simple::new();
407        assert_eq!(
408            dir.add_entry("path_without_separators", file::read_only(b"test")),
409            Ok(()),
410            "add entry with valid filename should succeed"
411        );
412    }
413
414    #[test]
415    fn add_entry_error_name_with_path_separator() {
416        let dir = Simple::new();
417        let status = dir
418            .add_entry("path/with/separators", file::read_only(b"test"))
419            .expect_err("add entry with path separator should fail");
420        assert_eq!(status, Status::INVALID_ARGS);
421    }
422
423    #[test]
424    fn add_entry_error_name_too_long() {
425        let dir = Simple::new();
426        let status = dir
427            .add_entry("a".repeat(10000), file::read_only(b"test"))
428            .expect_err("add entry whose name is too long should fail");
429        assert_eq!(status, Status::BAD_PATH);
430    }
431
432    #[fuchsia::test]
433    async fn not_found_handler() {
434        let path_mutex = Arc::new(Mutex::new(None));
435        let path_mutex_clone = path_mutex.clone();
436        let dir = Simple::new_with_not_found_handler(move |path| {
437            *path_mutex_clone.lock() = Some(path.to_string());
438        });
439
440        let path_mutex_clone = path_mutex.clone();
441        let sub_dir = Simple::new_with_not_found_handler(move |path| {
442            *path_mutex_clone.lock() = Some(path.to_string());
443        });
444        dir.add_entry("dir", sub_dir).expect("add entry with valid filename should succeed");
445
446        dir.add_entry("file", file::read_only(b"test"))
447            .expect("add entry with valid filename should succeed");
448
449        let scope = ExecutionScope::new();
450
451        for (path, expectation) in vec![
452            (".", None),
453            ("does-not-exist", Some("does-not-exist".to_string())),
454            ("file", None),
455            ("dir", None),
456            ("dir/does-not-exist", Some("dir/does-not-exist".to_string())),
457        ] {
458            log::info!("{path}");
459            let (_proxy, server_end) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
460            let flags = fio::Flags::PROTOCOL_NODE | fio::Flags::FLAG_SEND_REPRESENTATION;
461            let path = Path::validate_and_split(path).unwrap();
462            ObjectRequest::new(flags, &fio::Options::default(), server_end.into())
463                .handle(|request| dir.clone().open(scope.clone(), path, flags, request));
464
465            assert_eq!(expectation, path_mutex.lock().take());
466        }
467    }
468
469    #[test]
470    fn remove_all_entries() {
471        let dir = Simple::new();
472
473        dir.add_entry("file", file::read_only(""))
474            .expect("add entry with valid filename should succeed");
475
476        dir.remove_all_entries();
477        assert_eq!(
478            dir.get_entry("file").err().expect("file should no longer exist"),
479            Status::NOT_FOUND
480        );
481    }
482
483    #[fuchsia::test]
484    async fn test_alternate_scope() {
485        struct MockEntry(ExecutionScope);
486
487        impl DirectoryEntry for MockEntry {
488            fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
489                assert_eq!(request.scope(), &self.0);
490                Ok(())
491            }
492
493            fn scope(&self) -> Option<ExecutionScope> {
494                Some(self.0.clone())
495            }
496        }
497
498        impl GetEntryInfo for MockEntry {
499            fn entry_info(&self) -> EntryInfo {
500                EntryInfo::new(1, fio::DirentType::Directory)
501            }
502        }
503
504        let dir = Simple::new();
505
506        dir.add_entry("foo", Arc::new(MockEntry(ExecutionScope::new()))).expect("add_entry failed");
507
508        let (_client, server) = create_endpoints::<fio::DirectoryMarker>();
509        let mut request =
510            ObjectRequest::new(fio::Flags::empty(), &fio::Options::default(), server.into());
511        dir.open(ExecutionScope::new(), Path::dot(), fio::Flags::empty(), &mut request)
512            .expect("open succeeded");
513    }
514}