1 use crate::{Error, NixPath, Result};
2 use crate::errno::Errno;
3 use crate::fcntl::{self, OFlag};
4 use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
5 use std::ptr;
6 use std::ffi;
7 use crate::sys;
8 
9 #[cfg(target_os = "linux")]
10 use libc::{dirent64 as dirent, readdir64_r as readdir_r};
11 
12 #[cfg(not(target_os = "linux"))]
13 use 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(Clone, Debug, Eq, Hash, PartialEq)]
29 pub struct Dir(
30     ptr::NonNull<libc::DIR>
31 );
32 
33 impl Dir {
34     /// Opens the given path as with `fcntl::open`.
open<P: ?Sized + NixPath>(path: &P, oflag: OFlag, mode: sys::stat::Mode) -> Result<Self>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`.
openat<P: ?Sized + NixPath>(dirfd: RawFd, path: &P, oflag: OFlag, mode: sys::stat::Mode) -> Result<Self>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]
from<F: IntoRawFd>(fd: F) -> Result<Self>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.
from_fd(fd: RawFd) -> Result<Self>55     pub fn from_fd(fd: RawFd) -> Result<Self> {
56         let d = unsafe { libc::fdopendir(fd) };
57         if d.is_null() {
58             let e = Error::last();
59             unsafe { libc::close(fd) };
60             return Err(e);
61         };
62         // Always guaranteed to be non-null by the previous check
63         Ok(Dir(ptr::NonNull::new(d).unwrap()))
64     }
65 
66     /// Returns an iterator of `Result<Entry>` which rewinds when finished.
iter(&mut self) -> Iter67     pub fn iter(&mut self) -> Iter {
68         Iter(self)
69     }
70 }
71 
72 // `Dir` is not `Sync`. With the current implementation, it could be, but according to
73 // https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html,
74 // future versions of POSIX are likely to obsolete `readdir_r` and specify that it's unsafe to
75 // call `readdir` simultaneously from multiple threads.
76 //
77 // `Dir` is safe to pass from one thread to another, as it's not reference-counted.
78 unsafe impl Send for Dir {}
79 
80 impl AsRawFd for Dir {
as_raw_fd(&self) -> RawFd81     fn as_raw_fd(&self) -> RawFd {
82         unsafe { libc::dirfd(self.0.as_ptr()) }
83     }
84 }
85 
86 impl Drop for Dir {
drop(&mut self)87     fn drop(&mut self) {
88         unsafe { libc::closedir(self.0.as_ptr()) };
89     }
90 }
91 
92 #[derive(Debug, Eq, Hash, PartialEq)]
93 pub struct Iter<'d>(&'d mut Dir);
94 
95 impl<'d> Iterator for Iter<'d> {
96     type Item = Result<Entry>;
97 
next(&mut self) -> Option<Self::Item>98     fn next(&mut self) -> Option<Self::Item> {
99         unsafe {
100             // Note: POSIX specifies that portable applications should dynamically allocate a
101             // buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1
102             // for the NUL byte. It doesn't look like the std library does this; it just uses
103             // fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate).
104             // Probably fine here too then.
105             let mut ent = std::mem::MaybeUninit::<dirent>::uninit();
106             let mut result = ptr::null_mut();
107             if let Err(e) = Errno::result(
108                 readdir_r((self.0).0.as_ptr(), ent.as_mut_ptr(), &mut result))
109             {
110                 return Some(Err(e));
111             }
112             if result.is_null() {
113                 return None;
114             }
115             assert_eq!(result, ent.as_mut_ptr());
116             Some(Ok(Entry(ent.assume_init())))
117         }
118     }
119 }
120 
121 impl<'d> Drop for Iter<'d> {
drop(&mut self)122     fn drop(&mut self) {
123         unsafe { libc::rewinddir((self.0).0.as_ptr()) }
124     }
125 }
126 
127 /// A directory entry, similar to `std::fs::DirEntry`.
128 ///
129 /// Note that unlike the std version, this may represent the `.` or `..` entries.
130 #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
131 #[repr(transparent)]
132 pub struct Entry(dirent);
133 
134 #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
135 pub enum Type {
136     Fifo,
137     CharacterDevice,
138     Directory,
139     BlockDevice,
140     File,
141     Symlink,
142     Socket,
143 }
144 
145 impl Entry {
146     /// Returns the inode number (`d_ino`) of the underlying `dirent`.
147     #[cfg(any(target_os = "android",
148               target_os = "emscripten",
149               target_os = "fuchsia",
150               target_os = "haiku",
151               target_os = "ios",
152               target_os = "l4re",
153               target_os = "linux",
154               target_os = "macos",
155               target_os = "solaris"))]
ino(&self) -> u64156     pub fn ino(&self) -> u64 {
157         self.0.d_ino as u64
158     }
159 
160     /// Returns the inode number (`d_fileno`) of the underlying `dirent`.
161     #[cfg(not(any(target_os = "android",
162                   target_os = "emscripten",
163                   target_os = "fuchsia",
164                   target_os = "haiku",
165                   target_os = "ios",
166                   target_os = "l4re",
167                   target_os = "linux",
168                   target_os = "macos",
169                   target_os = "solaris")))]
ino(&self) -> u64170     pub fn ino(&self) -> u64 {
171         u64::from(self.0.d_fileno)
172     }
173 
174     /// Returns the bare file name of this directory entry without any other leading path component.
file_name(&self) -> &ffi::CStr175     pub fn file_name(&self) -> &ffi::CStr {
176         unsafe { ::std::ffi::CStr::from_ptr(self.0.d_name.as_ptr()) }
177     }
178 
179     /// Returns the type of this directory entry, if known.
180     ///
181     /// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known;
182     /// notably, some Linux filesystems don't implement this. The caller should use `stat` or
183     /// `fstat` if this returns `None`.
file_type(&self) -> Option<Type>184     pub fn file_type(&self) -> Option<Type> {
185         match self.0.d_type {
186             libc::DT_FIFO => Some(Type::Fifo),
187             libc::DT_CHR => Some(Type::CharacterDevice),
188             libc::DT_DIR => Some(Type::Directory),
189             libc::DT_BLK => Some(Type::BlockDevice),
190             libc::DT_REG => Some(Type::File),
191             libc::DT_LNK => Some(Type::Symlink),
192             libc::DT_SOCK => Some(Type::Socket),
193             /* libc::DT_UNKNOWN | */ _ => None,
194         }
195     }
196 }
197