openpgp_cert_d/
unixdir.rs1use std::cell::OnceCell;
2use std::ffi::CStr;
3use std::ffi::c_char;
4use std::os::unix::ffi::OsStringExt;
5use std::path::Path;
6
7use crate::Tag;
8
9type Result<T> = std::result::Result<T, std::io::Error>;
10
11pub(crate) struct FileType(u8);
12
13impl FileType {
14 pub fn is_dir(&self) -> bool {
23 self.0 == libc::DT_DIR
24 }
25
26 pub fn is_unknown(&self) -> bool {
28 self.0 == libc::DT_UNKNOWN
29 }
30}
31
32#[cfg(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd"))]
45mod libc64 {
46 pub(super) use libc::stat64 as stat;
47 pub(super) use libc::fstatat64 as fstatat;
48 pub(super) use libc::dirent64 as dirent;
49 pub(super) use libc::readdir64 as readdir;
50}
51#[cfg(not(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd")))]
52mod libc64 {
53 pub(super) use libc::stat;
54 pub(super) use libc::fstatat;
55 pub(super) use libc::dirent;
56 pub(super) use libc::readdir;
57}
58
59pub(crate) struct Metadata(libc64::stat);
61
62impl Metadata {
63 pub fn size(&self) -> u64 {
65 self.0.st_size as u64
66 }
67
68 pub fn modified(&self) -> std::time::Duration {
70 #[cfg(any(target_os = "openbsd", target_os = "netbsd"))]
73 return std::time::Duration::new(
74 self.0.st_mtime as u64,
75 self.0.st_mtimensec as u32);
76
77 #[cfg(not(any(target_os = "openbsd", target_os = "netbsd")))]
78 return std::time::Duration::new(
79 self.0.st_mtime as u64,
80 self.0.st_mtime_nsec as u32);
81 }
82
83 pub fn is_dir(&self) -> bool {
85 (self.0.st_mode & libc::S_IFMT) == libc::S_IFDIR
86 }
87}
88
89impl std::convert::From<&crate::unixdir::Metadata> for Tag {
90 fn from(m: &Metadata) -> Self {
91 let d = m.modified();
92 let size = m.size();
93
94 Tag::new(d.as_secs(), d.subsec_nanos(), size, m.is_dir())
95 }
96}
97
98impl Metadata {
99 fn fstat(dir: *mut libc::DIR,
100 nul_terminated_filename: &[u8])
101 -> Result<Self>
102 {
103 assert_eq!(nul_terminated_filename[nul_terminated_filename.len() - 1],
105 0);
106
107 let dirfd = unsafe { libc::dirfd(dir) };
108 if dirfd == -1 {
109 return Err(std::io::Error::last_os_error());
110 }
111
112 let mut statbuf = std::mem::MaybeUninit::<libc64::stat>::uninit();
113
114 let result = unsafe {
115 libc64::fstatat(
116 dirfd,
117 nul_terminated_filename.as_ptr() as *const c_char,
118 statbuf.as_mut_ptr(),
119 libc::AT_SYMLINK_NOFOLLOW,
120 )
121 };
122 if result == -1 {
123 return Err(std::io::Error::last_os_error());
124 }
125
126 Ok(Metadata(unsafe { statbuf.assume_init() }))
127 }
128}
129
130pub(crate) struct DirEntry {
134 dir: *mut libc::DIR,
135
136 entry: *mut libc64::dirent,
166
167 name_len: OnceCell<usize>,
168 metadata: OnceCell<Result<Metadata>>,
170}
171
172impl DirEntry {
173 fn entry_d_type(&self) -> libc::c_uchar {
177 unsafe { (&*self.entry).d_type }
178 }
179
180 fn entry_d_name(&self) -> *const c_char {
184 unsafe { (&*self.entry).d_name.as_ptr() as *const c_char }
185 }
186
187 pub fn file_type(&self) -> FileType {
189 FileType(self.entry_d_type())
190 }
191
192 pub fn file_name(&self) -> &[u8] {
196 unsafe {
197 let name = self.entry_d_name();
198
199 let name_len = *self.name_len.get_or_init(|| {
200 libc::strlen(name)
201 });
202
203 std::slice::from_raw_parts(
204 name as *const u8,
205 name_len)
206 }
207 }
208
209 pub fn metadata(&self) -> Result<&Metadata> {
214 let result = self.metadata.get_or_init(|| {
218 let dirfd = unsafe { libc::dirfd(self.dir) };
219 if dirfd == -1 {
220 return Err(std::io::Error::last_os_error());
221 }
222
223 let mut statbuf = std::mem::MaybeUninit::<libc64::stat>::uninit();
224
225 let result = unsafe {
226 libc64::fstatat(
227 dirfd,
228 self.entry_d_name(),
229 statbuf.as_mut_ptr(),
230 libc::AT_SYMLINK_NOFOLLOW,
231 )
232 };
233 if result == -1 {
234 return Err(std::io::Error::last_os_error());
235 }
236
237 Ok(Metadata(unsafe { statbuf.assume_init() }))
238 });
239
240 match result {
241 Ok(metadata) => Ok(metadata),
242 Err(err) => {
243 if let Some(underlying) = err.get_ref() {
244 Err(std::io::Error::new(
248 err.kind(),
249 underlying.to_string()))
250 } else {
251 Err(std::io::Error::from(err.kind()))
252 }
253 },
254 }
255 }
256}
257
258pub(crate) struct Dir {
259 dir: Option<*mut libc::DIR>,
260 entry: Option<DirEntry>,
261}
262
263impl Drop for Dir {
264 fn drop(&mut self) {
265 if let Some(dir) = self.dir.take() {
266 unsafe { libc::closedir(dir) };
267 }
268 self.entry = None;
269 }
270}
271
272impl Dir {
273 pub fn open(dir: &Path) -> Result<Self> {
274 let mut dir = dir.as_os_str().to_os_string().into_vec();
275 dir.push(0);
277 let dir = unsafe { CStr::from_ptr(dir.as_ptr() as *const c_char) };
278 let dir = unsafe { libc::opendir(dir.as_ptr().cast()) };
279 if dir.is_null() {
280 return Err(std::io::Error::last_os_error());
281 }
282
283 let dir = Dir {
284 dir: Some(dir),
285 entry: None,
286 };
287 Ok(dir)
288 }
289
290 pub fn readdir(&mut self) -> Option<&mut DirEntry> {
298 let dir = self.dir?;
299
300 let entry = unsafe { libc64::readdir(dir) };
301 if entry.is_null() {
302 unsafe { libc::closedir(dir) };
303 self.dir = None;
304 self.entry = None;
305 return None;
306 }
307
308 self.entry = Some(DirEntry {
309 dir,
310 entry,
311 name_len: OnceCell::default(),
312 metadata: OnceCell::default(),
313 });
314 self.entry.as_mut()
315 }
316
317 pub fn fstat(&mut self, nul_terminated_filename: &[u8]) -> Result<Metadata> {
319 let dir = self.dir.ok_or_else(|| {
320 std::io::Error::new(std::io::ErrorKind::Other, "Directory closed")
321 })?;
322
323 Metadata::fstat(dir, nul_terminated_filename)
324 }
325}