1use alloc::{boxed::Box, vec::Vec};
6use core::{mem, ptr, slice};
7
8use crate::{
9 c_str::CStr,
10 c_vec::CVec,
11 error::{Errno, ResultExt, ResultExtPtrMut},
12 fs::File,
13 header::{
14 errno::{EINVAL, EIO, ENOMEM, ENOTDIR},
15 fcntl, stdlib, string, sys_stat,
16 },
17 out::Out,
18 platform::{
19 self, Pal, Sys,
20 types::{
21 c_char, c_int, c_long, c_uchar, c_ushort, c_void, ino_t, off_t, reclen_t, size_t,
22 ssize_t,
23 },
24 },
25};
26
27pub const DT_UNKNOWN: c_int = 0;
31pub const DT_FIFO: c_int = 1;
33pub const DT_CHR: c_int = 2;
35pub const DT_DIR: c_int = 4;
37pub const DT_BLK: c_int = 6;
39pub const DT_REG: c_int = 8;
41pub const DT_LNK: c_int = 10;
43pub const DT_SOCK: c_int = 12;
45pub const DT_WHT: c_int = 14;
48
49const INITIAL_BUFSIZE: usize = 512;
53
54pub struct DIR {
58 file: File,
59 buf: Vec<u8>,
60 buf_offset: usize,
61
62 opaque_offset: u64,
64}
65impl DIR {
66 pub fn new(path: CStr) -> Result<Box<Self>, Errno> {
67 Ok(Box::new(Self {
68 file: File::open(
69 path,
70 fcntl::O_RDONLY | fcntl::O_DIRECTORY | fcntl::O_CLOEXEC,
71 )?,
72 buf: Vec::with_capacity(INITIAL_BUFSIZE),
73 buf_offset: 0,
74 opaque_offset: 0,
75 }))
76 }
77 pub fn from_fd(fd: c_int) -> Result<Box<Self>, Errno> {
78 let mut stat = sys_stat::stat::default();
79 Sys::fstat(fd, Out::from_mut(&mut stat))?;
80 if (stat.st_mode & sys_stat::S_IFMT) != sys_stat::S_IFDIR {
81 return Err(Errno(ENOTDIR));
82 }
83 Sys::fcntl(fd, fcntl::F_SETFD, fcntl::FD_CLOEXEC as _)?;
84
85 let file = File::new(fd);
87 Ok(Self {
88 file,
89 buf: Vec::with_capacity(INITIAL_BUFSIZE),
90 buf_offset: 0,
91 opaque_offset: 0,
92 }
93 .into())
94 }
95 fn next_dirent(&mut self) -> Result<*mut dirent, Errno> {
96 let mut this_dent = self.buf.get(self.buf_offset..).ok_or(Errno(EIO))?;
97 if this_dent.is_empty() {
98 let size = loop {
99 self.buf.resize(self.buf.capacity(), 0_u8);
100 match Sys::getdents(*self.file, &mut self.buf, self.opaque_offset) {
102 Ok(size) => break size,
103 Err(Errno(EINVAL)) => {
104 self.buf
105 .try_reserve_exact(self.buf.len())
106 .map_err(|_| Errno(ENOMEM))?;
107 continue;
108 }
109 Err(Errno(other)) => return Err(Errno(other)),
110 }
111 };
112 self.buf.truncate(size);
113 self.buf_offset = 0;
114
115 if size == 0 {
116 return Ok(core::ptr::null_mut());
117 }
118 this_dent = &self.buf;
119 }
120 let (this_reclen, this_next_opaque) =
121 unsafe { Sys::dent_reclen_offset(this_dent, self.buf_offset).ok_or(Errno(EIO))? };
122
123 let next_off = self
126 .buf_offset
127 .checked_add(usize::from(this_reclen))
128 .ok_or(Errno(EIO))?;
129 if next_off > self.buf.len() {
130 return Err(Errno(EIO));
131 }
132 if this_dent.len() < usize::from(this_reclen) {
133 return Err(Errno(EIO));
135 }
136 let dent_ptr = this_dent.as_ptr() as *mut dirent;
137
138 self.opaque_offset = this_next_opaque;
139 self.buf_offset = next_off;
140 Ok(dent_ptr)
141 }
142 fn seek(&mut self, off: u64) {
143 let Ok(()) = Sys::dir_seek(*self.file, off) else {
144 return;
145 };
146 self.buf.clear();
147 self.buf_offset = 0;
148 self.opaque_offset = off;
149 }
150 fn rewind(&mut self) {
151 self.opaque_offset = 0;
152 let Ok(()) = Sys::dir_seek(*self.file, 0) else {
153 return;
154 };
155 self.buf.clear();
156 self.buf_offset = 0;
157 self.opaque_offset = 0;
158 }
159 fn close(mut self) -> Result<(), Errno> {
160 self.file.reference = true;
162
163 Sys::close(*self.file)
165 }
166}
167
168#[repr(C)]
170#[derive(Clone)]
171pub struct dirent {
172 pub d_ino: ino_t,
173 pub d_off: off_t,
174 pub d_reclen: c_ushort,
175 pub d_type: c_uchar,
176 pub d_name: [c_char; 256],
177}
178
179#[repr(C)]
182#[derive(Clone)]
183pub struct posix_dent {
184 pub d_ino: ino_t,
185 pub d_off: off_t, pub d_reclen: reclen_t,
187 pub d_type: c_uchar,
188 pub d_name: [c_char; 256],
189}
190
191#[cfg(target_os = "redox")]
193const _: () = {
194 use core::mem::{offset_of, size_of};
195 use syscall::dirent::DirentHeader;
196
197 if offset_of!(dirent, d_ino) != offset_of!(DirentHeader, inode) {
198 panic!("struct dirent layout mismatch (inode)");
199 }
200 if offset_of!(dirent, d_off) != offset_of!(DirentHeader, next_opaque_id) {
201 panic!("struct dirent layout mismatch (inode)");
202 }
203 if offset_of!(dirent, d_reclen) != offset_of!(DirentHeader, record_len) {
204 panic!("struct dirent layout mismatch (len)");
205 }
206 if offset_of!(dirent, d_type) != offset_of!(DirentHeader, kind) {
207 panic!("struct dirent layout mismatch (kind)");
208 }
209 if offset_of!(dirent, d_name) != size_of::<DirentHeader>() {
210 panic!("struct dirent layout mismatch (name)");
211 }
212};
213
214#[unsafe(no_mangle)]
216pub unsafe extern "C" fn alphasort(first: *mut *const dirent, second: *mut *const dirent) -> c_int {
217 unsafe { string::strcoll((**first).d_name.as_ptr(), (**second).d_name.as_ptr()) }
218}
219
220#[unsafe(no_mangle)]
222pub extern "C" fn closedir(dir: Box<DIR>) -> c_int {
223 dir.close().map(|()| 0).or_minus_one_errno()
224}
225
226#[unsafe(no_mangle)]
232pub extern "C" fn fdclosedir(dir: Box<DIR>) -> c_int {
233 let mut file = dir.file;
234 file.reference = true;
235
236 *file
237}
238
239#[unsafe(no_mangle)]
241pub extern "C" fn dirfd(dir: &mut DIR) -> c_int {
242 *dir.file
243}
244
245#[unsafe(no_mangle)]
247pub unsafe extern "C" fn opendir(path: *const c_char) -> *mut DIR {
248 let path = unsafe { CStr::from_ptr(path) };
249
250 DIR::new(path).or_errno_null_mut()
251}
252
253#[unsafe(no_mangle)]
255pub extern "C" fn fdopendir(fd: c_int) -> *mut DIR {
256 DIR::from_fd(fd).or_errno_null_mut()
257}
258
259#[unsafe(no_mangle)]
261pub extern "C" fn posix_getdents(
262 fildes: c_int,
263 buf: *mut c_void,
264 nbyte: size_t,
265 _flags: c_int,
266) -> ssize_t {
267 let slice = unsafe { slice::from_raw_parts_mut(buf.cast::<u8>(), nbyte) };
268
269 Sys::posix_getdents(fildes, slice)
270 .map(|s| s as ssize_t)
271 .or_minus_one_errno()
272}
273
274#[unsafe(no_mangle)]
276pub extern "C" fn readdir(dir: &mut DIR) -> *mut dirent {
277 dir.next_dirent().or_errno_null_mut()
278}
279
280#[deprecated]
286pub extern "C" fn readdir_r(
288 _dir: *mut DIR,
289 _entry: *mut dirent,
290 _result: *mut *mut dirent,
291) -> *mut dirent {
292 unimplemented!(); }
294
295#[unsafe(no_mangle)]
297pub extern "C" fn rewinddir(dir: &mut DIR) {
298 dir.rewind();
299}
300
301#[unsafe(no_mangle)]
303pub unsafe extern "C" fn scandir(
304 dirp: *const c_char,
305 namelist: *mut *mut *mut dirent,
306 filter: Option<extern "C" fn(_: *const dirent) -> c_int>,
307 compare: Option<extern "C" fn(_: *mut *const dirent, _: *mut *const dirent) -> c_int>,
308) -> c_int {
309 let dir = unsafe { opendir(dirp) };
310 if dir.is_null() {
311 return -1;
312 }
313
314 let mut vec = match CVec::with_capacity(4) {
315 Ok(vec) => vec,
316 Err(err) => return -1,
317 };
318
319 let old_errno = platform::ERRNO.get();
320 platform::ERRNO.set(0);
321
322 loop {
323 let entry: *mut dirent = readdir(unsafe { &mut *dir });
324 if entry.is_null() {
325 break;
326 }
327
328 if let Some(filter) = filter
329 && filter(entry) == 0
330 {
331 continue;
332 }
333
334 let copy = unsafe { platform::alloc(mem::size_of::<dirent>()) }.cast::<dirent>();
335 if copy.is_null() {
336 break;
337 }
338 unsafe { ptr::write(copy, (*entry).clone()) };
339 if vec.push(copy).is_err() {
340 break;
341 }
342 }
343
344 closedir(unsafe { Box::from_raw(dir) });
345
346 let len = vec.len();
347 if vec.shrink_to_fit().is_err() {
348 return -1;
349 }
350
351 if platform::ERRNO.get() != 0 {
352 for ptr in &mut vec {
353 unsafe { platform::free((*ptr).cast::<c_void>()) };
354 }
355 -1
356 } else {
357 unsafe {
358 if vec.is_empty() {
360 *namelist = ptr::null_mut();
361 } else {
362 *namelist = vec.leak();
363 }
364 }
365
366 platform::ERRNO.set(old_errno);
367 unsafe {
368 stdlib::qsort(
369 (*namelist).cast::<c_void>(),
370 len as size_t,
371 mem::size_of::<*mut dirent>(),
372 #[allow(clippy::missing_transmute_annotations)] mem::transmute(compare),
374 )
375 };
376
377 len as c_int
378 }
379}
380
381#[unsafe(no_mangle)]
383pub extern "C" fn seekdir(dir: &mut DIR, off: c_long) {
384 let Ok(off) = off.try_into() else {
385 platform::ERRNO.set(EINVAL);
386 return;
387 };
388
389 dir.seek(off);
390}
391
392#[unsafe(no_mangle)]
394pub extern "C" fn telldir(dir: &mut DIR) -> c_long {
395 dir.opaque_offset as c_long
396}