summaryrefslogtreecommitdiffstats
path: root/openat/src/dir.rs
diff options
context:
space:
mode:
authorTomasz Kramkowski <tomasz@kramkow.ski>2023-01-27 13:58:10 +0000
committerTomasz Kramkowski <tomasz@kramkow.ski>2023-01-27 13:58:10 +0000
commit6cef9f0fc159de4c9fd708050ec76adb4e74d390 (patch)
tree81856b3b4da6d18e10516b0be737a157e13f129b /openat/src/dir.rs
parent9e8dd00da25273fba9f0cafccbde2236e04fb24e (diff)
downloadpam_usercg_rust-6cef9f0fc159de4c9fd708050ec76adb4e74d390.tar.gz
pam_usercg_rust-6cef9f0fc159de4c9fd708050ec76adb4e74d390.tar.xz
pam_usercg_rust-6cef9f0fc159de4c9fd708050ec76adb4e74d390.zip
openat variant
Diffstat (limited to 'openat/src/dir.rs')
-rw-r--r--openat/src/dir.rs696
1 files changed, 696 insertions, 0 deletions
diff --git a/openat/src/dir.rs b/openat/src/dir.rs
new file mode 100644
index 0000000..eac2f38
--- /dev/null
+++ b/openat/src/dir.rs
@@ -0,0 +1,696 @@
+use std::io;
+use std::mem;
+use std::ffi::{OsString, CStr};
+use std::fs::{File, read_link};
+use std::os::unix::io::{AsRawFd, RawFd, FromRawFd, IntoRawFd};
+use std::os::unix::ffi::{OsStringExt};
+use std::path::{PathBuf};
+
+use libc;
+use crate::metadata::{self, Metadata};
+use crate::list::{DirIter, open_dir, open_dirfd};
+
+use crate::{Dir, AsPath};
+
+#[cfg(target_os="linux")]
+const BASE_OPEN_FLAGS: libc::c_int = libc::O_PATH|libc::O_CLOEXEC;
+#[cfg(target_os="freebsd")]
+const BASE_OPEN_FLAGS: libc::c_int = libc::O_DIRECTORY|libc::O_CLOEXEC;
+#[cfg(not(any(target_os="linux", target_os="freebsd")))]
+const BASE_OPEN_FLAGS: libc::c_int = libc::O_CLOEXEC;
+
+impl Dir {
+ /// Creates a directory descriptor that resolves paths relative to current
+ /// working directory (AT_FDCWD)
+ #[deprecated(since="0.1.15", note="\
+ Use `Dir::open(\".\")` instead. \
+ Dir::cwd() doesn't open actual file descriptor and uses magic value \
+ instead which resolves to current dir on any syscall invocation. \
+ This is usually counter-intuitive and yields a broken \
+ file descriptor when using `Dir::as_raw_fd`. \
+ Will be removed in version v0.2 of the library.")]
+ pub fn cwd() -> Dir {
+ Dir(libc::AT_FDCWD)
+ }
+
+ /// Open a directory descriptor at specified path
+ // TODO(tailhook) maybe accept only absolute paths?
+ pub fn open<P: AsPath>(path: P) -> io::Result<Dir> {
+ Dir::_open(to_cstr(path)?.as_ref())
+ }
+
+ fn _open(path: &CStr) -> io::Result<Dir> {
+ let fd = unsafe {
+ libc::open(path.as_ptr(), BASE_OPEN_FLAGS)
+ };
+ if fd < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(Dir(fd))
+ }
+ }
+
+ /// List subdirectory of this dir
+ ///
+ /// You can list directory itself with `list_self`.
+ pub fn list_dir<P: AsPath>(&self, path: P) -> io::Result<DirIter> {
+ open_dir(self, to_cstr(path)?.as_ref())
+ }
+
+ /// List this dir
+ pub fn list_self(&self) -> io::Result<DirIter> {
+ unsafe {
+ open_dirfd(libc::dup(self.0))
+ }
+ }
+
+ /// Open subdirectory
+ ///
+ /// Note that this method does not resolve symlinks by default, so you may have to call
+ /// [`read_link`] to resolve the real path first.
+ ///
+ /// [`read_link`]: #method.read_link
+ pub fn sub_dir<P: AsPath>(&self, path: P) -> io::Result<Dir> {
+ self._sub_dir(to_cstr(path)?.as_ref())
+ }
+
+ fn _sub_dir(&self, path: &CStr) -> io::Result<Dir> {
+ let fd = unsafe {
+ libc::openat(self.0,
+ path.as_ptr(),
+ BASE_OPEN_FLAGS|libc::O_NOFOLLOW)
+ };
+ if fd < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(Dir(fd))
+ }
+ }
+
+ /// Read link in this directory
+ pub fn read_link<P: AsPath>(&self, path: P) -> io::Result<PathBuf> {
+ self._read_link(to_cstr(path)?.as_ref())
+ }
+
+ fn _read_link(&self, path: &CStr) -> io::Result<PathBuf> {
+ let mut buf = vec![0u8; 4096];
+ let res = unsafe {
+ libc::readlinkat(self.0,
+ path.as_ptr(),
+ buf.as_mut_ptr() as *mut libc::c_char, buf.len())
+ };
+ if res < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ buf.truncate(res as usize);
+ Ok(OsString::from_vec(buf).into())
+ }
+ }
+
+ /// Open file for reading in this directory
+ ///
+ /// Note that this method does not resolve symlinks by default, so you may have to call
+ /// [`read_link`] to resolve the real path first.
+ ///
+ /// [`read_link`]: #method.read_link
+ pub fn open_file<P: AsPath>(&self, path: P) -> io::Result<File> {
+ self._open_file(to_cstr(path)?.as_ref(),
+ libc::O_RDONLY, 0)
+ }
+
+ /// Open file with specified flags relative to this directory
+ pub fn open_file_ex<P: AsPath>(&self, path: P, flags: libc::c_int, mode: libc::mode_t) -> io::Result<File> {
+ self._open_file(to_cstr(path)?.as_ref(), flags, mode)
+ }
+
+ /// Open file for writing, create if necessary, truncate on open
+ ///
+ /// If there exists a symlink at the destination path, this method will fail. In that case, you
+ /// will need to remove the symlink before calling this method. If you are on Linux, you can
+ /// alternatively create an unnamed file with [`new_unnamed_file`] and then rename it,
+ /// clobbering the symlink at the destination.
+ ///
+ /// [`new_unnamed_file`]: #method.new_unnamed_file
+ pub fn write_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
+ -> io::Result<File>
+ {
+ self._open_file(to_cstr(path)?.as_ref(),
+ libc::O_CREAT|libc::O_WRONLY|libc::O_TRUNC,
+ mode)
+ }
+
+ /// Open file for append, create if necessary
+ ///
+ /// If there exists a symlink at the destination path, this method will fail. In that case, you
+ /// will need to call [`read_link`] to resolve the real path first.
+ ///
+ /// [`read_link`]: #method.read_link
+ pub fn append_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
+ -> io::Result<File>
+ {
+ self._open_file(to_cstr(path)?.as_ref(),
+ libc::O_CREAT|libc::O_WRONLY|libc::O_APPEND,
+ mode)
+ }
+
+ /// Create file for writing (and truncate) in this directory
+ ///
+ /// Deprecated alias for `write_file`
+ ///
+ /// If there exists a symlink at the destination path, this method will fail. In that case, you
+ /// will need to remove the symlink before calling this method. If you are on Linux, you can
+ /// alternatively create an unnamed file with [`new_unnamed_file`] and then rename it,
+ /// clobbering the symlink at the destination.
+ ///
+ /// [`new_unnamed_file`]: #method.new_unnamed_file
+ #[deprecated(since="0.1.7", note="please use `write_file` instead")]
+ pub fn create_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
+ -> io::Result<File>
+ {
+ self._open_file(to_cstr(path)?.as_ref(),
+ libc::O_CREAT|libc::O_WRONLY|libc::O_TRUNC,
+ mode)
+ }
+
+ /// Create a tmpfile in this directory which isn't linked to any filename
+ ///
+ /// This works by passing `O_TMPFILE` into the openat call. The flag is
+ /// supported only on linux. So this function always returns error on
+ /// such systems.
+ ///
+ /// **WARNING!** On glibc < 2.22 file permissions of the newly created file
+ /// may be arbitrary. Consider chowning after creating a file.
+ ///
+ /// Note: It may be unclear why creating unnamed file requires a dir. There
+ /// are two reasons:
+ ///
+ /// 1. It's created (and occupies space) on a real filesystem, so the
+ /// directory is a way to find out which filesystem to attach file to
+ /// 2. This method is mostly needed to initialize the file then link it
+ /// using ``link_file_at`` to the real directory entry. When linking
+ /// it must be linked into the same filesystem. But because for most
+ /// programs finding out filesystem layout is an overkill the rule of
+ /// thumb is to create a file in the the target directory.
+ ///
+ /// Currently, we recommend to fallback on any error if this operation
+ /// can't be accomplished rather than relying on specific error codes,
+ /// because semantics of errors are very ugly.
+ #[cfg(target_os="linux")]
+ pub fn new_unnamed_file(&self, mode: libc::mode_t)
+ -> io::Result<File>
+ {
+ self._open_file(unsafe { CStr::from_bytes_with_nul_unchecked(b".\0") },
+ libc::O_TMPFILE|libc::O_WRONLY,
+ mode)
+ }
+
+ /// Create a tmpfile in this directory which isn't linked to any filename
+ ///
+ /// This works by passing `O_TMPFILE` into the openat call. The flag is
+ /// supported only on linux. So this function always returns error on
+ /// such systems.
+ ///
+ /// Note: It may be unclear why creating unnamed file requires a dir. There
+ /// are two reasons:
+ ///
+ /// 1. It's created (and occupies space) on a real filesystem, so the
+ /// directory is a way to find out which filesystem to attach file to
+ /// 2. This method is mostly needed to initialize the file then link it
+ /// using ``link_file_at`` to the real directory entry. When linking
+ /// it must be linked into the same filesystem. But because for most
+ /// programs finding out filesystem layout is an overkill the rule of
+ /// thumb is to create a file in the the target directory.
+ ///
+ /// Currently, we recommend to fallback on any error if this operation
+ /// can't be accomplished rather than relying on specific error codes,
+ /// because semantics of errors are very ugly.
+ #[cfg(not(target_os="linux"))]
+ pub fn new_unnamed_file<P: AsPath>(&self, _mode: libc::mode_t)
+ -> io::Result<File>
+ {
+ Err(io::Error::new(io::ErrorKind::Other,
+ "creating unnamed tmpfiles is only supported on linux"))
+ }
+
+ /// Link open file to a specified path
+ ///
+ /// This is used with ``new_unnamed_file()`` to create and initialize the
+ /// file before linking it into a filesystem. This requires `/proc` to be
+ /// mounted and works **only on linux**.
+ ///
+ /// On systems other than linux this always returns error. It's expected
+ /// that in most cases this methos is not called if ``new_unnamed_file``
+ /// fails. But in obscure scenarios where `/proc` is not mounted this
+ /// method may fail even on linux. So your code should be able to fallback
+ /// to a named file if this method fails too.
+ #[cfg(target_os="linux")]
+ pub fn link_file_at<F: AsRawFd, P: AsPath>(&self, file: &F, path: P)
+ -> io::Result<()>
+ {
+ let fd_path = format!("/proc/self/fd/{}", file.as_raw_fd());
+ _hardlink(&Dir(libc::AT_FDCWD), to_cstr(fd_path)?.as_ref(),
+ &self, to_cstr(path)?.as_ref(),
+ libc::AT_SYMLINK_FOLLOW)
+ }
+
+ /// Link open file to a specified path
+ ///
+ /// This is used with ``new_unnamed_file()`` to create and initialize the
+ /// file before linking it into a filesystem. This requires `/proc` to be
+ /// mounted and works **only on linux**.
+ ///
+ /// On systems other than linux this always returns error. It's expected
+ /// that in most cases this methos is not called if ``new_unnamed_file``
+ /// fails. But in obscure scenarios where `/proc` is not mounted this
+ /// method may fail even on linux. So your code should be able to fallback
+ /// to a named file if this method fails too.
+ #[cfg(not(target_os="linux"))]
+ pub fn link_file_at<F: AsRawFd, P: AsPath>(&self, _file: F, _path: P)
+ -> io::Result<()>
+ {
+ Err(io::Error::new(io::ErrorKind::Other,
+ "linking unnamed fd to directories is only supported on linux"))
+ }
+
+ /// Create file if not exists, fail if exists
+ ///
+ /// This function checks existence and creates file atomically with
+ /// respect to other threads and processes.
+ ///
+ /// Technically it means passing `O_EXCL` flag to open.
+ pub fn new_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
+ -> io::Result<File>
+ {
+ self._open_file(to_cstr(path)?.as_ref(),
+ libc::O_CREAT|libc::O_EXCL|libc::O_WRONLY,
+ mode)
+ }
+
+ /// Open file for reading and writing without truncation, create if needed
+ ///
+ /// If there exists a symlink at the destination path, this method will fail. In that case, you
+ /// will need to call [`read_link`] to resolve the real path first.
+ ///
+ /// [`read_link`]: #method.read_link
+ pub fn update_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
+ -> io::Result<File>
+ {
+ self._open_file(to_cstr(path)?.as_ref(),
+ libc::O_CREAT|libc::O_RDWR,
+ mode)
+ }
+
+ fn _open_file(&self, path: &CStr, flags: libc::c_int, mode: libc::mode_t)
+ -> io::Result<File>
+ {
+ unsafe {
+ // Note: In below call to `openat`, *mode* must be cast to
+ // `unsigned` because the optional `mode` argument to `openat` is
+ // variadic in the signature. Since integers are not implicitly
+ // promoted as they are in C this would break on Freebsd where
+ // *mode_t* is an alias for `uint16_t`.
+ let res = libc::openat(self.0, path.as_ptr(),
+ flags|libc::O_CLOEXEC|libc::O_NOFOLLOW,
+ mode as libc::c_uint);
+ if res < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(File::from_raw_fd(res))
+ }
+ }
+ }
+
+ /// Make a symlink in this directory
+ ///
+ /// Note: the order of arguments differ from `symlinkat`
+ pub fn symlink<P: AsPath, R: AsPath>(&self, path: P, value: R)
+ -> io::Result<()>
+ {
+ self._symlink(to_cstr(path)?.as_ref(), to_cstr(value)?.as_ref())
+ }
+ fn _symlink(&self, path: &CStr, link: &CStr) -> io::Result<()> {
+ unsafe {
+ let res = libc::symlinkat(link.as_ptr(),
+ self.0, path.as_ptr());
+ if res < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+ }
+ }
+
+ /// Create a subdirectory in this directory
+ pub fn create_dir<P: AsPath>(&self, path: P, mode: libc::mode_t)
+ -> io::Result<()>
+ {
+ self._create_dir(to_cstr(path)?.as_ref(), mode)
+ }
+ fn _create_dir(&self, path: &CStr, mode: libc::mode_t) -> io::Result<()> {
+ unsafe {
+ let res = libc::mkdirat(self.0, path.as_ptr(), mode);
+ if res < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+ }
+ }
+
+ /// Rename a file in this directory to another name (keeping same dir)
+ pub fn local_rename<P: AsPath, R: AsPath>(&self, old: P, new: R)
+ -> io::Result<()>
+ {
+ rename(self, to_cstr(old)?.as_ref(), self, to_cstr(new)?.as_ref())
+ }
+
+ /// Similar to `local_rename` but atomically swaps both paths
+ ///
+ /// Only supported on Linux.
+ #[cfg(target_os="linux")]
+ pub fn local_exchange<P: AsPath, R: AsPath>(&self, old: P, new: R)
+ -> io::Result<()>
+ {
+ // Workaround https://github.com/tailhook/openat/issues/35
+ // AKA https://github.com/rust-lang/libc/pull/2116
+ // Unfortunately since we made this libc::c_int in our
+ // public API, we can't easily change it right now.
+ let flags = libc::RENAME_EXCHANGE as libc::c_int;
+ rename_flags(self, to_cstr(old)?.as_ref(),
+ self, to_cstr(new)?.as_ref(),
+ flags)
+ }
+
+ /// Remove a subdirectory in this directory
+ ///
+ /// Note only empty directory may be removed
+ pub fn remove_dir<P: AsPath>(&self, path: P)
+ -> io::Result<()>
+ {
+ self._unlink(to_cstr(path)?.as_ref(), libc::AT_REMOVEDIR)
+ }
+ /// Remove a file in this directory
+ pub fn remove_file<P: AsPath>(&self, path: P)
+ -> io::Result<()>
+ {
+ self._unlink(to_cstr(path)?.as_ref(), 0)
+ }
+ fn _unlink(&self, path: &CStr, flags: libc::c_int) -> io::Result<()> {
+ unsafe {
+ let res = libc::unlinkat(self.0, path.as_ptr(), flags);
+ if res < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+ }
+ }
+
+ /// Get the path of this directory (if possible)
+ ///
+ /// This uses symlinks in `/proc/self`, they sometimes may not be
+ /// available so use with care.
+ pub fn recover_path(&self) -> io::Result<PathBuf> {
+ let fd = self.0;
+ if fd != libc::AT_FDCWD {
+ read_link(format!("/proc/self/fd/{}", fd))
+ } else {
+ read_link("/proc/self/cwd")
+ }
+ }
+
+ /// Returns metadata of an entry in this directory
+ ///
+ /// If the destination path is a symlink, this will return the metadata of the symlink itself.
+ /// If you would like to follow the symlink and return the metadata of the target, you will
+ /// have to call [`read_link`] to resolve the real path first.
+ ///
+ /// [`read_link`]: #method.read_link
+ pub fn metadata<P: AsPath>(&self, path: P) -> io::Result<Metadata> {
+ self._stat(to_cstr(path)?.as_ref(), libc::AT_SYMLINK_NOFOLLOW)
+ }
+ fn _stat(&self, path: &CStr, flags: libc::c_int) -> io::Result<Metadata> {
+ unsafe {
+ let mut stat = mem::zeroed();
+ let res = libc::fstatat(self.0, path.as_ptr(),
+ &mut stat, flags);
+ if res < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(metadata::new(stat))
+ }
+ }
+ }
+
+ /// Returns the metadata of the directory itself.
+ pub fn self_metadata(&self) -> io::Result<Metadata> {
+ unsafe {
+ let mut stat = mem::zeroed();
+ let res = libc::fstat(self.0, &mut stat);
+ if res < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(metadata::new(stat))
+ }
+ }
+ }
+
+ /// Constructs a new `Dir` from a given raw file descriptor,
+ /// ensuring it is a directory file descriptor first.
+ ///
+ /// This function **consumes ownership** of the specified file
+ /// descriptor. The returned `Dir` will take responsibility for
+ /// closing it when it goes out of scope.
+ pub unsafe fn from_raw_fd_checked(fd: RawFd) -> io::Result<Self> {
+ let mut stat = mem::zeroed();
+ let res = libc::fstat(fd, &mut stat);
+ if res < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ match stat.st_mode & libc::S_IFMT {
+ libc::S_IFDIR => Ok(Dir(fd)),
+ _ => Err(io::Error::from_raw_os_error(libc::ENOTDIR))
+ }
+ }
+ }
+
+ /// Creates a new independently owned handle to the underlying directory.
+ pub fn try_clone(&self) -> io::Result<Self> {
+ let fd = unsafe { libc::dup(self.0) };
+ if fd == -1 {
+ Err(io::Error::last_os_error())
+ } else {
+ unsafe { Self::from_raw_fd_checked(fd) }
+ }
+ }
+}
+
+/// Rename (move) a file between directories
+///
+/// Files must be on a single filesystem anyway. This funtion does **not**
+/// fallback to copying if needed.
+pub fn rename<P, R>(old_dir: &Dir, old: P, new_dir: &Dir, new: R)
+ -> io::Result<()>
+ where P: AsPath, R: AsPath,
+{
+ _rename(old_dir, to_cstr(old)?.as_ref(), new_dir, to_cstr(new)?.as_ref())
+}
+
+fn _rename(old_dir: &Dir, old: &CStr, new_dir: &Dir, new: &CStr)
+ -> io::Result<()>
+{
+ unsafe {
+ let res = libc::renameat(old_dir.0, old.as_ptr(),
+ new_dir.0, new.as_ptr());
+ if res < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+ }
+}
+
+/// Create a hardlink to a file
+///
+/// Files must be on a single filesystem even if they are in different
+/// directories.
+///
+/// Note: by default ``linkat`` syscall doesn't resolve symbolic links, and
+/// it's also behavior of this function. It's recommended to resolve symlinks
+/// manually if needed.
+pub fn hardlink<P, R>(old_dir: &Dir, old: P, new_dir: &Dir, new: R)
+ -> io::Result<()>
+ where P: AsPath, R: AsPath,
+{
+ _hardlink(old_dir, to_cstr(old)?.as_ref(),
+ new_dir, to_cstr(new)?.as_ref(),
+ 0)
+}
+
+fn _hardlink(old_dir: &Dir, old: &CStr, new_dir: &Dir, new: &CStr,
+ flags: libc::c_int)
+ -> io::Result<()>
+{
+ unsafe {
+ let res = libc::linkat(old_dir.0, old.as_ptr(),
+ new_dir.0, new.as_ptr(), flags);
+ if res < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+ }
+}
+
+/// Rename (move) a file between directories with flags
+///
+/// Files must be on a single filesystem anyway. This funtion does **not**
+/// fallback to copying if needed.
+///
+/// Only supported on Linux.
+#[cfg(target_os="linux")]
+pub fn rename_flags<P, R>(old_dir: &Dir, old: P, new_dir: &Dir, new: R,
+ flags: libc::c_int)
+ -> io::Result<()>
+ where P: AsPath, R: AsPath,
+{
+ _rename_flags(old_dir, to_cstr(old)?.as_ref(),
+ new_dir, to_cstr(new)?.as_ref(),
+ flags)
+}
+
+#[cfg(target_os="linux")]
+fn _rename_flags(old_dir: &Dir, old: &CStr, new_dir: &Dir, new: &CStr,
+ flags: libc::c_int)
+ -> io::Result<()>
+{
+ unsafe {
+ let res = libc::syscall(
+ libc::SYS_renameat2,
+ old_dir.0, old.as_ptr(),
+ new_dir.0, new.as_ptr(), flags);
+ if res < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+ }
+}
+
+impl AsRawFd for Dir {
+ #[inline]
+ fn as_raw_fd(&self) -> RawFd {
+ self.0
+ }
+}
+
+impl FromRawFd for Dir {
+ /// The user must guarantee that the passed in `RawFd` is in fact
+ /// a directory file descriptor.
+ #[inline]
+ unsafe fn from_raw_fd(fd: RawFd) -> Dir {
+ Dir(fd)
+ }
+}
+
+impl IntoRawFd for Dir {
+ #[inline]
+ fn into_raw_fd(self) -> RawFd {
+ let result = self.0;
+ mem::forget(self);
+ return result;
+ }
+}
+
+impl Drop for Dir {
+ fn drop(&mut self) {
+ let fd = self.0;
+ if fd != libc::AT_FDCWD {
+ unsafe {
+ libc::close(fd);
+ }
+ }
+ }
+}
+
+fn to_cstr<P: AsPath>(path: P) -> io::Result<P::Buffer> {
+ path.to_path()
+ .ok_or_else(|| {
+ io::Error::new(io::ErrorKind::InvalidInput,
+ "nul byte in file name")
+ })
+}
+
+#[cfg(test)]
+mod test {
+ use std::io::{Read};
+ use std::path::Path;
+ use std::os::unix::io::{FromRawFd, IntoRawFd};
+ use crate::{Dir};
+
+ #[test]
+ fn test_open_ok() {
+ assert!(Dir::open("src").is_ok());
+ }
+
+ #[test]
+ #[cfg_attr(target_os="freebsd", should_panic(expected="Not a directory"))]
+ fn test_open_file() {
+ Dir::open("src/lib.rs").unwrap();
+ }
+
+ #[test]
+ fn test_read_file() {
+ let dir = Dir::open("src").unwrap();
+ let mut buf = String::new();
+ dir.open_file("lib.rs").unwrap()
+ .read_to_string(&mut buf).unwrap();
+ assert!(buf.find("extern crate libc;").is_some());
+ }
+
+ #[test]
+ fn test_from_into() {
+ let dir = Dir::open("src").unwrap();
+ let dir = unsafe { Dir::from_raw_fd(dir.into_raw_fd()) };
+ let mut buf = String::new();
+ dir.open_file("lib.rs").unwrap()
+ .read_to_string(&mut buf).unwrap();
+ assert!(buf.find("extern crate libc;").is_some());
+ }
+
+ #[test]
+ #[should_panic(expected="No such file or directory")]
+ fn test_open_no_dir() {
+ Dir::open("src/some-non-existent-file").unwrap();
+ }
+
+ #[test]
+ fn test_list() {
+ let dir = Dir::open("src").unwrap();
+ let me = dir.list_dir(".").unwrap();
+ assert!(me.collect::<Result<Vec<_>, _>>().unwrap()
+ .iter().find(|x| {
+ x.file_name() == Path::new("lib.rs").as_os_str()
+ })
+ .is_some());
+ }
+
+ #[test]
+ fn test_from_raw_fd_checked() {
+ let fd = Dir::open(".").unwrap().into_raw_fd();
+ let dir = unsafe { Dir::from_raw_fd_checked(fd) }.unwrap();
+ let filefd = dir.open_file("src/lib.rs").unwrap().into_raw_fd();
+ match unsafe { Dir::from_raw_fd_checked(filefd) } {
+ Ok(_) => assert!(false, "from_raw_fd_checked succeeded on a non-directory fd!"),
+ Err(e) => assert_eq!(e.raw_os_error().unwrap(), libc::ENOTDIR)
+ }
+ }
+
+ #[test]
+ fn test_try_clone() {
+ let d = Dir::open(".").unwrap();
+ let d2 = d.try_clone().unwrap();
+ drop(d);
+ let _file = d2.open_file("src/lib.rs").unwrap();
+ }
+}