Skip to main content

starnix_core/vfs/
dirent_sink.rs

1// Copyright 2021 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
5use smallvec::SmallVec;
6use std::mem;
7use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
8
9use crate::mm::MemoryAccessor;
10use crate::task::CurrentTask;
11use crate::vfs::FsStr;
12use starnix_uapi::errors::{ENOSPC, Errno};
13use starnix_uapi::file_mode::FileMode;
14use starnix_uapi::math::round_up_to_increment;
15use starnix_uapi::user_address::UserAddress;
16use starnix_uapi::{NAME_MAX, error, ino_t, off_t};
17
18#[derive(Debug, Default, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
19pub struct DirectoryEntryType(u8);
20
21// These values are defined in libc.
22impl DirectoryEntryType {
23    pub const UNKNOWN: DirectoryEntryType = DirectoryEntryType(0);
24    pub const FIFO: DirectoryEntryType = DirectoryEntryType(1);
25    pub const CHR: DirectoryEntryType = DirectoryEntryType(2);
26    pub const DIR: DirectoryEntryType = DirectoryEntryType(4);
27    pub const BLK: DirectoryEntryType = DirectoryEntryType(6);
28    pub const REG: DirectoryEntryType = DirectoryEntryType(8);
29    pub const LNK: DirectoryEntryType = DirectoryEntryType(10);
30    pub const SOCK: DirectoryEntryType = DirectoryEntryType(12);
31
32    pub fn from_bits(bits: u8) -> DirectoryEntryType {
33        Self(bits)
34    }
35
36    pub fn from_mode(mode: FileMode) -> DirectoryEntryType {
37        match mode.fmt() {
38            FileMode::IFLNK => DirectoryEntryType::LNK,
39            FileMode::IFREG => DirectoryEntryType::REG,
40            FileMode::IFDIR => DirectoryEntryType::DIR,
41            FileMode::IFCHR => DirectoryEntryType::CHR,
42            FileMode::IFBLK => DirectoryEntryType::BLK,
43            FileMode::IFIFO => DirectoryEntryType::FIFO,
44            FileMode::IFSOCK => DirectoryEntryType::SOCK,
45            _ => DirectoryEntryType::UNKNOWN,
46        }
47    }
48
49    pub fn bits(&self) -> u8 {
50        self.0
51    }
52}
53
54const DIRENT64_PADDING_SIZE: usize = 5;
55
56#[repr(C)]
57#[derive(Debug, Default, Copy, Clone, IntoBytes, KnownLayout, FromBytes, Immutable)]
58struct DirentHeader64 {
59    d_ino: u64,
60    d_off: i64,
61    d_reclen: u16,
62    d_type: u8,
63    padding: [u8; DIRENT64_PADDING_SIZE],
64}
65
66const DIRENT64_HEADER_SIZE: usize = mem::size_of::<DirentHeader64>() - DIRENT64_PADDING_SIZE;
67
68// Large enough for a 32-bit or 64-bit dirent whose name is at most NAME_MAX bytes.
69const DIRENT_BUFFER_SIZE: usize =
70    (DIRENT64_HEADER_SIZE + NAME_MAX as usize + 1).next_multiple_of(8);
71type DirentBuffer = SmallVec<[u8; DIRENT_BUFFER_SIZE]>;
72
73pub trait DirentSink {
74    /// Add the given directory entry to this buffer.
75    ///
76    /// In case of success, this will update the offset from the FileObject. Any other bookkeeping
77    /// must be done by the caller after this method returns successfully.
78    ///
79    /// Returns error!(ENOSPC) if the entry does not fit.
80    fn add(
81        &mut self,
82        inode_num: ino_t,
83        offset: off_t,
84        entry_type: DirectoryEntryType,
85        name: &FsStr,
86    ) -> Result<(), Errno>;
87
88    /// The current offset to return.
89    fn offset(&self) -> off_t;
90
91    /// Optional information about the max number of bytes to send to the user.
92    fn user_capacity(&self) -> Option<usize> {
93        None
94    }
95}
96
97struct BaseDirentSink<'a> {
98    current_task: &'a CurrentTask,
99    offset: &'a mut off_t,
100    user_buffer: UserAddress,
101    user_capacity: usize,
102    actual: usize,
103}
104
105impl<'a> BaseDirentSink<'a> {
106    fn add(&mut self, offset: off_t, buffer: &[u8]) -> Result<(), Errno> {
107        if self.user_capacity().is_none_or(|cap| self.actual + buffer.len() > cap) {
108            return error!(ENOSPC);
109        }
110        self.current_task.write_memory((self.user_buffer + self.actual)?, buffer)?;
111        self.actual += buffer.len();
112        *self.offset = offset;
113        Ok(())
114    }
115
116    fn offset(&self) -> off_t {
117        *self.offset
118    }
119
120    // Validate `i32::MAX` lazily here rather than upfront in `sys_getdents`/`sys_getdents64` so
121    // `EBADF`, `ENOENT`, `EACCES`, and `ENOTDIR` checks in `FileObject::readdir` run first.
122    fn user_capacity(&self) -> Option<usize> {
123        if self.user_capacity > i32::MAX as usize { None } else { Some(self.user_capacity) }
124    }
125
126    // Converts result value received from `add()` to `getdents()` result.
127    //
128    // `add()` may return `ENOSPC` if there was not enough space in the buffer for the new entry.
129    // This error should be returned to the caller only if we didn't have space for the first
130    // directory entry.
131    //
132    // We use `ENOSPC` rather than `EINVAL` to signal this condition because `EINVAL` is a very
133    // generic error. We only want to perform this transformation in exactly the case where there
134    // was not sufficient space in the DirentSink.
135    fn map_result_with_actual(&self, result: Result<(), Errno>) -> Result<usize, Errno> {
136        match result {
137            Ok(()) => Ok(self.actual),
138            Err(_) if self.actual > 0 => Ok(self.actual),
139            Err(errno) if errno == ENOSPC => error!(EINVAL),
140            Err(e) => Err(e),
141        }
142    }
143}
144
145pub struct DirentSink64<'a> {
146    base: BaseDirentSink<'a>,
147}
148
149impl<'a> DirentSink64<'a> {
150    pub fn new(
151        current_task: &'a CurrentTask,
152        offset: &'a mut off_t,
153        user_buffer: UserAddress,
154        user_capacity: usize,
155    ) -> Self {
156        Self {
157            base: BaseDirentSink { current_task, offset, user_buffer, user_capacity, actual: 0 },
158        }
159    }
160
161    pub fn map_result_with_actual(&self, result: Result<(), Errno>) -> Result<usize, Errno> {
162        self.base.map_result_with_actual(result)
163    }
164}
165
166impl DirentSink for DirentSink64<'_> {
167    fn add(
168        &mut self,
169        inode_num: ino_t,
170        offset: off_t,
171        entry_type: DirectoryEntryType,
172        name: &FsStr,
173    ) -> Result<(), Errno> {
174        let content_size = DIRENT64_HEADER_SIZE + name.len();
175        let entry_size = round_up_to_increment(content_size + 1, 8)?; // +1 for the null terminator.
176        let mut buffer = DirentBuffer::with_capacity(entry_size);
177        let header = DirentHeader64 {
178            d_ino: inode_num,
179            d_off: offset,
180            d_reclen: entry_size as u16,
181            d_type: entry_type.bits(),
182            ..DirentHeader64::default()
183        };
184        let header_bytes = header.as_bytes();
185        buffer.extend_from_slice(&header_bytes[..DIRENT64_HEADER_SIZE]);
186        buffer.extend_from_slice(name);
187        buffer.resize(buffer.len() + (entry_size - content_size), b'\0');
188        assert_eq!(buffer.len(), entry_size);
189        self.base.add(offset, &buffer)
190    }
191
192    fn offset(&self) -> off_t {
193        self.base.offset()
194    }
195
196    fn user_capacity(&self) -> Option<usize> {
197        self.base.user_capacity()
198    }
199}
200
201#[cfg(target_arch = "x86_64")]
202pub use x86_64::*;
203
204#[cfg(target_arch = "x86_64")]
205mod x86_64 {
206    use super::{BaseDirentSink, DirectoryEntryType, DirentBuffer, DirentSink};
207    use crate::task::CurrentTask;
208    use crate::vfs::FsStr;
209    use starnix_uapi::errors::Errno;
210    use starnix_uapi::math::round_up_to_increment;
211    use starnix_uapi::user_address::UserAddress;
212    use starnix_uapi::{NAME_MAX, ino_t, off_t};
213    use std::mem;
214    use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
215
216    const DIRENT32_PADDING_SIZE: usize = 6;
217    const DIRENT32_HEADER_SIZE: usize = mem::size_of::<DirentHeader32>() - DIRENT32_PADDING_SIZE;
218
219    const _: () = assert!(
220        (DIRENT32_HEADER_SIZE + NAME_MAX as usize + 2).next_multiple_of(8)
221            <= super::DIRENT_BUFFER_SIZE
222    );
223
224    #[repr(C)]
225    #[derive(Debug, Default, Copy, Clone, IntoBytes, KnownLayout, FromBytes, Immutable)]
226    struct DirentHeader32 {
227        d_ino: u64,
228        d_off: i64,
229        d_reclen: u16,
230        padding: [u8; DIRENT32_PADDING_SIZE],
231        // pad: u8, // Zero padding byte
232        // d_type: u8, // File type
233    }
234
235    pub struct DirentSink32<'a> {
236        base: BaseDirentSink<'a>,
237    }
238
239    impl<'a> DirentSink32<'a> {
240        pub fn new(
241            current_task: &'a CurrentTask,
242            offset: &'a mut off_t,
243            user_buffer: UserAddress,
244            user_capacity: usize,
245        ) -> Self {
246            Self {
247                base: BaseDirentSink {
248                    current_task,
249                    offset,
250                    user_buffer,
251                    user_capacity,
252                    actual: 0,
253                },
254            }
255        }
256
257        pub fn map_result_with_actual(&self, result: Result<(), Errno>) -> Result<usize, Errno> {
258            self.base.map_result_with_actual(result)
259        }
260    }
261
262    impl DirentSink for DirentSink32<'_> {
263        fn add(
264            &mut self,
265            inode_num: ino_t,
266            offset: off_t,
267            entry_type: DirectoryEntryType,
268            name: &FsStr,
269        ) -> Result<(), Errno> {
270            let content_size = DIRENT32_HEADER_SIZE + name.len();
271            // +1 for the null terminator, +1 for the type.
272            let entry_size = round_up_to_increment(content_size + 2, 8)?;
273            let mut buffer = DirentBuffer::with_capacity(entry_size);
274            let header = DirentHeader32 {
275                d_ino: inode_num,
276                d_off: offset,
277                d_reclen: entry_size as u16,
278                ..DirentHeader32::default()
279            };
280            let header_bytes = header.as_bytes();
281            buffer.extend_from_slice(&header_bytes[..DIRENT32_HEADER_SIZE]);
282            buffer.extend_from_slice(name);
283            buffer.resize(buffer.len() + (entry_size - content_size - 1), b'\0');
284            buffer.push(entry_type.bits()); // Include the type.
285            assert_eq!(buffer.len(), entry_size);
286            self.base.add(offset, &buffer)
287        }
288
289        fn offset(&self) -> off_t {
290            self.base.offset()
291        }
292
293        fn user_capacity(&self) -> Option<usize> {
294            self.base.user_capacity()
295        }
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302    use crate::mm::{MemoryAccessorExt, PAGE_SIZE};
303    use crate::testing::{map_memory, spawn_kernel_and_run};
304
305    const SHORT_RECLEN: usize = 24;
306    const LONG_RECLEN: usize = 288;
307
308    #[::fuchsia::test]
309    fn dirent_buffer_inline_capacity() {
310        let max_entry =
311            round_up_to_increment(DIRENT64_HEADER_SIZE + NAME_MAX as usize + 1, 8).unwrap();
312        assert!(!DirentBuffer::with_capacity(max_entry).spilled());
313        assert!(DirentBuffer::with_capacity(max_entry + 8).spilled());
314    }
315
316    #[::fuchsia::test]
317    async fn test_dirent_sink64_add() {
318        spawn_kernel_and_run(async |current_task| {
319            let user_buffer = map_memory(current_task, UserAddress::default(), *PAGE_SIZE);
320            let mut offset = 0;
321            let mut sink =
322                DirentSink64::new(current_task, &mut offset, user_buffer, *PAGE_SIZE as usize);
323
324            // Short entry (fits in stack buffer, padded with zeros to 8-byte alignment).
325            sink.add(42, 1, DirectoryEntryType::REG, "a".into()).expect("add short entry");
326            assert_eq!(sink.offset(), 1);
327
328            // Max standard filename (NAME_MAX = 255 -> 280-byte entry, exact stack buffer size).
329            let max_name = vec![b'x'; NAME_MAX as usize];
330            sink.add(43, 2, DirectoryEntryType::DIR, max_name.as_slice().into())
331                .expect("add NAME_MAX entry");
332            assert_eq!(sink.offset(), 2);
333
334            // Oversized filename (> NAME_MAX -> exercises heap fallback).
335            let long_name = vec![b'y'; NAME_MAX as usize + 10];
336            sink.add(44, 3, DirectoryEntryType::LNK, long_name.as_slice().into())
337                .expect("add oversized entry");
338            assert_eq!(sink.offset(), 3);
339
340            let total_written = sink.map_result_with_actual(Ok(())).expect("map_result");
341            assert_eq!(total_written, SHORT_RECLEN + DIRENT_BUFFER_SIZE + LONG_RECLEN);
342
343            let bytes = current_task.read_memory_to_vec(user_buffer, total_written).expect("read");
344
345            let (hdr1, _) = DirentHeader64::read_from_prefix(&bytes[..SHORT_RECLEN]).unwrap();
346            assert_eq!(hdr1.d_ino, 42);
347            assert_eq!(hdr1.d_off, 1);
348            assert_eq!(hdr1.d_reclen, SHORT_RECLEN as u16);
349            assert_eq!(hdr1.d_type, DirectoryEntryType::REG.bits());
350            assert_eq!(&bytes[DIRENT64_HEADER_SIZE..SHORT_RECLEN], b"a\0\0\0\0");
351
352            let entry2 = &bytes[SHORT_RECLEN..SHORT_RECLEN + DIRENT_BUFFER_SIZE];
353            let (hdr2, _) = DirentHeader64::read_from_prefix(entry2).unwrap();
354            assert_eq!(hdr2.d_ino, 43);
355            assert_eq!(hdr2.d_off, 2);
356            assert_eq!(hdr2.d_reclen, DIRENT_BUFFER_SIZE as u16);
357            assert_eq!(hdr2.d_type, DirectoryEntryType::DIR.bits());
358            assert_eq!(
359                &entry2[DIRENT64_HEADER_SIZE..DIRENT64_HEADER_SIZE + max_name.len()],
360                &max_name[..]
361            );
362            assert!(entry2[DIRENT64_HEADER_SIZE + max_name.len()..].iter().all(|&b| b == 0));
363
364            let entry3 = &bytes[SHORT_RECLEN + DIRENT_BUFFER_SIZE..];
365            let (hdr3, _) = DirentHeader64::read_from_prefix(entry3).unwrap();
366            assert_eq!(hdr3.d_ino, 44);
367            assert_eq!(hdr3.d_off, 3);
368            assert_eq!(hdr3.d_reclen, LONG_RECLEN as u16);
369            assert_eq!(hdr3.d_type, DirectoryEntryType::LNK.bits());
370            assert_eq!(
371                &entry3[DIRENT64_HEADER_SIZE..DIRENT64_HEADER_SIZE + long_name.len()],
372                &long_name[..]
373            );
374            assert!(entry3[DIRENT64_HEADER_SIZE + long_name.len()..].iter().all(|&b| b == 0));
375        })
376        .await;
377    }
378}