Skip to main content

nix/
dir.rs

1//! List directory contents
2
3use crate::errno::Errno;
4use crate::fcntl::{self, OFlag};
5use crate::sys;
6use crate::{NixPath, Result};
7use cfg_if::cfg_if;
8use std::ffi::{CStr, CString};
9use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
10use std::ptr;
11
12/// An open directory.
13///
14/// This is a lower-level interface than [`std::fs::ReadDir`]. Notable differences:
15///    * can be opened from a file descriptor (as returned by [`openat`][openat],
16///      perhaps before knowing if the path represents a file or directory).
17///    * implements [`AsFd`][AsFd], so it can be passed to [`fstat`][fstat],
18///      [`openat`][openat], etc. The file descriptor continues to be owned by the
19///      `Dir`, so callers must not keep a `RawFd` after the `Dir` is dropped.
20///    * can be iterated through multiple times without closing and reopening the file
21///      descriptor. Each iteration rewinds when finished.
22///    * returns entries for `.` (current directory) and `..` (parent directory).
23///    * returns entries' names as a [`CStr`] (no allocation or conversion beyond whatever libc
24///      does).
25///
26/// [AsFd]: std::os::fd::AsFd
27/// [fstat]: crate::sys::stat::fstat
28/// [openat]: crate::fcntl::openat
29/// [cstr]: std::ffi::CStr
30///
31/// # Examples
32///
33/// Traverse the current directory, and print entries' names:
34///
35/// ```
36/// use nix::dir::Dir;
37/// use nix::fcntl::OFlag;
38/// use nix::sys::stat::Mode;
39///
40/// let mut cwd = Dir::open(".", OFlag::O_RDONLY | OFlag::O_CLOEXEC, Mode::empty()).unwrap();
41/// for res_entry in cwd.iter() {
42///     let entry = res_entry.unwrap();
43///     println!("File name: {}", entry.file_name().to_string_lossy());
44/// }
45/// ```
46#[derive(Debug, Eq, Hash, PartialEq)]
47pub struct Dir(ptr::NonNull<libc::DIR>);
48
49impl Dir {
50    /// Opens the given path as with `fcntl::open`.
51    pub fn open<P: ?Sized + NixPath>(
52        path: &P,
53        oflag: OFlag,
54        mode: sys::stat::Mode,
55    ) -> Result<Self> {
56        let fd = fcntl::open(path, oflag, mode)?;
57        Dir::from_fd(fd)
58    }
59
60    /// Opens the given path as with `fcntl::openat`.
61    pub fn openat<Fd: std::os::fd::AsFd, P: ?Sized + NixPath>(
62        dirfd: Fd,
63        path: &P,
64        oflag: OFlag,
65        mode: sys::stat::Mode,
66    ) -> Result<Self> {
67        let fd = fcntl::openat(dirfd, path, oflag, mode)?;
68        Dir::from_fd(fd)
69    }
70
71    /// Converts from a descriptor-based object, closing the descriptor on success or failure.
72    ///
73    /// # Safety
74    ///
75    /// It is only safe if `fd` is an owned file descriptor.
76    #[inline]
77    #[deprecated(
78        since = "0.30.0",
79        note = "Deprecate this since it is not I/O-safe, use from_fd instead."
80    )]
81    pub unsafe fn from<F: IntoRawFd>(fd: F) -> Result<Self> {
82        use std::os::fd::FromRawFd;
83        use std::os::fd::OwnedFd;
84
85        // SAFETY:
86        //
87        // This is indeed unsafe is `fd` it not an owned fd.
88        let owned_fd = unsafe { OwnedFd::from_raw_fd(fd.into_raw_fd()) };
89        Dir::from_fd(owned_fd)
90    }
91
92    /// Converts from a file descriptor, closing it on failure.
93    ///
94    /// # Examples
95    ///
96    /// `ENOTDIR` would be returned if `fd` does not refer to a directory:
97    ///
98    /// ```should_panic
99    /// use std::os::fd::OwnedFd;
100    /// use nix::dir::Dir;
101    ///
102    /// let temp_file = tempfile::tempfile().unwrap();
103    /// let temp_file_fd: OwnedFd = temp_file.into();
104    /// let never = Dir::from_fd(temp_file_fd).unwrap();
105    /// ```
106    #[doc(alias("fdopendir"))]
107    pub fn from_fd(fd: std::os::fd::OwnedFd) -> Result<Self> {
108        // take the ownership as the constructed `Dir` is now the owner
109        let raw_fd = fd.into_raw_fd();
110        let d = ptr::NonNull::new(unsafe { libc::fdopendir(raw_fd) })
111            .ok_or(Errno::last())?;
112        Ok(Dir(d))
113    }
114
115    /// Returns an iterator of `Result<Entry>` which rewinds when finished.
116    pub fn iter(&mut self) -> Iter<'_> {
117        Iter(self)
118    }
119}
120
121// `Dir` is not `Sync` because it's unsafe to call `readdir` simultaneously from multiple threads.
122//
123// `Dir` is safe to pass from one thread to another, as it's not reference-counted.
124unsafe impl Send for Dir {}
125
126impl std::os::fd::AsFd for Dir {
127    fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> {
128        let raw_fd = self.as_raw_fd();
129
130        // SAFETY:
131        //
132        // `raw_fd` should be open and valid for the lifetime of the returned
133        // `BorrowedFd` as it is extracted from `&self`.
134        unsafe { std::os::fd::BorrowedFd::borrow_raw(raw_fd) }
135    }
136}
137
138impl AsRawFd for Dir {
139    fn as_raw_fd(&self) -> RawFd {
140        unsafe { libc::dirfd(self.0.as_ptr()) }
141    }
142}
143
144impl Drop for Dir {
145    fn drop(&mut self) {
146        let e = Errno::result(unsafe { libc::closedir(self.0.as_ptr()) });
147        if !std::thread::panicking() && e == Err(Errno::EBADF) {
148            panic!("Closing an invalid file descriptor!");
149        };
150    }
151}
152
153// The pass by mut is technically needless only because the inner NonNull is
154// Copy.  But we are actually mutating the Dir, so we pass by mut.
155#[allow(clippy::needless_pass_by_ref_mut)]
156fn readdir(dir: &mut Dir) -> Option<Result<Entry>> {
157    Errno::clear();
158    unsafe {
159        let de = libc::readdir(dir.0.as_ptr());
160        if de.is_null() {
161            if Errno::last_raw() == 0 {
162                // EOF
163                None
164            } else {
165                Some(Err(Errno::last()))
166            }
167        } else {
168            Some(Ok(Entry::from_raw(&*de)))
169        }
170    }
171}
172
173/// Return type of [`Dir::iter`].
174#[derive(Debug, Eq, Hash, PartialEq)]
175pub struct Iter<'d>(&'d mut Dir);
176
177impl Iterator for Iter<'_> {
178    type Item = Result<Entry>;
179
180    fn next(&mut self) -> Option<Self::Item> {
181        readdir(self.0)
182    }
183}
184
185impl Drop for Iter<'_> {
186    fn drop(&mut self) {
187        unsafe { libc::rewinddir((self.0).0.as_ptr()) }
188    }
189}
190
191/// The return type of [Dir::into_iter]
192#[derive(Debug, Eq, Hash, PartialEq)]
193pub struct OwningIter(Dir);
194
195impl Iterator for OwningIter {
196    type Item = Result<Entry>;
197
198    fn next(&mut self) -> Option<Self::Item> {
199        readdir(&mut self.0)
200    }
201}
202
203/// The file descriptor continues to be owned by the `OwningIter`,
204/// so callers must not keep a `RawFd` after the `OwningIter` is dropped.
205impl AsRawFd for OwningIter {
206    fn as_raw_fd(&self) -> RawFd {
207        self.0.as_raw_fd()
208    }
209}
210
211impl IntoIterator for Dir {
212    type Item = Result<Entry>;
213    type IntoIter = OwningIter;
214
215    /// Creates a owning iterator, that is, one that takes ownership of the
216    /// `Dir`. The `Dir` cannot be used after calling this.  This can be useful
217    /// when you have a function that both creates a `Dir` instance and returns
218    /// an `Iterator`.
219    ///
220    /// Example:
221    ///
222    /// ```
223    /// use nix::{dir::Dir, fcntl::OFlag, sys::stat::Mode};
224    /// use std::{iter::Iterator, string::String};
225    ///
226    /// fn ls_upper(dirname: &str) -> impl Iterator<Item=String> {
227    ///     let d = Dir::open(dirname, OFlag::O_DIRECTORY, Mode::S_IXUSR).unwrap();
228    ///     d.into_iter().map(|x| x.unwrap().file_name().as_ref().to_string_lossy().to_ascii_uppercase())
229    /// }
230    /// ```
231    fn into_iter(self) -> Self::IntoIter {
232        OwningIter(self)
233    }
234}
235
236/// A directory entry, similar to `std::fs::DirEntry`.
237///
238/// Note that unlike the std version, this may represent the `.` or `..` entries.
239#[derive(Clone, Debug, Eq, Hash, PartialEq)]
240pub struct Entry {
241    ino: u64,
242    type_: Option<Type>,
243    // On some platforms libc::dirent is a "flexible-length structure", where there may be
244    // data beyond the end of the struct definition.  So it isn't possible to copy it and subsequently
245    // dereference the copy's d_name field.  Nor is it possible for Entry to wrap a &libc::dirent.
246    // At least, not if it is to work with the Iterator trait.  Because the Entry would need to
247    // maintain a mutable reference to the Dir, which would conflict with the iterator's mutable
248    // reference to the same Dir.  So we're forced to copy the name here.
249    name: CString,
250}
251
252/// Type of file referenced by a directory entry
253#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
254pub enum Type {
255    /// FIFO (Named pipe)
256    Fifo,
257    /// Character device
258    CharacterDevice,
259    /// Directory
260    Directory,
261    /// Block device
262    BlockDevice,
263    /// Regular file
264    File,
265    /// Symbolic link
266    Symlink,
267    /// Unix-domain socket
268    Socket,
269}
270
271impl Entry {
272    /// Returns the inode number (`d_ino`) of the underlying `dirent`.
273    pub fn ino(&self) -> u64 {
274        self.ino
275    }
276
277    /// Returns the bare file name of this directory entry without any other leading path component.
278    pub fn file_name(&self) -> &CStr {
279        self.name.as_c_str()
280    }
281
282    /// Returns the type of this directory entry, if known.
283    ///
284    /// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known;
285    /// notably, some Linux filesystems don't implement this. The caller should use `stat` or
286    /// `fstat` if this returns `None`.
287    pub fn file_type(&self) -> Option<Type> {
288        self.type_
289    }
290
291    #[allow(clippy::useless_conversion)] // Not useless on all OSes
292    // The cast is not unnecessary on all platforms.
293    #[allow(clippy::unnecessary_cast)]
294    fn from_raw(de: &libc::dirent) -> Self {
295        cfg_if! {
296            if #[cfg(any(target_os = "aix",
297                         target_os = "emscripten",
298                         target_os = "fuchsia",
299                         target_os = "haiku",
300                         target_os = "hurd",
301                         target_os = "cygwin",
302                         solarish,
303                         linux_android,
304                         apple_targets))] {
305                let ino = de.d_ino as u64;
306            } else {
307                let ino = u64::from(de.d_fileno);
308            }
309        }
310        cfg_if! {
311            if #[cfg(not(any(solarish, target_os = "aix", target_os = "haiku")))] {
312                let type_ = match de.d_type {
313                    libc::DT_FIFO => Some(Type::Fifo),
314                    libc::DT_CHR => Some(Type::CharacterDevice),
315                    libc::DT_DIR => Some(Type::Directory),
316                    libc::DT_BLK => Some(Type::BlockDevice),
317                    libc::DT_REG => Some(Type::File),
318                    libc::DT_LNK => Some(Type::Symlink),
319                    libc::DT_SOCK => Some(Type::Socket),
320                    /* libc::DT_UNKNOWN | */ _ => None,
321                };
322            } else {
323                // illumos, Solaris, and Haiku systems do not have the d_type member at all:
324                #[cfg(any(solarish, target_os = "aix", target_os = "haiku"))]
325                let type_ = None;
326            }
327        }
328        // Annoyingly, since libc::dirent is open-ended, the easiest way to read the name field in
329        // Rust is to treat it like a pointer.
330        // Safety: safe because we knod that de.d_name is in valid memory.
331        let name = unsafe { CStr::from_ptr(de.d_name.as_ptr()) }.to_owned();
332
333        Entry { ino, type_, name }
334    }
335}