• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! List directory contents
2 
3 use crate::errno::Errno;
4 use crate::fcntl::{self, OFlag};
5 use crate::sys;
6 use crate::{Error, NixPath, Result};
7 use cfg_if::cfg_if;
8 use std::ffi;
9 use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
10 use std::ptr;
11 
12 #[cfg(target_os = "linux")]
13 use libc::{dirent64 as dirent, readdir64_r as readdir_r};
14 
15 #[cfg(not(target_os = "linux"))]
16 use 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)]
32 pub struct Dir(ptr::NonNull<libc::DIR>);
33 
34 impl Dir {
35     /// Opens the given path as with `fcntl::open`.
open<P: ?Sized + NixPath>( path: &P, oflag: OFlag, mode: sys::stat::Mode, ) -> Result<Self>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`.
openat<P: ?Sized + NixPath>( dirfd: RawFd, path: &P, oflag: OFlag, mode: sys::stat::Mode, ) -> Result<Self>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]
from<F: IntoRawFd>(fd: F) -> Result<Self>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"))]
from_fd(fd: RawFd) -> Result<Self>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.
iter(&mut self) -> Iter76     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.
87 unsafe impl Send for Dir {}
88 
89 impl AsRawFd for Dir {
as_raw_fd(&self) -> RawFd90     fn as_raw_fd(&self) -> RawFd {
91         unsafe { libc::dirfd(self.0.as_ptr()) }
92     }
93 }
94 
95 impl Drop for Dir {
drop(&mut self)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 
next(dir: &mut Dir) -> Option<Result<Entry>>104 fn 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)]
130 pub struct Iter<'d>(&'d mut Dir);
131 
132 impl<'d> Iterator for Iter<'d> {
133     type Item = Result<Entry>;
134 
next(&mut self) -> Option<Self::Item>135     fn next(&mut self) -> Option<Self::Item> {
136         next(self.0)
137     }
138 }
139 
140 impl<'d> Drop for Iter<'d> {
drop(&mut self)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)]
148 pub struct OwningIter(Dir);
149 
150 impl Iterator for OwningIter {
151     type Item = Result<Entry>;
152 
next(&mut self) -> Option<Self::Item>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.
160 impl AsRawFd for OwningIter {
as_raw_fd(&self) -> RawFd161     fn as_raw_fd(&self) -> RawFd {
162         self.0.as_raw_fd()
163     }
164 }
165 
166 impl 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     /// ```
into_iter(self) -> Self::IntoIter186     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)]
196 pub struct Entry(dirent);
197 
198 /// Type of file referenced by a directory entry
199 #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
200 pub 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 
217 impl 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)]
ino(&self) -> u64222     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.
file_name(&self) -> &ffi::CStr242     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`.
file_type(&self) -> Option<Type>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 }
277