nix/
dir.rs

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