From 815357e37fc7b982573e17d3fd18fa398a83fb5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Th=C3=A4ter?= Date: Mon, 19 Apr 2021 22:00:56 +0200 Subject: [PATCH 1/6] Add/Refactor Dir::list() -> DirIter ctor dir.list() consumes the dir and creates a DirIter. The underlying 'dup()' operation now becomes explicit, one may need to call try_clone() first. the list_self() and list_dir() using this now but are merely convinience wrappers and may (or may not) deprecated in future. Rationale: this simplifies the code a bit (list::open_dir() removed) and is closer to the low level semantics. --- src/dir.rs | 15 ++++++++++----- src/list.rs | 14 +------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/dir.rs b/src/dir.rs index 3bed451..add0382 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -7,8 +7,8 @@ use std::os::unix::ffi::{OsStringExt}; use std::path::{PathBuf}; use libc; +use crate::list::{open_dirfd, DirIter}; use crate::metadata::{self, Metadata}; -use crate::list::{DirIter, open_dir, open_dirfd}; use crate::{Dir, AsPath}; @@ -53,15 +53,20 @@ impl Dir { /// List subdirectory of this dir /// /// You can list directory itself with `list_self`. + // TODO(cehteh): may be deprecated in favor of list() pub fn list_dir(&self, path: P) -> io::Result { - open_dir(self, to_cstr(path)?.as_ref()) + self.sub_dir(path)?.list() } /// List this dir + // TODO(cehteh): may be deprecated in favor of list() pub fn list_self(&self) -> io::Result { - unsafe { - open_dirfd(libc::dup(self.0)) - } + self.try_clone()?.list() + } + + /// Create a DirIter from a Dir + pub fn list(self) -> io::Result { + open_dirfd(self.0) } /// Open subdirectory diff --git a/src/list.rs b/src/list.rs index 5b4d3cd..3d4889c 100644 --- a/src/list.rs +++ b/src/list.rs @@ -5,8 +5,7 @@ use std::os::unix::ffi::OsStrExt; use libc; -use crate::{Dir, Entry, SimpleType}; - +use crate::{Entry, SimpleType}; // We have such weird constants because C types are ugly const DOT: [libc::c_char; 2] = [b'.' as libc::c_char, 0]; @@ -104,17 +103,6 @@ pub fn open_dirfd(fd: libc::c_int) -> io::Result { } } -pub fn open_dir(dir: &Dir, path: &CStr) -> io::Result { - let dir_fd = unsafe { - libc::openat(dir.0, path.as_ptr(), libc::O_DIRECTORY|libc::O_CLOEXEC) - }; - if dir_fd < 0 { - Err(io::Error::last_os_error()) - } else { - open_dirfd(dir_fd) - } -} - impl Iterator for DirIter { type Item = io::Result; fn next(&mut self) -> Option { From 8647f9383927f2f52a8a0d65c15ff148fe2b36ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Th=C3=A4ter?= Date: Tue, 20 Apr 2021 02:09:33 +0200 Subject: [PATCH 2/6] FIX: fixes previous commit, adds special O_PATH funcs * Forgot to forget the Dir handle, thus it would been dropped. * On linux O_PATH was passed when Dir descriptors where opened. This is generally a good thing but broke the refactored list(). This also shown that O_PATH has different enough semantics to be problematic vs. opening handles without (as on other OS'es). Changed this to Dir::open() and Dir::sub_dir() default to opening without O_PATH (but O_DIRECTORY see remarks). * added Dir::open_path() and Dir::sub_dir_path() with O_PATH (urgh, better idea for naming? open_protected() or something like that?) which offer the O_PATH feature on linux. --- src/dir.rs | 102 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 78 insertions(+), 24 deletions(-) diff --git a/src/dir.rs b/src/dir.rs index add0382..2490bf0 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -12,11 +12,12 @@ use crate::metadata::{self, Metadata}; 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")))] +// NOTE(cehteh): removed O_PATH since it is linux only and highly unportable (semantics can't be emulated) +// but see open_path() below. +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +const BASE_OPEN_FLAGS: libc::c_int = libc::O_DIRECTORY | libc::O_CLOEXEC; +// NOTE(cehteh): O_DIRECTORY is defined in posix, what is the reason for not using it? +#[cfg(not(any(target_os = "linux", target_os = "freebsd")))] const BASE_OPEN_FLAGS: libc::c_int = libc::O_CLOEXEC; impl Dir { @@ -36,13 +37,18 @@ impl Dir { /// Open a directory descriptor at specified path // TODO(tailhook) maybe accept only absolute paths? pub fn open(path: P) -> io::Result { - Dir::_open(to_cstr(path)?.as_ref()) + Dir::_open(to_cstr(path)?.as_ref(), BASE_OPEN_FLAGS) } - fn _open(path: &CStr) -> io::Result { - let fd = unsafe { - libc::open(path.as_ptr(), BASE_OPEN_FLAGS) - }; + /// Open a directory descriptor at specified path with O_PATH (linux only) + /// A descriptor obtained with this flag is restricted to do only certain operations (see: man 2 open) + #[cfg(any(target_os = "linux"))] + pub fn open_path(path: P) -> io::Result { + Dir::_open(to_cstr(path)?.as_ref(), libc::O_PATH | BASE_OPEN_FLAGS) + } + + fn _open(path: &CStr, flags: libc::c_int) -> io::Result { + let fd = unsafe { libc::open(path.as_ptr(), flags) }; if fd < 0 { Err(io::Error::last_os_error()) } else { @@ -66,7 +72,9 @@ impl Dir { /// Create a DirIter from a Dir pub fn list(self) -> io::Result { - open_dirfd(self.0) + let fd = self.0; + std::mem::forget(self); + open_dirfd(fd) } /// Open subdirectory @@ -76,15 +84,23 @@ impl Dir { /// /// [`read_link`]: #method.read_link pub fn sub_dir(&self, path: P) -> io::Result { - self._sub_dir(to_cstr(path)?.as_ref()) + self._sub_dir(to_cstr(path)?.as_ref(), BASE_OPEN_FLAGS | libc::O_NOFOLLOW) } - fn _sub_dir(&self, path: &CStr) -> io::Result { - let fd = unsafe { - libc::openat(self.0, - path.as_ptr(), - BASE_OPEN_FLAGS|libc::O_NOFOLLOW) - }; + /// Open subdirectory with O_PATH (linux only) + /// + /// Note that this method does not resolve symlinks by default, so you may have to call + /// A descriptor obtained with this flag is restricted to do only certain operations (see: man 2 open) + /// [`read_link`] to resolve the real path first. + /// + /// [`read_link`]: #method.read_link + #[cfg(any(target_os = "linux"))] + pub fn sub_dir_path(&self, path: P) -> io::Result { + self._sub_dir(to_cstr(path)?.as_ref(), BASE_OPEN_FLAGS | libc::O_NOFOLLOW | libc::O_PATH) + } + + fn _sub_dir(&self, path: &CStr, flags: libc::c_int) -> io::Result { + let fd = unsafe { libc::openat(self.0, path.as_ptr(), flags) }; if fd < 0 { Err(io::Error::last_os_error()) } else { @@ -634,7 +650,8 @@ mod test { } #[test] - #[cfg_attr(target_os="freebsd", should_panic(expected="Not a directory"))] + #[cfg_attr(any(target_os = "freebsd", target_os = "linux"), should_panic(expected = "Not a directory"))] + // NOTE(cehteh): should fail in all cases! see O_DIRECTORY at the top fn test_open_file() { Dir::open("src/lib.rs").unwrap(); } @@ -666,13 +683,50 @@ mod test { #[test] fn test_list() { + let dir = Dir::open("src").unwrap(); + let me = dir.list().unwrap(); + assert!(me + .collect::, _>>() + .unwrap() + .iter() + .find(|x| { x.file_name() == Path::new("lib.rs").as_os_str() }) + .is_some()); + } + + #[test] + fn test_list_self() { + let dir = Dir::open("src").unwrap(); + let me = dir.list_self().unwrap(); + assert!(me + .collect::, _>>() + .unwrap() + .iter() + .find(|x| { x.file_name() == Path::new("lib.rs").as_os_str() }) + .is_some()); + } + + #[test] + fn test_list_dot() { let dir = Dir::open("src").unwrap(); let me = dir.list_dir(".").unwrap(); - assert!(me.collect::, _>>().unwrap() - .iter().find(|x| { - x.file_name() == Path::new("lib.rs").as_os_str() - }) - .is_some()); + assert!(me + .collect::, _>>() + .unwrap() + .iter() + .find(|x| { x.file_name() == Path::new("lib.rs").as_os_str() }) + .is_some()); + } + + #[test] + fn test_list_dir() { + let dir = Dir::open(".").unwrap(); + let me = dir.list_dir("src").unwrap(); + assert!(me + .collect::, _>>() + .unwrap() + .iter() + .find(|x| { x.file_name() == Path::new("lib.rs").as_os_str() }) + .is_some()); } #[test] From d567fb1aafee57a5880a7a8f9dd7a89c186278fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Th=C3=A4ter?= Date: Wed, 21 Apr 2021 14:29:45 +0200 Subject: [PATCH 3/6] rename *_path to *_lite, improve docs, provide impl for non linux 'Lite' file descriptors are possibly a better naming of what O_PATH does. This introduces then and implements them portable. On systems which do not support O_PATH normal file descriptors are used. --- src/dir.rs | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/dir.rs b/src/dir.rs index 2490bf0..894e4f1 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -40,13 +40,22 @@ impl Dir { Dir::_open(to_cstr(path)?.as_ref(), BASE_OPEN_FLAGS) } - /// Open a directory descriptor at specified path with O_PATH (linux only) - /// A descriptor obtained with this flag is restricted to do only certain operations (see: man 2 open) + /// Open a 'lite' directory descriptor at specified path + /// A descriptor obtained with this flag is restricted to do only certain operations: + /// - It may be used as anchor for opening sub-objects + /// - One can query metadata of this directory + /// Using this descriptor for iterating over the content is unspecified. + /// Uses O_PATH on Linux #[cfg(any(target_os = "linux"))] - pub fn open_path(path: P) -> io::Result { + pub fn open_lite(path: P) -> io::Result { Dir::_open(to_cstr(path)?.as_ref(), libc::O_PATH | BASE_OPEN_FLAGS) } + #[cfg(not(target_os = "linux"))] + pub fn open_lite(path: P) -> io::Result { + Dir::_open(to_cstr(path)?.as_ref(), BASE_OPEN_FLAGS) + } + fn _open(path: &CStr, flags: libc::c_int) -> io::Result { let fd = unsafe { libc::open(path.as_ptr(), flags) }; if fd < 0 { @@ -87,18 +96,28 @@ impl Dir { self._sub_dir(to_cstr(path)?.as_ref(), BASE_OPEN_FLAGS | libc::O_NOFOLLOW) } - /// Open subdirectory with O_PATH (linux only) + /// Open subdirectory with a 'lite' descriptor at specified path + /// A descriptor obtained with this flag is restricted to do only certain operations: + /// - It may be used as anchor for opening sub-objects + /// - One can query metadata of this directory + /// Using this descriptor for iterating over the content is unspecified. + /// Uses O_PATH on Linux /// /// Note that this method does not resolve symlinks by default, so you may have to call - /// A descriptor obtained with this flag is restricted to do only certain operations (see: man 2 open) + /// /// [`read_link`] to resolve the real path first. /// /// [`read_link`]: #method.read_link #[cfg(any(target_os = "linux"))] - pub fn sub_dir_path(&self, path: P) -> io::Result { + pub fn sub_dir_lite(&self, path: P) -> io::Result { self._sub_dir(to_cstr(path)?.as_ref(), BASE_OPEN_FLAGS | libc::O_NOFOLLOW | libc::O_PATH) } + #[cfg(not(target_os = "linux"))] + pub fn sub_dir_lite(&self, path: P) -> io::Result { + self._sub_dir(to_cstr(path)?.as_ref(), BASE_OPEN_FLAGS | libc::O_NOFOLLOW) + } + fn _sub_dir(&self, path: &CStr, flags: libc::c_int) -> io::Result { let fd = unsafe { libc::openat(self.0, path.as_ptr(), flags) }; if fd < 0 { From e5e9c5b7747d14f52184f19bd5fd8301f0fbbbb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Th=C3=A4ter?= Date: Wed, 21 Apr 2021 15:06:58 +0200 Subject: [PATCH 4/6] New: clone_dirfd() may fix #34, ADD: FdType machinery, libc_ok() * With FdType/fd_type() one can determine the kind of an underlying file descriptor. Lite descriptors are implemented only in Linux (for now). When O_DIRECTORY is supported it uses fcntl() in favo over stat() * clone_dirfd() tries to do 'the right thing' for duplicating FD's * libc_ok() is a simple wraper for libc calls that return -1 on error, I will refactor the code in the next commits to make use of that more. Please test this! So far it works for me on Linux. --- src/dir.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 13 deletions(-) diff --git a/src/dir.rs b/src/dir.rs index 894e4f1..54270df 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -497,29 +497,91 @@ impl Dir { /// 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 { - 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)) - } + match fd_type(fd)? { + FdType::NormalDir | FdType::LiteDir => 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 { - let fd = unsafe { libc::dup(self.0) }; - if fd == -1 { - Err(io::Error::last_os_error()) + Ok(Dir(clone_dirfd(self.0)?)) + } + + //TODO(cehteh): clone/conversion full<->lite +} + +const CURRENT_DIRECTORY: [libc::c_char; 2] = [b'.' as libc::c_char, 0]; + +fn clone_dirfd(fd: libc::c_int) -> io::Result { + unsafe { + match fd_type(fd)? { + FdType::NormalDir => libc_ok(libc::openat( + fd, + &CURRENT_DIRECTORY as *const libc::c_char, + BASE_OPEN_FLAGS, + )), + #[cfg(target_os = "linux")] + FdType::LiteDir => libc_ok(libc::dup(fd)), + _ => Err(io::Error::from_raw_os_error(libc::ENOTDIR)), + } + } +} + +enum FdType { + NormalDir, + LiteDir, + Other, +} + +// OSes with O_DIRECTORY can use fcntl() +// Linux hash O_PATH +#[cfg(target_os = "linux")] +fn fd_type(fd: libc::c_int) -> io::Result { + let flags = unsafe { libc_ok(libc::fcntl(fd, libc::F_GETFL))? }; + if flags & libc::O_DIRECTORY != 0 { + if flags & libc::O_PATH != 0 { + Ok(FdType::LiteDir) } else { - unsafe { Self::from_raw_fd_checked(fd) } + Ok(FdType::NormalDir) + } + } else { + Ok(FdType::Other) + } +} + +#[cfg(target_os = "freebsd")] +fn fd_type(fd: libc::c_int) -> io::Result { + let flags = unsafe { libc_ok(libc::fcntl(fd, libc::F_GETFL))? }; + if flags & libc::O_DIRECTORY != 0 { + Ok(FdType::NormalDir) + } else { + Ok(FdType::Other) + } +} + +// OSes without O_DIRECTORY use stat() +#[cfg(not(any(target_os = "linux", target_os = "freebsd")))] +fn fd_type(fd: libc::c_int) -> io::Result { + unsafe { + let mut stat = mem::zeroed(); // TODO(cehteh): uninit + libc_ok(libc::fstat(fd, &mut stat))?; + match stat.st_mode & libc::S_IFMT { + libc::S_IFDIR => Ok(FdType::NormalDir), + _ => Ok(FdType::Other), } } } +#[inline] +fn libc_ok(ret: libc::c_int) -> io::Result { + if ret != -1 { + Ok(ret) + } else { + Err(io::Error::last_os_error()) + } +} + /// Rename (move) a file between directories /// /// Files must be on a single filesystem anyway. This funtion does **not** From 9d48913c3aa4bf49d8764d3e81f9b67cffb9e0e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Th=C3=A4ter?= Date: Wed, 21 Apr 2021 16:00:14 +0200 Subject: [PATCH 5/6] cosmetics: use libc_ok() where applicable This simplifies the code a bit, in 'release' the same output is generated. debug builds may not inline the libc_ok() and be slightly larger. --- src/dir.rs | 100 +++++++++++++++++------------------------------------ 1 file changed, 32 insertions(+), 68 deletions(-) diff --git a/src/dir.rs b/src/dir.rs index 54270df..c5dd427 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -57,12 +57,8 @@ impl Dir { } fn _open(path: &CStr, flags: libc::c_int) -> io::Result { - let fd = unsafe { libc::open(path.as_ptr(), flags) }; - if fd < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(Dir(fd)) - } + let fd = unsafe { libc_ok(libc::open(path.as_ptr(), flags))? }; + Ok(Dir(fd)) } /// List subdirectory of this dir @@ -119,12 +115,7 @@ impl Dir { } fn _sub_dir(&self, path: &CStr, flags: libc::c_int) -> io::Result { - let fd = unsafe { libc::openat(self.0, path.as_ptr(), flags) }; - if fd < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(Dir(fd)) - } + Ok(Dir(unsafe { libc_ok(libc::openat(self.0, path.as_ptr(), flags))? })) } /// Read link in this directory @@ -344,14 +335,12 @@ impl Dir { // 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)) - } + let res = libc_ok( + libc::openat(self.0, path.as_ptr(), + flags|libc::O_CLOEXEC|libc::O_NOFOLLOW, + mode as libc::c_uint) + )?; + Ok(File::from_raw_fd(res)) } } @@ -383,13 +372,9 @@ impl Dir { } 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(()) - } + libc_ok(libc::mkdirat(self.0, path.as_ptr(), mode))?; } + Ok(()) } /// Rename a file in this directory to another name (keeping same dir) @@ -432,19 +417,16 @@ impl Dir { } 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(()) - } + libc_ok(libc::unlinkat(self.0, path.as_ptr(), flags))?; } + 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. + // FIXME(cehteh): proc stuff isn't portable pub fn recover_path(&self) -> io::Result { let fd = self.0; if fd != libc::AT_FDCWD { @@ -466,27 +448,18 @@ impl Dir { } fn _stat(&self, path: &CStr, flags: libc::c_int) -> io::Result { 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)) - } + let mut stat = mem::zeroed(); // TODO(cehteh): uninit + libc_ok(libc::fstatat(self.0, path.as_ptr(), &mut stat, flags))?; + Ok(metadata::new(stat)) } } /// Returns the metadata of the directory itself. pub fn self_metadata(&self) -> io::Result { 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)) - } + let mut stat = mem::zeroed(); // TODO(cehteh): uninit + libc_ok(libc::fstat(self.0, &mut stat))?; + Ok(metadata::new(stat)) } } @@ -593,18 +566,11 @@ pub fn rename(old_dir: &Dir, old: P, new_dir: &Dir, new: R) _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<()> -{ +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(()) - } + libc_ok(libc::renameat(old_dir.0, old.as_ptr(), new_dir.0, new.as_ptr()))?; } + Ok(()) } /// Create a hardlink to a file @@ -624,19 +590,17 @@ pub fn hardlink(old_dir: &Dir, old: P, new_dir: &Dir, new: R) 0) } -fn _hardlink(old_dir: &Dir, old: &CStr, new_dir: &Dir, new: &CStr, - flags: libc::c_int) - -> io::Result<()> -{ +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(()) - } + libc_ok(libc::linkat(old_dir.0, old.as_ptr(), new_dir.0, new.as_ptr(), flags))?; } + Ok(()) } /// Rename (move) a file between directories with flags From fcf92fa7ec0529d6499ef37c1f4bd7c144098b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Th=C3=A4ter?= Date: Wed, 21 Apr 2021 16:31:18 +0200 Subject: [PATCH 6/6] add clone_upgrade()/clone_downgrade(), fix list_self() This up/downgrade cloning converts into normal/lite handles which was missing before. I hope this fixes #34 finally. --- src/dir.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/src/dir.rs b/src/dir.rs index c5dd427..66bf82d 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -17,6 +17,7 @@ use crate::{Dir, AsPath}; #[cfg(any(target_os = "linux", target_os = "freebsd"))] const BASE_OPEN_FLAGS: libc::c_int = libc::O_DIRECTORY | libc::O_CLOEXEC; // NOTE(cehteh): O_DIRECTORY is defined in posix, what is the reason for not using it? +// TODO(cehteh): on systems that do not support O_DIRECTORY a runtime stat-check is required #[cfg(not(any(target_os = "linux", target_os = "freebsd")))] const BASE_OPEN_FLAGS: libc::c_int = libc::O_CLOEXEC; @@ -72,10 +73,11 @@ impl Dir { /// List this dir // TODO(cehteh): may be deprecated in favor of list() pub fn list_self(&self) -> io::Result { - self.try_clone()?.list() + self.clone_upgrade()?.list() } /// Create a DirIter from a Dir + /// Dir must not be a 'Lite' handle pub fn list(self) -> io::Result { let fd = self.0; std::mem::forget(self); @@ -477,11 +479,21 @@ impl Dir { } /// Creates a new independently owned handle to the underlying directory. + /// The new handle has the same (Normal/Lite) semantics as the original handle. pub fn try_clone(&self) -> io::Result { Ok(Dir(clone_dirfd(self.0)?)) } - //TODO(cehteh): clone/conversion full<->lite + /// Creates a new 'Normal' independently owned handle to the underlying directory. + pub fn clone_upgrade(&self) -> io::Result { + Ok(Dir(clone_dirfd_upgrade(self.0)?)) + } + + /// Creates a new 'Lite' independently owned handle to the underlying directory. + pub fn clone_downgrade(&self) -> io::Result { + Ok(Dir(clone_dirfd_downgrade(self.0)?)) + } + } const CURRENT_DIRECTORY: [libc::c_char; 2] = [b'.' as libc::c_char, 0]; @@ -501,6 +513,41 @@ fn clone_dirfd(fd: libc::c_int) -> io::Result { } } +fn clone_dirfd_upgrade(fd: libc::c_int) -> io::Result { + unsafe { + match fd_type(fd)? { + FdType::NormalDir | FdType::LiteDir => libc_ok(libc::openat( + fd, + &CURRENT_DIRECTORY as *const libc::c_char, + BASE_OPEN_FLAGS, + )), + _ => Err(io::Error::from_raw_os_error(libc::ENOTDIR)), + } + } +} + +fn clone_dirfd_downgrade(fd: libc::c_int) -> io::Result { + unsafe { + match fd_type(fd)? { + #[cfg(target_os = "linux")] + FdType::NormalDir => libc_ok(libc::openat( + fd, + &CURRENT_DIRECTORY as *const libc::c_char, + libc::O_PATH | BASE_OPEN_FLAGS, + )), + #[cfg(not(target_os = "linux"))] + FdType::NormalDir => libc_ok(libc::openat( + fd, + &CURRENT_DIRECTORY as *const libc::c_char, + BASE_OPEN_FLAGS, + )), + #[cfg(target_os = "linux")] + FdType::LiteDir => libc_ok(libc::dup(fd)), + _ => Err(io::Error::from_raw_os_error(libc::ENOTDIR)), + } + } +} + enum FdType { NormalDir, LiteDir,