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.actual + buffer.len() > self.user_capacity {
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    // Converts result value received from `add()` to `getdents()` result.
115    //
116    // `add()` may return `ENOSPC` if there was not enough space in the buffer for the new entry.
117    // This error should be returned to the caller only if we didn't have space for the first
118    // directory entry.
119    //
120    // We use `ENOSPC` rather than `EINVAL` to signal this condition because `EINVAL` is a very
121    // generic error. We only want to perform this transformation in exactly the case where there
122    // was not sufficient space in the DirentSink.
123    fn map_result_with_actual(&self, result: Result<(), Errno>) -> Result<usize, Errno> {
124        match result {
125            Ok(()) => Ok(self.actual),
126            Err(_) if self.actual > 0 => Ok(self.actual),
127            Err(errno) if errno == ENOSPC => error!(EINVAL),
128            Err(e) => Err(e),
129        }
130    }
131}
132
133pub struct DirentSink64<'a> {
134    base: BaseDirentSink<'a>,
135}
136
137impl<'a> DirentSink64<'a> {
138    pub fn new(
139        current_task: &'a CurrentTask,
140        offset: &'a mut off_t,
141        user_buffer: UserAddress,
142        user_capacity: usize,
143    ) -> Self {
144        Self {
145            base: BaseDirentSink { current_task, offset, user_buffer, user_capacity, actual: 0 },
146        }
147    }
148
149    pub fn map_result_with_actual(&self, result: Result<(), Errno>) -> Result<usize, Errno> {
150        self.base.map_result_with_actual(result)
151    }
152}
153
154impl DirentSink for DirentSink64<'_> {
155    fn add(
156        &mut self,
157        inode_num: ino_t,
158        offset: off_t,
159        entry_type: DirectoryEntryType,
160        name: &FsStr,
161    ) -> Result<(), Errno> {
162        let content_size = DIRENT64_HEADER_SIZE + name.len();
163        let entry_size = round_up_to_increment(content_size + 1, 8)?; // +1 for the null terminator.
164        let mut buffer = Vec::with_capacity(entry_size);
165        let header = DirentHeader64 {
166            d_ino: inode_num,
167            d_off: offset,
168            d_reclen: entry_size as u16,
169            d_type: entry_type.bits(),
170            ..DirentHeader64::default()
171        };
172        let header_bytes = header.as_bytes();
173        buffer.extend_from_slice(&header_bytes[..DIRENT64_HEADER_SIZE]);
174        buffer.extend_from_slice(name);
175        buffer.resize(buffer.len() + (entry_size - content_size), b'\0');
176        assert_eq!(buffer.len(), entry_size);
177        self.base.add(offset, &buffer)
178    }
179
180    fn offset(&self) -> off_t {
181        self.base.offset()
182    }
183
184    fn user_capacity(&self) -> Option<usize> {
185        Some(self.base.user_capacity)
186    }
187}
188
189#[cfg(target_arch = "x86_64")]
190pub use x86_64::*;
191
192#[cfg(target_arch = "x86_64")]
193mod x86_64 {
194    use super::{BaseDirentSink, DirectoryEntryType, DirentSink};
195    use crate::task::CurrentTask;
196    use crate::vfs::FsStr;
197    use starnix_uapi::errors::Errno;
198    use starnix_uapi::math::round_up_to_increment;
199    use starnix_uapi::user_address::UserAddress;
200    use starnix_uapi::{ino_t, off_t};
201    use std::mem;
202    use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
203
204    const DIRENT32_PADDING_SIZE: usize = 6;
205    const DIRENT32_HEADER_SIZE: usize = mem::size_of::<DirentHeader32>() - DIRENT32_PADDING_SIZE;
206
207    #[repr(C)]
208    #[derive(Debug, Default, Copy, Clone, IntoBytes, KnownLayout, FromBytes, Immutable)]
209    struct DirentHeader32 {
210        d_ino: u64,
211        d_off: i64,
212        d_reclen: u16,
213        padding: [u8; DIRENT32_PADDING_SIZE],
214        // pad: u8, // Zero padding byte
215        // d_type: u8, // File type
216    }
217
218    pub struct DirentSink32<'a> {
219        base: BaseDirentSink<'a>,
220    }
221
222    impl<'a> DirentSink32<'a> {
223        pub fn new(
224            current_task: &'a CurrentTask,
225            offset: &'a mut off_t,
226            user_buffer: UserAddress,
227            user_capacity: usize,
228        ) -> Self {
229            Self {
230                base: BaseDirentSink {
231                    current_task,
232                    offset,
233                    user_buffer,
234                    user_capacity,
235                    actual: 0,
236                },
237            }
238        }
239
240        pub fn map_result_with_actual(&self, result: Result<(), Errno>) -> Result<usize, Errno> {
241            self.base.map_result_with_actual(result)
242        }
243    }
244
245    impl DirentSink for DirentSink32<'_> {
246        fn add(
247            &mut self,
248            inode_num: ino_t,
249            offset: off_t,
250            entry_type: DirectoryEntryType,
251            name: &FsStr,
252        ) -> Result<(), Errno> {
253            let content_size = DIRENT32_HEADER_SIZE + name.len();
254            // +1 for the null terminator, +1 for the type.
255            let entry_size = round_up_to_increment(content_size + 2, 8)?;
256            let mut buffer = Vec::with_capacity(entry_size);
257            let header = DirentHeader32 {
258                d_ino: inode_num,
259                d_off: offset,
260                d_reclen: entry_size as u16,
261                ..DirentHeader32::default()
262            };
263            let header_bytes = header.as_bytes();
264            buffer.extend_from_slice(&header_bytes[..DIRENT32_HEADER_SIZE]);
265            buffer.extend_from_slice(name);
266            buffer.resize(buffer.len() + (entry_size - content_size - 1), b'\0');
267            buffer.push(entry_type.bits()); // Include the type.
268            assert_eq!(buffer.len(), entry_size);
269            self.base.add(offset, &buffer)
270        }
271
272        fn offset(&self) -> off_t {
273            self.base.offset()
274        }
275
276        fn user_capacity(&self) -> Option<usize> {
277            Some(self.base.user_capacity)
278        }
279    }
280}