vfs/directory/
read_dirents.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//! A sink that can consume directory entry information, encoding them as expected by `fuchsia.io`
6//! `Directory::ReadDirents` result.
7
8use crate::directory::common::encode_dirent;
9use crate::directory::dirents_sink::{self, AppendResult};
10use crate::directory::entry::EntryInfo;
11use crate::directory::traversal_position::TraversalPosition;
12
13use fidl_fuchsia_io as fio;
14use std::any::Any;
15use std::convert::TryInto as _;
16use zx_status::Status;
17
18/// An instance of this type represents a sink that may still accept additional entries.  Depending
19/// on the entry size it may turn itself into a [`Done`] value, indicating that the internal buffer
20/// is full.
21pub struct Sink {
22    buf: Vec<u8>,
23    max_bytes: u64,
24    state: SinkState,
25}
26
27/// An instance of this type is a `ReadDirents` result sink that is full and may not consume any
28/// more values.
29pub struct Done {
30    pub(super) buf: Vec<u8>,
31    pub(super) status: Status,
32}
33
34#[derive(PartialEq, Eq)]
35enum SinkState {
36    NotCalled,
37    DidNotFit,
38    FitOne,
39}
40
41impl Sink {
42    /// Constructs a new sync that will have the specified number of bytes of storage.
43    pub(super) fn new(max_bytes: u64) -> Box<Sink> {
44        Box::new(Sink { buf: vec![], max_bytes, state: SinkState::NotCalled })
45    }
46}
47
48impl dirents_sink::Sink for Sink {
49    fn append(mut self: Box<Self>, entry: &EntryInfo, name: &str) -> AppendResult {
50        if !encode_dirent(&mut self.buf, self.max_bytes, entry, name) {
51            if self.state == SinkState::NotCalled {
52                self.state = SinkState::DidNotFit;
53            }
54            AppendResult::Sealed(self.seal())
55        } else {
56            if self.state == SinkState::NotCalled {
57                self.state = SinkState::FitOne;
58            }
59            AppendResult::Ok(self)
60        }
61    }
62
63    fn seal(self: Box<Self>) -> Box<dyn dirents_sink::Sealed> {
64        Box::new(Done {
65            buf: self.buf,
66            status: match self.state {
67                SinkState::NotCalled | SinkState::FitOne => Status::OK,
68                SinkState::DidNotFit => Status::BUFFER_TOO_SMALL,
69            },
70        })
71    }
72}
73
74impl dirents_sink::Sealed for Done {
75    fn open(self: Box<Self>) -> Box<dyn Any> {
76        self
77    }
78}
79
80/// Helper for implementing `crate::directory::entry_container::Directory::read_dirents`.
81///
82/// `entries` must be the contents of the directory sorted by name, the second tuple element.
83/// `entries` must not contain ".". This fn will append "." to `sink` as the first element
84///   automatically using inode `fidl_fuchsia_io::INO_UNKNOWN`.
85pub async fn read_dirents<'a>(
86    entries: &'a [(EntryInfo, String)],
87    pos: &'a TraversalPosition,
88    mut sink: Box<(dyn dirents_sink::Sink + 'static)>,
89) -> Result<(TraversalPosition, Box<(dyn dirents_sink::Sealed + 'static)>), Status> {
90    let starting_position = match pos {
91        TraversalPosition::Start => {
92            match sink.append(&EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory), ".") {
93                AppendResult::Ok(new_sink) => sink = new_sink,
94                AppendResult::Sealed(sealed) => {
95                    return Ok((TraversalPosition::Start, sealed));
96                }
97            };
98            0usize
99        }
100        TraversalPosition::Index(i) => u64_to_usize_safe(*i),
101        TraversalPosition::End => {
102            return Ok((TraversalPosition::End, sink.seal()));
103        }
104        TraversalPosition::Name(_) => {
105            unreachable!("the VFS should never send this to us, since we never return it here");
106        }
107    };
108
109    for i in starting_position..entries.len() {
110        let (info, name) = &entries[i];
111        match sink.append(info, name) {
112            AppendResult::Ok(new_sink) => sink = new_sink,
113            AppendResult::Sealed(sealed) => {
114                return Ok((TraversalPosition::Index(usize_to_u64_safe(i)), sealed));
115            }
116        }
117    }
118    Ok((TraversalPosition::End, sink.seal()))
119}
120
121fn usize_to_u64_safe(u: usize) -> u64 {
122    let ret: u64 = u.try_into().unwrap();
123    static_assertions::assert_eq_size_val!(u, ret);
124    ret
125}
126
127fn u64_to_usize_safe(u: u64) -> usize {
128    let ret: usize = u.try_into().unwrap();
129    static_assertions::assert_eq_size_val!(u, ret);
130    ret
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn usize_to_u64_safe_does_not_panic() {
139        assert_eq!(usize_to_u64_safe(usize::MAX), usize::MAX as u64);
140    }
141
142    #[test]
143    fn u64_to_usize_safe_does_not_panic() {
144        assert_eq!(u64_to_usize_safe(u64::MAX), u64::MAX as usize);
145    }
146
147    /// Implementation of `crate::directory::dirents_sink::Sink`.
148    /// `Sink::append` begins to fail (return Sealed) after `max_entries` entries have been
149    /// appended.
150    #[derive(Clone)]
151    pub(crate) struct FakeSink {
152        max_entries: usize,
153        entries: Vec<(EntryInfo, String)>,
154        sealed: bool,
155    }
156
157    impl FakeSink {
158        fn new(max_entries: usize) -> Self {
159            FakeSink { max_entries, entries: Vec::with_capacity(max_entries), sealed: false }
160        }
161
162        fn from_sealed(sealed: Box<dyn dirents_sink::Sealed>) -> Box<FakeSink> {
163            sealed.into()
164        }
165    }
166
167    impl From<Box<dyn dirents_sink::Sealed>> for Box<FakeSink> {
168        fn from(sealed: Box<dyn dirents_sink::Sealed>) -> Self {
169            sealed.open().downcast::<FakeSink>().unwrap()
170        }
171    }
172
173    impl dirents_sink::Sink for FakeSink {
174        fn append(mut self: Box<Self>, entry: &EntryInfo, name: &str) -> AppendResult {
175            assert!(!self.sealed);
176            if self.entries.len() == self.max_entries {
177                AppendResult::Sealed(self.seal())
178            } else {
179                self.entries.push((entry.clone(), name.to_owned()));
180                AppendResult::Ok(self)
181            }
182        }
183
184        fn seal(mut self: Box<Self>) -> Box<dyn dirents_sink::Sealed> {
185            self.sealed = true;
186            self
187        }
188    }
189
190    impl dirents_sink::Sealed for FakeSink {
191        fn open(self: Box<Self>) -> Box<dyn Any> {
192            self
193        }
194    }
195
196    #[fuchsia::test]
197    async fn read_dirents_start() {
198        let entries = vec![
199            (EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory), "dir".into()),
200            (EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File), "file".into()),
201        ];
202
203        // No space in sink.
204        let (pos, sealed) =
205            read_dirents(&entries, &TraversalPosition::Start, Box::new(FakeSink::new(0)))
206                .await
207                .expect("read_dirents failed");
208        assert_eq!(pos, TraversalPosition::Start);
209        assert_eq!(FakeSink::from_sealed(sealed).entries, vec![]);
210
211        // Only enough space in sink for partial write.
212        let (pos, sealed) =
213            read_dirents(&entries, &TraversalPosition::Start, Box::new(FakeSink::new(2)))
214                .await
215                .expect("read_dirents failed");
216        assert_eq!(pos, TraversalPosition::Index(1));
217        assert_eq!(
218            FakeSink::from_sealed(sealed).entries,
219            vec![
220                (EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory), ".".into()),
221                (EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory), "dir".into()),
222            ]
223        );
224
225        // Enough space in sink for complete write.
226        let (pos, sealed) =
227            read_dirents(&entries, &TraversalPosition::Start, Box::new(FakeSink::new(3)))
228                .await
229                .expect("read_dirents failed");
230        assert_eq!(pos, TraversalPosition::End);
231        assert_eq!(
232            FakeSink::from_sealed(sealed).entries,
233            vec![
234                (EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory), ".".into()),
235                (EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory), "dir".into()),
236                (EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File), "file".into())
237            ]
238        );
239    }
240
241    #[fuchsia::test]
242    async fn read_dirents_index() {
243        let entries = vec![
244            (EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory), "dir".into()),
245            (EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File), "file".into()),
246        ];
247
248        // No space in sink.
249        let (pos, sealed) =
250            read_dirents(&entries, &TraversalPosition::Index(0), Box::new(FakeSink::new(0)))
251                .await
252                .expect("read_dirents failed");
253        assert_eq!(pos, TraversalPosition::Index(0));
254        assert_eq!(FakeSink::from_sealed(sealed).entries, vec![]);
255
256        // Only enough space in sink for partial write.
257        let (pos, sealed) =
258            read_dirents(&entries, &TraversalPosition::Index(0), Box::new(FakeSink::new(1)))
259                .await
260                .expect("read_dirents failed");
261        assert_eq!(pos, TraversalPosition::Index(1));
262        assert_eq!(
263            FakeSink::from_sealed(sealed).entries,
264            vec![(EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory), "dir".into()),]
265        );
266
267        // Enough space in sink for complete write.
268        let (pos, sealed) =
269            read_dirents(&entries, &TraversalPosition::Index(0), Box::new(FakeSink::new(2)))
270                .await
271                .expect("read_dirents failed");
272        assert_eq!(pos, TraversalPosition::End);
273        assert_eq!(
274            FakeSink::from_sealed(sealed).entries,
275            vec![
276                (EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory), "dir".into()),
277                (EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File), "file".into()),
278            ]
279        );
280    }
281
282    #[fuchsia::test]
283    async fn read_dirents_end() {
284        let entries = vec![
285            (EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory), "dir".into()),
286            (EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File), "file".into()),
287        ];
288
289        let (pos, sealed) =
290            read_dirents(&entries, &TraversalPosition::End, Box::new(FakeSink::new(3)))
291                .await
292                .expect("read_dirents failed");
293        assert_eq!(pos, TraversalPosition::End);
294        assert_eq!(FakeSink::from_sealed(sealed).entries, vec![]);
295    }
296}