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 std::mem;
6use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
7
8use crate::mm::MemoryAccessor;
9use crate::task::CurrentTask;
10use crate::vfs::FsStr;
11use starnix_uapi::errors::{ENOSPC, Errno};
12use starnix_uapi::file_mode::FileMode;
13use starnix_uapi::math::round_up_to_increment;
14use starnix_uapi::user_address::UserAddress;
15use starnix_uapi::{error, ino_t, off_t};
16
17#[derive(Debug, Default, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
18pub struct DirectoryEntryType(u8);
19
20// These values are defined in libc.
21impl DirectoryEntryType {
22    pub const UNKNOWN: DirectoryEntryType = DirectoryEntryType(0);
23    pub const FIFO: DirectoryEntryType = DirectoryEntryType(1);
24    pub const CHR: DirectoryEntryType = DirectoryEntryType(2);
25    pub const DIR: DirectoryEntryType = DirectoryEntryType(4);
26    pub const BLK: DirectoryEntryType = DirectoryEntryType(6);
27    pub const REG: DirectoryEntryType = DirectoryEntryType(8);
28    pub const LNK: DirectoryEntryType = DirectoryEntryType(10);
29    pub const SOCK: DirectoryEntryType = DirectoryEntryType(12);
30
31    pub fn from_bits(bits: u8) -> DirectoryEntryType {
32        Self(bits)
33    }
34
35    pub fn from_mode(mode: FileMode) -> DirectoryEntryType {
36        match mode.fmt() {
37            FileMode::IFLNK => DirectoryEntryType::LNK,
38            FileMode::IFREG => DirectoryEntryType::REG,
39            FileMode::IFDIR => DirectoryEntryType::DIR,
40            FileMode::IFCHR => DirectoryEntryType::CHR,
41            FileMode::IFBLK => DirectoryEntryType::BLK,
42            FileMode::IFIFO => DirectoryEntryType::FIFO,
43            FileMode::IFSOCK => DirectoryEntryType::SOCK,
44            _ => DirectoryEntryType::UNKNOWN,
45        }
46    }
47
48    pub fn bits(&self) -> u8 {
49        self.0
50    }
51}
52
53const DIRENT64_PADDING_SIZE: usize = 5;
54
55#[repr(C)]
56#[derive(Debug, Default, Copy, Clone, IntoBytes, KnownLayout, FromBytes, Immutable)]
57struct DirentHeader64 {
58    d_ino: u64,
59    d_off: i64,
60    d_reclen: u16,
61    d_type: u8,
62    padding: [u8; DIRENT64_PADDING_SIZE],
63}
64
65const DIRENT64_HEADER_SIZE: usize = mem::size_of::<DirentHeader64>() - DIRENT64_PADDING_SIZE;
66
67pub trait DirentSink {
68    /// Add the given directory entry to this buffer.
69    ///
70    /// In case of success, this will update the offset from the FileObject. Any other bookkeeping
71    /// must be done by the caller after this method returns successfully.
72    ///
73    /// Returns error!(ENOSPC) if the entry does not fit.
74    fn add(
75        &mut self,
76        inode_num: ino_t,
77        offset: off_t,
78        entry_type: DirectoryEntryType,
79        name: &FsStr,
80    ) -> Result<(), Errno>;
81
82    /// The current offset to return.
83    fn offset(&self) -> off_t;
84
85    /// Optional information about the max number of bytes to send to the user.
86    fn user_capacity(&self) -> Option<usize> {
87        None
88    }
89}
90
91struct BaseDirentSink<'a> {
92    current_task: &'a CurrentTask,
93    offset: &'a mut off_t,
94    user_buffer: UserAddress,
95    user_capacity: usize,
96    actual: usize,
97}
98
99impl<'a> BaseDirentSink<'a> {
100    fn add(&mut self, offset: off_t, buffer: &[u8]) -> Result<(), Errno> {
101        if self.user_capacity().is_none_or(|cap| self.actual + buffer.len() > cap) {
102            return error!(ENOSPC);
103        }
104        self.current_task.write_memory((self.user_buffer + self.actual)?, buffer)?;
105        self.actual += buffer.len();
106        *self.offset = offset;
107        Ok(())
108    }
109
110    fn offset(&self) -> off_t {
111        *self.offset
112    }
113
114    // Validate `i32::MAX` lazily here rather than upfront in `sys_getdents`/`sys_getdents64` so
115    // `EBADF`, `ENOENT`, `EACCES`, and `ENOTDIR` checks in `FileObject::readdir` run first.
116    fn user_capacity(&self) -> Option<usize> {
117        if self.user_capacity > i32::MAX as usize { None } else { Some(self.user_capacity) }
118    }
119
120    // Converts result value received from `add()` to `getdents()` result.
121    //
122    // `add()` may return `ENOSPC` if there was not enough space in the buffer for the new entry.
123    // This error should be returned to the caller only if we didn't have space for the first
124    // directory entry.
125    //
126    // We use `ENOSPC` rather than `EINVAL` to signal this condition because `EINVAL` is a very
127    // generic error. We only want to perform this transformation in exactly the case where there
128    // was not sufficient space in the DirentSink.
129    fn map_result_with_actual(&self, result: Result<(), Errno>) -> Result<usize, Errno> {
130        match result {
131            Ok(()) => Ok(self.actual),
132            Err(_) if self.actual > 0 => Ok(self.actual),
133            Err(errno) if errno == ENOSPC => error!(EINVAL),
134            Err(e) => Err(e),
135        }
136    }
137}
138
139pub struct DirentSink64<'a> {
140    base: BaseDirentSink<'a>,
141}
142
143impl<'a> DirentSink64<'a> {
144    pub fn new(
145        current_task: &'a CurrentTask,
146        offset: &'a mut off_t,
147        user_buffer: UserAddress,
148        user_capacity: usize,
149    ) -> Self {
150        Self {
151            base: BaseDirentSink { current_task, offset, user_buffer, user_capacity, actual: 0 },
152        }
153    }
154
155    pub fn map_result_with_actual(&self, result: Result<(), Errno>) -> Result<usize, Errno> {
156        self.base.map_result_with_actual(result)
157    }
158}
159
160impl DirentSink for DirentSink64<'_> {
161    fn add(
162        &mut self,
163        inode_num: ino_t,
164        offset: off_t,
165        entry_type: DirectoryEntryType,
166        name: &FsStr,
167    ) -> Result<(), Errno> {
168        let content_size = DIRENT64_HEADER_SIZE + name.len();
169        let entry_size = round_up_to_increment(content_size + 1, 8)?; // +1 for the null terminator.
170        let mut buffer = Vec::with_capacity(entry_size);
171        let header = DirentHeader64 {
172            d_ino: inode_num,
173            d_off: offset,
174            d_reclen: entry_size as u16,
175            d_type: entry_type.bits(),
176            ..DirentHeader64::default()
177        };
178        let header_bytes = header.as_bytes();
179        buffer.extend_from_slice(&header_bytes[..DIRENT64_HEADER_SIZE]);
180        buffer.extend_from_slice(name);
181        buffer.resize(buffer.len() + (entry_size - content_size), b'\0');
182        assert_eq!(buffer.len(), entry_size);
183        self.base.add(offset, &buffer)
184    }
185
186    fn offset(&self) -> off_t {
187        self.base.offset()
188    }
189
190    fn user_capacity(&self) -> Option<usize> {
191        self.base.user_capacity()
192    }
193}
194
195#[cfg(target_arch = "x86_64")]
196pub use x86_64::*;
197
198#[cfg(target_arch = "x86_64")]
199mod x86_64 {
200    use super::{BaseDirentSink, DirectoryEntryType, DirentSink};
201    use crate::task::CurrentTask;
202    use crate::vfs::FsStr;
203    use starnix_uapi::errors::Errno;
204    use starnix_uapi::math::round_up_to_increment;
205    use starnix_uapi::user_address::UserAddress;
206    use starnix_uapi::{ino_t, off_t};
207    use std::mem;
208    use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
209
210    const DIRENT32_PADDING_SIZE: usize = 6;
211    const DIRENT32_HEADER_SIZE: usize = mem::size_of::<DirentHeader32>() - DIRENT32_PADDING_SIZE;
212
213    #[repr(C)]
214    #[derive(Debug, Default, Copy, Clone, IntoBytes, KnownLayout, FromBytes, Immutable)]
215    struct DirentHeader32 {
216        d_ino: u64,
217        d_off: i64,
218        d_reclen: u16,
219        padding: [u8; DIRENT32_PADDING_SIZE],
220        // pad: u8, // Zero padding byte
221        // d_type: u8, // File type
222    }
223
224    pub struct DirentSink32<'a> {
225        base: BaseDirentSink<'a>,
226    }
227
228    impl<'a> DirentSink32<'a> {
229        pub fn new(
230            current_task: &'a CurrentTask,
231            offset: &'a mut off_t,
232            user_buffer: UserAddress,
233            user_capacity: usize,
234        ) -> Self {
235            Self {
236                base: BaseDirentSink {
237                    current_task,
238                    offset,
239                    user_buffer,
240                    user_capacity,
241                    actual: 0,
242                },
243            }
244        }
245
246        pub fn map_result_with_actual(&self, result: Result<(), Errno>) -> Result<usize, Errno> {
247            self.base.map_result_with_actual(result)
248        }
249    }
250
251    impl DirentSink for DirentSink32<'_> {
252        fn add(
253            &mut self,
254            inode_num: ino_t,
255            offset: off_t,
256            entry_type: DirectoryEntryType,
257            name: &FsStr,
258        ) -> Result<(), Errno> {
259            let content_size = DIRENT32_HEADER_SIZE + name.len();
260            // +1 for the null terminator, +1 for the type.
261            let entry_size = round_up_to_increment(content_size + 2, 8)?;
262            let mut buffer = Vec::with_capacity(entry_size);
263            let header = DirentHeader32 {
264                d_ino: inode_num,
265                d_off: offset,
266                d_reclen: entry_size as u16,
267                ..DirentHeader32::default()
268            };
269            let header_bytes = header.as_bytes();
270            buffer.extend_from_slice(&header_bytes[..DIRENT32_HEADER_SIZE]);
271            buffer.extend_from_slice(name);
272            buffer.resize(buffer.len() + (entry_size - content_size - 1), b'\0');
273            buffer.push(entry_type.bits()); // Include the type.
274            assert_eq!(buffer.len(), entry_size);
275            self.base.add(offset, &buffer)
276        }
277
278        fn offset(&self) -> off_t {
279            self.base.offset()
280        }
281
282        fn user_capacity(&self) -> Option<usize> {
283            self.base.user_capacity()
284        }
285    }
286}