Skip to main content

relibc/header/dirent/
mod.rs

1//! `dirent.h` implementation.
2//!
3//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/dirent.h.html>.
4
5use 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
27// values of below constants taken from musl
28
29/// Unknown file type.
30pub const DT_UNKNOWN: c_int = 0;
31/// FIFO special.
32pub const DT_FIFO: c_int = 1;
33/// Character special.
34pub const DT_CHR: c_int = 2;
35/// Directory.
36pub const DT_DIR: c_int = 4;
37/// Block special.
38pub const DT_BLK: c_int = 6;
39/// Regular.
40pub const DT_REG: c_int = 8;
41/// Symbolic link.
42pub const DT_LNK: c_int = 10;
43/// Socket.
44pub const DT_SOCK: c_int = 12;
45/// Non-POSIX.
46/// Whiteout inode.
47pub const DT_WHT: c_int = 14;
48
49// values of above constants taken from musl
50
51/// cbindgen:ignore
52const INITIAL_BUFSIZE: usize = 512;
53
54/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/dirent.h.html>.
55// No repr(C) needed, as this is a completely opaque struct. Being accessed as a pointer, in C it's
56// just defined as `struct DIR`.
57pub struct DIR {
58    file: File,
59    buf: Vec<u8>,
60    buf_offset: usize,
61
62    // The last value of d_off, used by telldir
63    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        // Take ownership now but not earlier so we don't close the fd on error.
86        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                // TODO: uninitialized memory?
101                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        //println!("CDENT {} {}+{}", self.opaque_offset, self.buf_offset, this_reclen);
124
125        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            // Don't want memory corruption if a scheme is adversarial.
134            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        // Reference files aren't closed when dropped
161        self.file.reference = true;
162
163        // TODO: result
164        Sys::close(*self.file)
165    }
166}
167
168/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/dirent.h.html>.
169#[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/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/dirent.h.html>.
180/// must have the same struct layout as dirent
181#[repr(C)]
182#[derive(Clone)]
183pub struct posix_dent {
184    pub d_ino: ino_t,
185    pub d_off: off_t, // not specified by posix
186    pub d_reclen: reclen_t,
187    pub d_type: c_uchar,
188    pub d_name: [c_char; 256],
189}
190
191/// cbindgen:ignore
192#[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/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/alphasort.html>.
215#[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/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/closedir.html>.
221#[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/// See <https://man.freebsd.org/cgi/man.cgi?query=fdopendir&sektion=3>
227///
228/// FreeBSD extension that transfers ownership of the directory file descriptor to the user.
229///
230/// It doesn't matter if DIR was opened with [`opendir`] or [`fdopendir`].
231#[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/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/dirfd.html>.
240#[unsafe(no_mangle)]
241pub extern "C" fn dirfd(dir: &mut DIR) -> c_int {
242    *dir.file
243}
244
245/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fdopendir.html>.
246#[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/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fdopendir.html>.
254#[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/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_getdents.html>.
260#[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/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/readdir.html>.
275#[unsafe(no_mangle)]
276pub extern "C" fn readdir(dir: &mut DIR) -> *mut dirent {
277    dir.next_dirent().or_errno_null_mut()
278}
279
280/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/readdir.html>.
281///
282/// # Deprecation
283/// The `readdir_r()` function was marked obsolescent in the Open Group Base
284/// Specifications Issue 8.
285#[deprecated]
286// #[unsafe(no_mangle)]
287pub extern "C" fn readdir_r(
288    _dir: *mut DIR,
289    _entry: *mut dirent,
290    _result: *mut *mut dirent,
291) -> *mut dirent {
292    unimplemented!(); // plus, deprecated
293}
294
295/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/rewinddir.html>.
296#[unsafe(no_mangle)]
297pub extern "C" fn rewinddir(dir: &mut DIR) {
298    dir.rewind();
299}
300
301/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/alphasort.html>.
302#[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            // Empty CVecs use a dangling pointer which cannot be freed, return null instead
359            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)] // too verbose
373                mem::transmute(compare),
374            )
375        };
376
377        len as c_int
378    }
379}
380
381/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/seekdir.html>.
382#[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/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/telldir.html>.
393#[unsafe(no_mangle)]
394pub extern "C" fn telldir(dir: &mut DIR) -> c_long {
395    dir.opaque_offset as c_long
396}