1use 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
21impl 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
68const 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 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 fn offset(&self) -> off_t;
90
91 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 fn user_capacity(&self) -> Option<usize> {
123 if self.user_capacity > i32::MAX as usize { None } else { Some(self.user_capacity) }
124 }
125
126 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)?; 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 }
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 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()); 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 sink.add(42, 1, DirectoryEntryType::REG, "a".into()).expect("add short entry");
326 assert_eq!(sink.offset(), 1);
327
328 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 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}