diff --git a/Cargo.toml b/Cargo.toml index a5b5b3f..4d232f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,28 @@ version = "0.1.21" authors = ["paul@colomiets.name"] edition = "2018" +[features] +default = [] +linux = ["o_path", "o_directory", "o_tmpfile", "statx", "proc_self_fd", "link_file_at", "rename_exchange", "renameat_flags"] +#NOTE(cehteh): eventually provide some baseline configs for other OS'es (for cross compilation) +o_path = [] +o_directory = [] +o_tmpfile = [] +o_search = [] +fcntl_o_directory = [] +proc_self_fd = [] +link_file_at = [] +renameat_flags = [] +rename_exchange = [] +statx = [] + [dependencies] libc = "0.2.34" +[build-dependencies] +libc = "0.2.34" +conf_test = "0.1" + [dev-dependencies] argparse = "0.2.1" tempfile = "3.0.3" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..851c014 --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +use conf_test::ConfTest; + +fn main() { + ConfTest::run(); +} diff --git a/conf_tests/fcntl_o_directory.rs b/conf_tests/fcntl_o_directory.rs new file mode 100644 index 0000000..5a135f0 --- /dev/null +++ b/conf_tests/fcntl_o_directory.rs @@ -0,0 +1,16 @@ +extern crate libc; + +fn main() { + unsafe { + let conf_tests = std::ffi::CString::new("conf_tests").unwrap(); + let fd = libc::open(conf_tests.as_ptr(), libc::O_DIRECTORY | libc::O_RDONLY); + + let flags = libc::fcntl(fd, libc::F_GETFL); + + if flags != -1 && flags & libc::O_DIRECTORY != 0 { + std::process::exit(0); + } else { + std::process::exit(1); + } + } +} diff --git a/conf_tests/link_file_at.rs b/conf_tests/link_file_at.rs new file mode 100644 index 0000000..0dd292d --- /dev/null +++ b/conf_tests/link_file_at.rs @@ -0,0 +1,13 @@ +extern crate libc; + +fn main() { + //NOTE(cehteh): same as the proc_self_fd test, maybe we need something smarter in future + unsafe { + let conf_tests = std::ffi::CString::new("/proc/self/fd/0").unwrap(); + if libc::open(conf_tests.as_ptr(), libc::O_RDONLY) != -1 { + std::process::exit(0); + } else { + std::process::exit(1); + } + } +} diff --git a/conf_tests/o_directory.rs b/conf_tests/o_directory.rs new file mode 100644 index 0000000..07c0f61 --- /dev/null +++ b/conf_tests/o_directory.rs @@ -0,0 +1,8 @@ +extern crate libc; + +fn main() { + unsafe { + let conf_tests = std::ffi::CString::new("conf_tests").unwrap(); + libc::open(conf_tests.as_ptr(), libc::O_DIRECTORY); + } +} diff --git a/conf_tests/o_path.rs b/conf_tests/o_path.rs new file mode 100644 index 0000000..e454460 --- /dev/null +++ b/conf_tests/o_path.rs @@ -0,0 +1,8 @@ +extern crate libc; + +fn main () { + unsafe { + let conf_tests = std::ffi::CString::new("conf_tests").unwrap(); + libc::open(conf_tests.as_ptr(), libc::O_PATH); + } +} diff --git a/conf_tests/o_search.rs b/conf_tests/o_search.rs new file mode 100644 index 0000000..60a3250 --- /dev/null +++ b/conf_tests/o_search.rs @@ -0,0 +1,8 @@ +extern crate libc; + +fn main() { + unsafe { + let conf_tests = std::ffi::CString::new("conf_tests").unwrap(); + libc::open(conf_tests.as_ptr(), libc::O_SEARCH); + } +} diff --git a/conf_tests/o_tmpfile.rs b/conf_tests/o_tmpfile.rs new file mode 100644 index 0000000..8c0ad16 --- /dev/null +++ b/conf_tests/o_tmpfile.rs @@ -0,0 +1,10 @@ +extern crate libc; + +fn main () { + unsafe { + let conf_tests = std::ffi::CString::new("conf_tests").unwrap(); + libc::open(conf_tests.as_ptr(), + libc::O_TMPFILE | libc::O_RDWR, + libc::S_IRUSR | libc::S_IWUSR); + } +} diff --git a/conf_tests/proc_self_fd.rs b/conf_tests/proc_self_fd.rs new file mode 100644 index 0000000..7983b5c --- /dev/null +++ b/conf_tests/proc_self_fd.rs @@ -0,0 +1,12 @@ +extern crate libc; + +fn main() { + unsafe { + let conf_tests = std::ffi::CString::new("/proc/self/fd/0").unwrap(); + if libc::open(conf_tests.as_ptr(), libc::O_RDONLY) != -1 { + std::process::exit(0); + } else { + std::process::exit(1); + } + } +} diff --git a/conf_tests/rename_exchange.rs b/conf_tests/rename_exchange.rs new file mode 100644 index 0000000..76f69f9 --- /dev/null +++ b/conf_tests/rename_exchange.rs @@ -0,0 +1,5 @@ +extern crate libc; + +fn main() { + let does_rename_exchange_exist = libc::RENAME_EXCHANGE; +} diff --git a/conf_tests/renameat_flags.rs b/conf_tests/renameat_flags.rs new file mode 100644 index 0000000..392ba1c --- /dev/null +++ b/conf_tests/renameat_flags.rs @@ -0,0 +1,5 @@ +extern crate libc; + +fn main() { + let does_renameat2_exist = libc::SYS_renameat2; +} diff --git a/examples/exchange.rs b/examples/exchange.rs index 21fdf0a..fdd651d 100644 --- a/examples/exchange.rs +++ b/examples/exchange.rs @@ -32,8 +32,10 @@ fn main() { } let parent = path1.parent().expect("path must have parent directory"); let dir = Dir::open(parent).expect("can open directory"); + #[cfg(feature = "rename_exchange")] dir.local_exchange( path1.file_name().expect("path1 must have filename"), path2.file_name().expect("path2 must have filename"), - ).expect("can rename"); + ) + .expect("can rename"); } diff --git a/src/dir.rs b/src/dir.rs index 3bed451..3c84399 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -7,17 +7,31 @@ 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}; -#[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; +// NOTE(cehteh): removed O_PATH since it is linux only and highly unportable (semantics can't be emulated) +// but see open_lite() below. + + +#[cfg(feature = "o_directory")] +const O_DIRECTORY_FLAG: libc::c_int = libc::O_DIRECTORY; +#[cfg(not(feature = "o_directory"))] +const O_DIRECTORY_FLAG: libc::c_int = 0; + +#[cfg(feature = "o_path")] +const O_PATH_FLAG: libc::c_int = libc::O_PATH; +#[cfg(not(feature = "o_path"))] +const O_PATH_FLAG: libc::c_int = 0; + +#[cfg(feature = "o_search")] +const O_SEARCH_FLAG: libc::c_int = libc::O_SEARCH; +#[cfg(not(feature = "o_search"))] +const O_SEARCH_FLAG: libc::c_int = 0; + +const BASE_OPEN_FLAGS: libc::c_int = O_DIRECTORY_FLAG | libc::O_CLOEXEC; impl Dir { /// Creates a directory descriptor that resolves paths relative to current @@ -36,17 +50,38 @@ 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()) - } - - fn _open(path: &CStr) -> io::Result { - let fd = unsafe { - libc::open(path.as_ptr(), BASE_OPEN_FLAGS) - }; - if fd < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(Dir(fd)) + Dir::_open(to_cstr(path)?.as_ref(), BASE_OPEN_FLAGS) + } + + /// 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 + pub fn open_lite(path: P) -> io::Result { + Dir::_open(to_cstr(path)?.as_ref(), O_PATH_FLAG | BASE_OPEN_FLAGS) + } + + fn _open(path: &CStr, flags: libc::c_int) -> io::Result { + let fd = unsafe { libc_ok(libc::open(path.as_ptr(), flags))? }; + Ok(Dir(fd)) + } + + /// Checks if the fd associated with the Dir object is really a directory. + /// There are subtle differences in how directories can be opened and what properties the + /// resulting file handles have. On some platforms it is possible that + /// Dir::open("somefile") succeeds. This will usually raise errors later when one tries to + /// do Directory operations on this. While checking if such an handle comes with cost of a + /// potential expensive 'stat()' operation. This library makes the assumption that in the + /// 'usual' case Dir objects are only created on directories and operations on Dir handles + /// handle errors properly. Still in some cases one may check a freshly created handle + /// explicitly. Thats what 'is_dir()' is for. Returns 'true' when the underlying handles + /// represents a directory and false otherwise. + pub fn is_dir(&self) -> bool { + match fd_type(self.0).unwrap_or(FdType::Other){ + FdType::NormalDir | FdType::LiteDir => true, + FdType::Other => false, } } @@ -54,14 +89,22 @@ impl Dir { /// /// You can list directory itself with `list_self`. pub fn list_dir(&self, path: P) -> io::Result { - open_dir(self, to_cstr(path)?.as_ref()) + //TODO(cehteh): with(O_SEARCH_FLAG) + self.sub_dir(path)?.list() } /// List this dir pub fn list_self(&self) -> io::Result { - unsafe { - open_dirfd(libc::dup(self.0)) - } + //TODO(cehteh): with(O_SEARCH_FLAG) + 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); + open_dirfd(fd) } /// Open subdirectory @@ -71,20 +114,27 @@ 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) - }; - if fd < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(Dir(fd)) - } + /// 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 + /// + /// [`read_link`] to resolve the real path first. + /// + /// [`read_link`]: #method.read_link + pub fn sub_dir_lite(&self, path: P) -> io::Result { + self._sub_dir(to_cstr(path)?.as_ref(), BASE_OPEN_FLAGS | libc::O_NOFOLLOW | O_PATH_FLAG) + } + + fn _sub_dir(&self, path: &CStr, flags: libc::c_int) -> io::Result { + Ok(Dir(unsafe { libc_ok(libc::openat(self.0, path.as_ptr(), flags))? })) } /// Read link in this directory @@ -190,7 +240,7 @@ impl Dir { /// 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")] + #[cfg(feature = "o_tmpfile")] pub fn new_unnamed_file(&self, mode: libc::mode_t) -> io::Result { @@ -219,7 +269,7 @@ impl Dir { /// 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"))] + #[cfg(not(feature = "o_tmpfile"))] pub fn new_unnamed_file(&self, _mode: libc::mode_t) -> io::Result { @@ -238,7 +288,7 @@ impl Dir { /// 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")] + #[cfg(feature = "link_file_at")] pub fn link_file_at(&self, file: &F, path: P) -> io::Result<()> { @@ -259,7 +309,10 @@ impl Dir { /// 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"))] + //NOTE(cehteh): would it make sense to remove this function (for non linux), this will + // generate a compile time error rather than a runtime error, which most likely is + // favorable since the semantic cant easily emulated. + #[cfg(not(feature = "link_file_at"))] pub fn link_file_at(&self, _file: F, _path: P) -> io::Result<()> { @@ -304,14 +357,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)) } } @@ -343,13 +394,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) @@ -362,7 +409,7 @@ impl Dir { /// Similar to `local_rename` but atomically swaps both paths /// /// Only supported on Linux. - #[cfg(target_os="linux")] + #[cfg(feature = "rename_exchange")] pub fn local_exchange(&self, old: P, new: R) -> io::Result<()> { @@ -392,19 +439,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. + #[cfg(feature = "proc_self_fd")] pub fn recover_path(&self) -> io::Result { let fd = self.0; if fd != libc::AT_FDCWD { @@ -426,27 +470,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)) } } @@ -457,26 +492,134 @@ 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)), + FdType::Other => Err(io::Error::from_raw_os_error(libc::ENOTDIR)), } } /// 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 { - let fd = unsafe { libc::dup(self.0) }; - if fd == -1 { - Err(io::Error::last_os_error()) + Ok(Dir(clone_dirfd(self.0)?)) + } + + /// 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]; + +//TODO(cehteh): eventually the clone calls should replicate O_SEARCH, maybe other file flags +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(feature = "o_path")] + FdType::LiteDir => libc_ok(libc::dup(fd)), + _ => Err(io::Error::from_raw_os_error(libc::ENOTDIR)), + } + } +} + +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(feature = "o_path")] + FdType::NormalDir => libc_ok(libc::openat( + fd, + &CURRENT_DIRECTORY as *const libc::c_char, + libc::O_PATH | BASE_OPEN_FLAGS, + )), + #[cfg(not(feature = "o_path"))] + FdType::NormalDir => libc_ok(libc::openat( + fd, + &CURRENT_DIRECTORY as *const libc::c_char, + BASE_OPEN_FLAGS, + )), + #[cfg(feature = "o_path")] + FdType::LiteDir => libc_ok(libc::dup(fd)), + _ => Err(io::Error::from_raw_os_error(libc::ENOTDIR)), + } + } +} + +enum FdType { + NormalDir, + LiteDir, + Other, +} + +// OSes with fcntl() that returns O_DIRECTORY +// Linux hash O_PATH +#[cfg(all(feature = "o_path", feature = "fcntl_o_directory"))] +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(all(not(feature = "o_path"), feature = "fcntl_o_directory"))] +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 where fcntl won't return O_DIRECTORY use stat() +#[cfg(not(feature = "fcntl_o_directory"))] +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()) } } @@ -491,18 +634,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 @@ -522,19 +658,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 @@ -543,7 +677,7 @@ fn _hardlink(old_dir: &Dir, old: &CStr, new_dir: &Dir, new: &CStr, /// fallback to copying if needed. /// /// Only supported on Linux. -#[cfg(target_os="linux")] +#[cfg(feature = "renameat_flags")] pub fn rename_flags(old_dir: &Dir, old: P, new_dir: &Dir, new: R, flags: libc::c_int) -> io::Result<()> @@ -554,7 +688,7 @@ pub fn rename_flags(old_dir: &Dir, old: P, new_dir: &Dir, new: R, flags) } -#[cfg(target_os="linux")] +#[cfg(feature = "renameat_flags")] fn _rename_flags(old_dir: &Dir, old: &CStr, new_dir: &Dir, new: &CStr, flags: libc::c_int) -> io::Result<()> @@ -628,12 +762,6 @@ mod test { 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(); @@ -661,13 +789,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] diff --git a/src/filetype.rs b/src/filetype.rs index efaedbe..4ee98e4 100644 --- a/src/filetype.rs +++ b/src/filetype.rs @@ -7,6 +7,8 @@ use std::fs::Metadata; /// this simplified enum that works for many appalications. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SimpleType { + /// Entry is of unknown type + Unknown, /// Entry is a symlink Symlink, /// Entry is a directory diff --git a/src/lib.rs b/src/lib.rs index a1a2feb..4bd3e3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,6 +73,7 @@ pub struct Dir(RawFd); pub struct Entry { name: CString, file_type: Option, + ino: libc::ino_t, } #[cfg(test)] diff --git a/src/list.rs b/src/list.rs index 5b4d3cd..20c2561 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]; @@ -37,6 +36,10 @@ impl Entry { pub fn simple_type(&self) -> Option { self.file_type } + /// Returns the inode number of this entry + pub fn inode(&self) -> libc::ino_t { + self.ino + } } #[cfg(any(target_os="linux", target_os="fuchsia"))] @@ -104,17 +107,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 { @@ -136,6 +128,7 @@ impl Iterator for DirIter { libc::DT_LNK => Some(SimpleType::Symlink), _ => Some(SimpleType::Other), }, + ino: e.d_ino, })); } } diff --git a/src/metadata.rs b/src/metadata.rs index de7cc22..7b5efb5 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -2,6 +2,7 @@ use std::fs::Permissions; use std::os::unix::fs::PermissionsExt; use libc; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use crate::SimpleType; @@ -17,11 +18,11 @@ pub struct Metadata { impl Metadata { /// Returns simplified type of the directory entry pub fn simple_type(&self) -> SimpleType { - let typ = self.stat.st_mode & libc::S_IFMT; - match typ { + match self.file_type().unwrap_or(0) as libc::mode_t { libc::S_IFREG => SimpleType::File, libc::S_IFDIR => SimpleType::Dir, libc::S_IFLNK => SimpleType::Symlink, + 0 => SimpleType::Unknown, _ => SimpleType::Other, } } @@ -45,12 +46,112 @@ impl Metadata { pub fn len(&self) -> u64 { self.stat.st_size as u64 } + /// Return low level file type, if available + pub fn file_type(&self) -> Option { + Some(self.stat.st_mode & libc::S_IFMT) + } + /// Return device node, if available + pub fn ino(&self) -> Option { + Some(self.stat.st_ino) + } + /// Return device node major of the file, if available + pub fn dev_major(&self) -> Option { + Some(major(self.stat.st_dev)) + } + /// Return device node minor of the file, if available + pub fn dev_minor(&self) -> Option { + Some(minor(self.stat.st_dev)) + } + /// Return device node major of an device descriptor, if available + pub fn rdev_major(&self) -> Option { + match self.file_type()? as libc::mode_t { + libc::S_IFBLK | libc::S_IFCHR => Some(major(self.stat.st_rdev)), + _ => None, + } + } + /// Return device node minor of an device descriptor, if available + pub fn rdev_minor(&self) -> Option { + match self.file_type()? as libc::mode_t { + libc::S_IFBLK | libc::S_IFCHR => Some(minor(self.stat.st_rdev)), + _ => None, + } + } + /// Return preferered I/O Blocksize, if available + pub fn blksize(&self) -> Option { + Some(self.stat.st_blksize) + } + /// Return the number of 512 bytes blocks, if available + pub fn blocks(&self) -> Option { + Some(self.stat.st_blocks) + } + /// Returns file size (same as len() but Option), if available + pub fn size(&self) -> Option { + Some(self.stat.st_size) + } + /// Returns number of hard-links, if available + pub fn nlink(&self) -> Option { + Some(self.stat.st_nlink) + } + /// Returns user id, if available + pub fn uid(&self) -> Option { + Some(self.stat.st_uid) + } + /// Returns group id, if available + pub fn gid(&self) -> Option { + Some(self.stat.st_gid) + } + /// Returns mode bits, if available + pub fn file_mode(&self) -> Option { + Some(self.stat.st_mode & 0o7777) + } + /// Returns last access time, if available + pub fn atime(&self) -> Option { + Some(unix_systemtime(self.stat.st_atime, self.stat.st_atime_nsec)) + } + /// Returns creation, if available + pub fn btime(&self) -> Option { + None + } + /// Returns last status change time, if available + pub fn ctime(&self) -> Option { + Some(unix_systemtime(self.stat.st_ctime, self.stat.st_ctime_nsec)) + } + /// Returns last modification time, if available + pub fn mtime(&self) -> Option { + Some(unix_systemtime(self.stat.st_mtime, self.stat.st_mtime_nsec)) + } } pub fn new(stat: libc::stat) -> Metadata { Metadata { stat: stat } } +fn unix_systemtime(sec: libc::time_t, nsec: i64) -> SystemTime { + UNIX_EPOCH + Duration::from_secs(sec as u64) + Duration::from_nanos(nsec as u64) +} + +#[cfg(not(target_os = "macos"))] +pub fn major(dev: libc::dev_t) -> u32 { + unsafe { libc::major(dev) } +} + +#[cfg(not(target_os = "macos"))] +pub fn minor(dev: libc::dev_t) -> u32 { + unsafe { libc::minor(dev) } +} + +// major/minor are not in rust's darwin libc (why) +// see https://github.com/apple/darwin-xnu/blob/main/bsd/sys/types.h +#[cfg(target_os = "macos")] +pub fn major(dev: libc::dev_t) -> u32 { + (dev as u32 >> 24) & 0xff +} + +#[cfg(target_os = "macos")] +pub fn minor(dev: libc::dev_t) -> u32 { + dev as u32 & 0xffffff +} + #[cfg(test)] mod test { use super::*; diff --git a/tests/tmpfile.rs b/tests/tmpfile.rs index 4fa0f0d..73e8c36 100644 --- a/tests/tmpfile.rs +++ b/tests/tmpfile.rs @@ -6,7 +6,7 @@ use std::os::unix::fs::PermissionsExt; use openat::Dir; #[test] -#[cfg(target_os="linux")] +#[cfg(all(feature = "o_tmpfile", feature = "link_file_at"))] fn unnamed_tmp_file_link() -> Result<(), io::Error> { let tmp = tempfile::tempdir()?; let dir = Dir::open(tmp.path())?;