diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/nix/src/dir.rs | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/nix/src/dir.rs')
-rw-r--r-- | third_party/rust/nix/src/dir.rs | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/third_party/rust/nix/src/dir.rs b/third_party/rust/nix/src/dir.rs new file mode 100644 index 0000000000..5ce503644e --- /dev/null +++ b/third_party/rust/nix/src/dir.rs @@ -0,0 +1,276 @@ +//! List directory contents + +use crate::errno::Errno; +use crate::fcntl::{self, OFlag}; +use crate::sys; +use crate::{Error, NixPath, Result}; +use cfg_if::cfg_if; +use std::ffi; +use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; +use std::ptr; + +#[cfg(target_os = "linux")] +use libc::{dirent64 as dirent, readdir64_r as readdir_r}; + +#[cfg(not(target_os = "linux"))] +use libc::{dirent, readdir_r}; + +/// An open directory. +/// +/// This is a lower-level interface than `std::fs::ReadDir`. Notable differences: +/// * can be opened from a file descriptor (as returned by `openat`, perhaps before knowing +/// if the path represents a file or directory). +/// * implements `AsRawFd`, so it can be passed to `fstat`, `openat`, etc. +/// The file descriptor continues to be owned by the `Dir`, so callers must not keep a `RawFd` +/// after the `Dir` is dropped. +/// * can be iterated through multiple times without closing and reopening the file +/// descriptor. Each iteration rewinds when finished. +/// * returns entries for `.` (current directory) and `..` (parent directory). +/// * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc +/// does). +#[derive(Debug, Eq, Hash, PartialEq)] +pub struct Dir(ptr::NonNull<libc::DIR>); + +impl Dir { + /// Opens the given path as with `fcntl::open`. + pub fn open<P: ?Sized + NixPath>( + path: &P, + oflag: OFlag, + mode: sys::stat::Mode, + ) -> Result<Self> { + let fd = fcntl::open(path, oflag, mode)?; + Dir::from_fd(fd) + } + + /// Opens the given path as with `fcntl::openat`. + pub fn openat<P: ?Sized + NixPath>( + dirfd: RawFd, + path: &P, + oflag: OFlag, + mode: sys::stat::Mode, + ) -> Result<Self> { + let fd = fcntl::openat(dirfd, path, oflag, mode)?; + Dir::from_fd(fd) + } + + /// Converts from a descriptor-based object, closing the descriptor on success or failure. + #[inline] + pub fn from<F: IntoRawFd>(fd: F) -> Result<Self> { + Dir::from_fd(fd.into_raw_fd()) + } + + /// Converts from a file descriptor, closing it on success or failure. + #[doc(alias("fdopendir"))] + pub fn from_fd(fd: RawFd) -> Result<Self> { + let d = ptr::NonNull::new(unsafe { libc::fdopendir(fd) }).ok_or_else( + || { + let e = Error::last(); + unsafe { libc::close(fd) }; + e + }, + )?; + Ok(Dir(d)) + } + + /// Returns an iterator of `Result<Entry>` which rewinds when finished. + pub fn iter(&mut self) -> Iter { + Iter(self) + } +} + +// `Dir` is not `Sync`. With the current implementation, it could be, but according to +// https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html, +// future versions of POSIX are likely to obsolete `readdir_r` and specify that it's unsafe to +// call `readdir` simultaneously from multiple threads. +// +// `Dir` is safe to pass from one thread to another, as it's not reference-counted. +unsafe impl Send for Dir {} + +impl AsRawFd for Dir { + fn as_raw_fd(&self) -> RawFd { + unsafe { libc::dirfd(self.0.as_ptr()) } + } +} + +impl Drop for Dir { + fn drop(&mut self) { + let e = Errno::result(unsafe { libc::closedir(self.0.as_ptr()) }); + if !std::thread::panicking() && e == Err(Errno::EBADF) { + panic!("Closing an invalid file descriptor!"); + }; + } +} + +fn next(dir: &mut Dir) -> Option<Result<Entry>> { + unsafe { + // Note: POSIX specifies that portable applications should dynamically allocate a + // buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1 + // for the NUL byte. It doesn't look like the std library does this; it just uses + // fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate). + // Probably fine here too then. + let mut ent = std::mem::MaybeUninit::<dirent>::uninit(); + let mut result = ptr::null_mut(); + if let Err(e) = Errno::result(readdir_r( + dir.0.as_ptr(), + ent.as_mut_ptr(), + &mut result, + )) { + return Some(Err(e)); + } + if result.is_null() { + return None; + } + assert_eq!(result, ent.as_mut_ptr()); + Some(Ok(Entry(ent.assume_init()))) + } +} + +/// Return type of [`Dir::iter`]. +#[derive(Debug, Eq, Hash, PartialEq)] +pub struct Iter<'d>(&'d mut Dir); + +impl<'d> Iterator for Iter<'d> { + type Item = Result<Entry>; + + fn next(&mut self) -> Option<Self::Item> { + next(self.0) + } +} + +impl<'d> Drop for Iter<'d> { + fn drop(&mut self) { + unsafe { libc::rewinddir((self.0).0.as_ptr()) } + } +} + +/// The return type of [Dir::into_iter] +#[derive(Debug, Eq, Hash, PartialEq)] +pub struct OwningIter(Dir); + +impl Iterator for OwningIter { + type Item = Result<Entry>; + + fn next(&mut self) -> Option<Self::Item> { + next(&mut self.0) + } +} + +/// The file descriptor continues to be owned by the `OwningIter`, +/// so callers must not keep a `RawFd` after the `OwningIter` is dropped. +impl AsRawFd for OwningIter { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + +impl IntoIterator for Dir { + type Item = Result<Entry>; + type IntoIter = OwningIter; + + /// Creates a owning iterator, that is, one that takes ownership of the + /// `Dir`. The `Dir` cannot be used after calling this. This can be useful + /// when you have a function that both creates a `Dir` instance and returns + /// an `Iterator`. + /// + /// Example: + /// + /// ``` + /// use nix::{dir::Dir, fcntl::OFlag, sys::stat::Mode}; + /// use std::{iter::Iterator, string::String}; + /// + /// fn ls_upper(dirname: &str) -> impl Iterator<Item=String> { + /// let d = Dir::open(dirname, OFlag::O_DIRECTORY, Mode::S_IXUSR).unwrap(); + /// d.into_iter().map(|x| x.unwrap().file_name().as_ref().to_string_lossy().to_ascii_uppercase()) + /// } + /// ``` + fn into_iter(self) -> Self::IntoIter { + OwningIter(self) + } +} + +/// A directory entry, similar to `std::fs::DirEntry`. +/// +/// Note that unlike the std version, this may represent the `.` or `..` entries. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +#[repr(transparent)] +pub struct Entry(dirent); + +/// Type of file referenced by a directory entry +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub enum Type { + /// FIFO (Named pipe) + Fifo, + /// Character device + CharacterDevice, + /// Directory + Directory, + /// Block device + BlockDevice, + /// Regular file + File, + /// Symbolic link + Symlink, + /// Unix-domain socket + Socket, +} + +impl Entry { + /// Returns the inode number (`d_ino`) of the underlying `dirent`. + #[allow(clippy::useless_conversion)] // Not useless on all OSes + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + pub fn ino(&self) -> u64 { + cfg_if! { + if #[cfg(any(target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "l4re", + target_os = "linux", + target_os = "macos", + target_os = "solaris"))] { + self.0.d_ino as u64 + } else { + u64::from(self.0.d_fileno) + } + } + } + + /// Returns the bare file name of this directory entry without any other leading path component. + pub fn file_name(&self) -> &ffi::CStr { + unsafe { ::std::ffi::CStr::from_ptr(self.0.d_name.as_ptr()) } + } + + /// Returns the type of this directory entry, if known. + /// + /// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known; + /// notably, some Linux filesystems don't implement this. The caller should use `stat` or + /// `fstat` if this returns `None`. + pub fn file_type(&self) -> Option<Type> { + #[cfg(not(any( + target_os = "illumos", + target_os = "solaris", + target_os = "haiku" + )))] + match self.0.d_type { + libc::DT_FIFO => Some(Type::Fifo), + libc::DT_CHR => Some(Type::CharacterDevice), + libc::DT_DIR => Some(Type::Directory), + libc::DT_BLK => Some(Type::BlockDevice), + libc::DT_REG => Some(Type::File), + libc::DT_LNK => Some(Type::Symlink), + libc::DT_SOCK => Some(Type::Socket), + /* libc::DT_UNKNOWN | */ _ => None, + } + + // illumos, Solaris, and Haiku systems do not have the d_type member at all: + #[cfg(any( + target_os = "illumos", + target_os = "solaris", + target_os = "haiku" + ))] + None + } +} |