nix/
dir.rs

1//! List directory contents
2
3use crate::errno::Errno;
4use crate::fcntl::{self, OFlag};
5use crate::sys;
6use crate::{Error, NixPath, Result};
7use cfg_if::cfg_if;
8use std::ffi;
9use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
10use std::ptr;
11
12#[cfg(target_os = "linux")]
13use libc::{dirent64 as dirent, readdir64_r as readdir_r};
14
15#[cfg(not(target_os = "linux"))]
16use libc::{dirent, readdir_r};
17
18/// An open directory.
19///
20/// This is a lower-level interface than `std::fs::ReadDir`. Notable differences:
21///    * can be opened from a file descriptor (as returned by `openat`, perhaps before knowing
22///      if the path represents a file or directory).
23///    * implements `AsRawFd`, so it can be passed to `fstat`, `openat`, etc.
24///      The file descriptor continues to be owned by the `Dir`, so callers must not keep a `RawFd`
25///      after the `Dir` is dropped.
26///    * can be iterated through multiple times without closing and reopening the file
27///      descriptor. Each iteration rewinds when finished.
28///    * returns entries for `.` (current directory) and `..` (parent directory).
29///    * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc
30///      does).
31#[derive(Debug, Eq, Hash, PartialEq)]
32pub struct Dir(ptr::NonNull<libc::DIR>);
33
34impl Dir {
35    /// Opens the given path as with `fcntl::open`.
36    pub fn open<P: ?Sized + NixPath>(
37        path: &P,
38        oflag: OFlag,
39        mode: sys::stat::Mode,
40    ) -> Result<Self> {
41        let fd = fcntl::open(path, oflag, mode)?;
42        Dir::from_fd(fd)
43    }
44
45    /// Opens the given path as with `fcntl::openat`.
46    pub fn openat<P: ?Sized + NixPath>(
47        dirfd: RawFd,
48        path: &P,
49        oflag: OFlag,
50        mode: sys::stat::Mode,
51    ) -> Result<Self> {
52        let fd = fcntl::openat(dirfd, path, oflag, mode)?;
53        Dir::from_fd(fd)
54    }
55
56    /// Converts from a descriptor-based object, closing the descriptor on success or failure.
57    #[inline]
58    pub fn from<F: IntoRawFd>(fd: F) -> Result<Self> {
59        Dir::from_fd(fd.into_raw_fd())
60    }
61
62    /// Converts from a file descriptor, closing it on success or failure.
63    #[doc(alias("fdopendir"))]
64    pub fn from_fd(fd: RawFd) -> Result<Self> {
65        let d = ptr::NonNull::new(unsafe { libc::fdopendir(fd) }).ok_or_else(
66            || {
67                let e = Error::last();
68                unsafe { libc::close(fd) };
69                e
70            },
71        )?;
72        Ok(Dir(d))
73    }
74
75    /// Returns an iterator of `Result<Entry>` which rewinds when finished.
76    pub fn iter(&mut self) -> Iter {
77        Iter(self)
78    }
79}
80
81// `Dir` is not `Sync`. With the current implementation, it could be, but according to
82// https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html,
83// future versions of POSIX are likely to obsolete `readdir_r` and specify that it's unsafe to
84// call `readdir` simultaneously from multiple threads.
85//
86// `Dir` is safe to pass from one thread to another, as it's not reference-counted.
87unsafe impl Send for Dir {}
88
89impl AsRawFd for Dir {
90    fn as_raw_fd(&self) -> RawFd {
91        unsafe { libc::dirfd(self.0.as_ptr()) }
92    }
93}
94
95impl Drop for Dir {
96    fn drop(&mut self) {
97        let e = Errno::result(unsafe { libc::closedir(self.0.as_ptr()) });
98        if !std::thread::panicking() && e == Err(Errno::EBADF) {
99            panic!("Closing an invalid file descriptor!");
100        };
101    }
102}
103
104fn next(dir: &mut Dir) -> Option<Result<Entry>> {
105    unsafe {
106        // Note: POSIX specifies that portable applications should dynamically allocate a
107        // buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1
108        // for the NUL byte. It doesn't look like the std library does this; it just uses
109        // fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate).
110        // Probably fine here too then.
111        let mut ent = std::mem::MaybeUninit::<dirent>::uninit();
112        let mut result = ptr::null_mut();
113        if let Err(e) = Errno::result(readdir_r(
114            dir.0.as_ptr(),
115            ent.as_mut_ptr(),
116            &mut result,
117        )) {
118            return Some(Err(e));
119        }
120        if result.is_null() {
121            return None;
122        }
123        assert_eq!(result, ent.as_mut_ptr());
124        Some(Ok(Entry(ent.assume_init())))
125    }
126}
127
128/// Return type of [`Dir::iter`].
129#[derive(Debug, Eq, Hash, PartialEq)]
130pub struct Iter<'d>(&'d mut Dir);
131
132impl<'d> Iterator for Iter<'d> {
133    type Item = Result<Entry>;
134
135    fn next(&mut self) -> Option<Self::Item> {
136        next(self.0)
137    }
138}
139
140impl<'d> Drop for Iter<'d> {
141    fn drop(&mut self) {
142        unsafe { libc::rewinddir((self.0).0.as_ptr()) }
143    }
144}
145
146/// The return type of [Dir::into_iter]
147#[derive(Debug, Eq, Hash, PartialEq)]
148pub struct OwningIter(Dir);
149
150impl Iterator for OwningIter {
151    type Item = Result<Entry>;
152
153    fn next(&mut self) -> Option<Self::Item> {
154        next(&mut self.0)
155    }
156}
157
158/// The file descriptor continues to be owned by the `OwningIter`,
159/// so callers must not keep a `RawFd` after the `OwningIter` is dropped.
160impl AsRawFd for OwningIter {
161    fn as_raw_fd(&self) -> RawFd {
162        self.0.as_raw_fd()
163    }
164}
165
166impl IntoIterator for Dir {
167    type Item = Result<Entry>;
168    type IntoIter = OwningIter;
169
170    /// Creates a owning iterator, that is, one that takes ownership of the
171    /// `Dir`. The `Dir` cannot be used after calling this.  This can be useful
172    /// when you have a function that both creates a `Dir` instance and returns
173    /// an `Iterator`.
174    ///
175    /// Example:
176    ///
177    /// ```
178    /// use nix::{dir::Dir, fcntl::OFlag, sys::stat::Mode};
179    /// use std::{iter::Iterator, string::String};
180    ///
181    /// fn ls_upper(dirname: &str) -> impl Iterator<Item=String> {
182    ///     let d = Dir::open(dirname, OFlag::O_DIRECTORY, Mode::S_IXUSR).unwrap();
183    ///     d.into_iter().map(|x| x.unwrap().file_name().as_ref().to_string_lossy().to_ascii_uppercase())
184    /// }
185    /// ```
186    fn into_iter(self) -> Self::IntoIter {
187        OwningIter(self)
188    }
189}
190
191/// A directory entry, similar to `std::fs::DirEntry`.
192///
193/// Note that unlike the std version, this may represent the `.` or `..` entries.
194#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
195#[repr(transparent)]
196pub struct Entry(dirent);
197
198/// Type of file referenced by a directory entry
199#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
200pub enum Type {
201    /// FIFO (Named pipe)
202    Fifo,
203    /// Character device
204    CharacterDevice,
205    /// Directory
206    Directory,
207    /// Block device
208    BlockDevice,
209    /// Regular file
210    File,
211    /// Symbolic link
212    Symlink,
213    /// Unix-domain socket
214    Socket,
215}
216
217impl Entry {
218    /// Returns the inode number (`d_ino`) of the underlying `dirent`.
219    #[allow(clippy::useless_conversion)] // Not useless on all OSes
220    // The cast is not unnecessary on all platforms.
221    #[allow(clippy::unnecessary_cast)]
222    pub fn ino(&self) -> u64 {
223        cfg_if! {
224            if #[cfg(any(target_os = "android",
225                         target_os = "emscripten",
226                         target_os = "fuchsia",
227                         target_os = "haiku",
228                         target_os = "illumos",
229                         target_os = "ios",
230                         target_os = "l4re",
231                         target_os = "linux",
232                         target_os = "macos",
233                         target_os = "solaris"))] {
234                self.0.d_ino as u64
235            } else {
236                u64::from(self.0.d_fileno)
237            }
238        }
239    }
240
241    /// Returns the bare file name of this directory entry without any other leading path component.
242    pub fn file_name(&self) -> &ffi::CStr {
243        unsafe { ::std::ffi::CStr::from_ptr(self.0.d_name.as_ptr()) }
244    }
245
246    /// Returns the type of this directory entry, if known.
247    ///
248    /// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known;
249    /// notably, some Linux filesystems don't implement this. The caller should use `stat` or
250    /// `fstat` if this returns `None`.
251    pub fn file_type(&self) -> Option<Type> {
252        #[cfg(not(any(
253            target_os = "illumos",
254            target_os = "solaris",
255            target_os = "haiku"
256        )))]
257        match self.0.d_type {
258            libc::DT_FIFO => Some(Type::Fifo),
259            libc::DT_CHR => Some(Type::CharacterDevice),
260            libc::DT_DIR => Some(Type::Directory),
261            libc::DT_BLK => Some(Type::BlockDevice),
262            libc::DT_REG => Some(Type::File),
263            libc::DT_LNK => Some(Type::Symlink),
264            libc::DT_SOCK => Some(Type::Socket),
265            /* libc::DT_UNKNOWN | */ _ => None,
266        }
267
268        // illumos, Solaris, and Haiku systems do not have the d_type member at all:
269        #[cfg(any(
270            target_os = "illumos",
271            target_os = "solaris",
272            target_os = "haiku"
273        ))]
274        None
275    }
276}