diff --git a/cap-async-std/src/fs/mod.rs b/cap-async-std/src/fs/mod.rs index 1b693128..b04c5bce 100644 --- a/cap-async-std/src/fs/mod.rs +++ b/cap-async-std/src/fs/mod.rs @@ -40,6 +40,9 @@ pub use cap_primitives::fs::{DirBuilder, FileType, Metadata, OpenOptions, Permis // Re-export conditional types from `cap_primitives`. #[cfg(any(unix, target_os = "vxworks", all(windows, windows_file_type_ext)))] pub use cap_primitives::fs::FileTypeExt; +pub use cap_primitives::fs::MetadataExt; +#[cfg(unix)] +pub use cap_primitives::fs::PermissionsExt; // Re-export things from `async_std` that we can use as-is. #[cfg(target_os = "wasi")] diff --git a/cap-async-std/src/fs_utf8/mod.rs b/cap-async-std/src/fs_utf8/mod.rs index 7c3c4a6e..c656ee5e 100644 --- a/cap-async-std/src/fs_utf8/mod.rs +++ b/cap-async-std/src/fs_utf8/mod.rs @@ -25,6 +25,9 @@ pub use crate::fs::{DirBuilder, FileType, Metadata, OpenOptions, Permissions}; // Re-export conditional types from `cap_primitives`. #[cfg(any(unix, target_os = "vxworks", all(windows, windows_file_type_ext)))] pub use cap_primitives::fs::FileTypeExt; +pub use cap_primitives::fs::MetadataExt; +#[cfg(unix)] +pub use cap_primitives::fs::PermissionsExt; // Re-export `camino` to make it easy for users to depend on the same // version we do, because we use its types in our public API. diff --git a/cap-fs-ext/build.rs b/cap-fs-ext/build.rs index 6e8f4d3f..5ea6ad2d 100644 --- a/cap-fs-ext/build.rs +++ b/cap-fs-ext/build.rs @@ -21,20 +21,60 @@ fn use_feature(feature: &str) { /// Test whether the rustc at `var("RUSTC")` supports the given feature. fn has_feature(feature: &str) -> bool { + can_compile(&format!( + "#![allow(stable_features)]\n#![feature({})]", + feature + )) +} + +/// Test whether the rustc at `var("RUSTC")` can compile the given code. +fn can_compile>(test: T) -> bool { + use std::process::Stdio; + let out_dir = var("OUT_DIR").unwrap(); let rustc = var("RUSTC").unwrap(); + let target = var("TARGET").unwrap(); + + // Use `RUSTC_WRAPPER` if it's set, unless it's set to an empty string, + // as documented [here]. + // [here]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-reads + let wrapper = var("RUSTC_WRAPPER") + .ok() + .and_then(|w| if w.is_empty() { None } else { Some(w) }); - let mut child = std::process::Command::new(rustc) - .arg("--crate-type=rlib") // Don't require `main`. + let mut cmd = if let Some(wrapper) = wrapper { + let mut cmd = std::process::Command::new(wrapper); + // The wrapper's first argument is supposed to be the path to rustc. + cmd.arg(rustc); + cmd + } else { + std::process::Command::new(rustc) + }; + + cmd.arg("--crate-type=rlib") // Don't require `main`. .arg("--emit=metadata") // Do as little as possible but still parse. + .arg("--target") + .arg(target) .arg("--out-dir") - .arg(out_dir) // Put the output somewhere inconsequential. + .arg(out_dir); // Put the output somewhere inconsequential. + + // If Cargo wants to set RUSTFLAGS, use that. + if let Ok(rustflags) = var("CARGO_ENCODED_RUSTFLAGS") { + if !rustflags.is_empty() { + for arg in rustflags.split('\x1f') { + cmd.arg(arg); + } + } + } + + let mut child = cmd .arg("-") // Read from stdin. - .stdin(std::process::Stdio::piped()) // Stdin is a pipe. + .stdin(Stdio::piped()) // Stdin is a pipe. + .stderr(Stdio::null()) // Errors from feature detection aren't interesting and can be confusing. .spawn() .unwrap(); - writeln!(child.stdin.take().unwrap(), "#![feature({})]", feature).unwrap(); + writeln!(child.stdin.take().unwrap(), "{}", test.as_ref()).unwrap(); child.wait().unwrap().success() } diff --git a/cap-fs-ext/src/metadata_ext.rs b/cap-fs-ext/src/metadata_ext.rs index 44c74a1e..fbacfcf7 100644 --- a/cap-fs-ext/src/metadata_ext.rs +++ b/cap-fs-ext/src/metadata_ext.rs @@ -75,17 +75,17 @@ impl MetadataExt for std::fs::Metadata { impl MetadataExt for cap_primitives::fs::Metadata { #[inline] fn dev(&self) -> u64 { - std::os::unix::fs::MetadataExt::dev(self) + cap_primitives::fs::MetadataExt::dev(self) } #[inline] fn ino(&self) -> u64 { - std::os::unix::fs::MetadataExt::ino(self) + cap_primitives::fs::MetadataExt::ino(self) } #[inline] fn nlink(&self) -> u64 { - std::os::unix::fs::MetadataExt::nlink(self) + cap_primitives::fs::MetadataExt::nlink(self) } } diff --git a/cap-primitives/build.rs b/cap-primitives/build.rs index 966799b5..7fbba9a5 100644 --- a/cap-primitives/build.rs +++ b/cap-primitives/build.rs @@ -25,20 +25,60 @@ fn use_feature(feature: &str) { /// Test whether the rustc at `var("RUSTC")` supports the given feature. fn has_feature(feature: &str) -> bool { + can_compile(&format!( + "#![allow(stable_features)]\n#![feature({})]", + feature + )) +} + +/// Test whether the rustc at `var("RUSTC")` can compile the given code. +fn can_compile>(test: T) -> bool { + use std::process::Stdio; + let out_dir = var("OUT_DIR").unwrap(); let rustc = var("RUSTC").unwrap(); + let target = var("TARGET").unwrap(); + + // Use `RUSTC_WRAPPER` if it's set, unless it's set to an empty string, + // as documented [here]. + // [here]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-reads + let wrapper = var("RUSTC_WRAPPER") + .ok() + .and_then(|w| if w.is_empty() { None } else { Some(w) }); - let mut child = std::process::Command::new(rustc) - .arg("--crate-type=rlib") // Don't require `main`. + let mut cmd = if let Some(wrapper) = wrapper { + let mut cmd = std::process::Command::new(wrapper); + // The wrapper's first argument is supposed to be the path to rustc. + cmd.arg(rustc); + cmd + } else { + std::process::Command::new(rustc) + }; + + cmd.arg("--crate-type=rlib") // Don't require `main`. .arg("--emit=metadata") // Do as little as possible but still parse. + .arg("--target") + .arg(target) .arg("--out-dir") - .arg(out_dir) // Put the output somewhere inconsequential. + .arg(out_dir); // Put the output somewhere inconsequential. + + // If Cargo wants to set RUSTFLAGS, use that. + if let Ok(rustflags) = var("CARGO_ENCODED_RUSTFLAGS") { + if !rustflags.is_empty() { + for arg in rustflags.split('\x1f') { + cmd.arg(arg); + } + } + } + + let mut child = cmd .arg("-") // Read from stdin. - .stdin(std::process::Stdio::piped()) // Stdin is a pipe. + .stdin(Stdio::piped()) // Stdin is a pipe. + .stderr(Stdio::null()) // Errors from feature detection aren't interesting and can be confusing. .spawn() .unwrap(); - writeln!(child.stdin.take().unwrap(), "#![feature({})]", feature).unwrap(); + writeln!(child.stdin.take().unwrap(), "{}", test.as_ref()).unwrap(); child.wait().unwrap().success() } diff --git a/cap-primitives/src/fs/metadata.rs b/cap-primitives/src/fs/metadata.rs index afcda83d..ed214e2a 100644 --- a/cap-primitives/src/fs/metadata.rs +++ b/cap-primitives/src/fs/metadata.rs @@ -1,4 +1,4 @@ -use crate::fs::{FileType, ImplFileTypeExt, MetadataExt, Permissions}; +use crate::fs::{FileType, ImplFileTypeExt, ImplMetadataExt, Permissions}; use crate::time::SystemTime; use std::{fs, io}; @@ -18,7 +18,7 @@ pub struct Metadata { pub(crate) modified: Option, pub(crate) accessed: Option, pub(crate) created: Option, - pub(crate) ext: MetadataExt, + pub(crate) ext: ImplMetadataExt, } #[allow(clippy::len_without_is_empty)] @@ -27,7 +27,7 @@ impl Metadata { #[inline] pub fn from_file(file: &fs::File) -> io::Result { let std = file.metadata()?; - let ext = MetadataExt::from(file, &std)?; + let ext = ImplMetadataExt::from(file, &std)?; let file_type = ImplFileTypeExt::from(file, &std)?; Ok(Self::from_parts(std, ext, file_type)) } @@ -41,13 +41,13 @@ impl Metadata { /// [`std::fs::Metadata::volume_serial_number`]: https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.volume_serial_number #[inline] pub fn from_just_metadata(std: fs::Metadata) -> Self { - let ext = MetadataExt::from_just_metadata(&std); + let ext = ImplMetadataExt::from_just_metadata(&std); let file_type = ImplFileTypeExt::from_just_metadata(&std); Self::from_parts(std, ext, file_type) } #[inline] - fn from_parts(std: fs::Metadata, ext: MetadataExt, file_type: FileType) -> Self { + fn from_parts(std: fs::Metadata, ext: ImplMetadataExt, file_type: FileType) -> Self { Self { file_type, len: std.len(), @@ -198,129 +198,215 @@ impl Metadata { } } +/// Unix-specific extensions for [`MetadataExt`]. +/// +/// This corresponds to [`std::os::unix::fs::MetadataExt`]. +#[cfg(any(unix, target_os = "vxworks"))] +pub trait MetadataExt { + /// Returns the ID of the device containing the file. + fn dev(&self) -> u64; + /// Returns the inode number. + fn ino(&self) -> u64; + /// Returns the rights applied to this file. + fn mode(&self) -> u32; + /// Returns the number of hard links pointing to this file. + fn nlink(&self) -> u64; + /// Returns the user ID of the owner of this file. + fn uid(&self) -> u32; + /// Returns the group ID of the owner of this file. + fn gid(&self) -> u32; + /// Returns the device ID of this file (if it is a special one). + fn rdev(&self) -> u64; + /// Returns the total size of this file in bytes. + fn size(&self) -> u64; + /// Returns the last access time of the file, in seconds since Unix Epoch. + fn atime(&self) -> i64; + /// Returns the last access time of the file, in nanoseconds since [`atime`]. + fn atime_nsec(&self) -> i64; + /// Returns the last modification time of the file, in seconds since Unix Epoch. + fn mtime(&self) -> i64; + /// Returns the last modification time of the file, in nanoseconds since [`mtime`]. + fn mtime_nsec(&self) -> i64; + /// Returns the last status change time of the file, in seconds since Unix Epoch. + fn ctime(&self) -> i64; + /// Returns the last status change time of the file, in nanoseconds since [`ctime`]. + fn ctime_nsec(&self) -> i64; + /// Returns the block size for filesystem I/O. + fn blksize(&self) -> u64; + /// Returns the number of blocks allocated to the file, in 512-byte units. + fn blocks(&self) -> u64; + #[cfg(target_os = "vxworks")] + fn attrib(&self) -> u8; +} + +/// WASI-specific extensions for [`MetadataExt`]. +/// +/// This corresponds to [`std::os::wasi::fs::MetadataExt`]. +#[cfg(target_os = "wasi")] +pub trait MetadataExt { + /// Returns the ID of the device containing the file. + fn dev(&self) -> u64; + /// Returns the inode number. + fn ino(&self) -> u64; + /// Returns the number of hard links pointing to this file. + fn nlink(&self) -> u64; + /// Returns the total size of this file in bytes. + fn size(&self) -> u64; + /// Returns the last access time of the file, in seconds since Unix Epoch. + fn atim(&self) -> u64; + /// Returns the last modification time of the file, in seconds since Unix Epoch. + fn mtim(&self) -> u64; + /// Returns the last status change time of the file, in seconds since Unix Epoch. + fn ctim(&self) -> u64; +} + +/// Windows-specific extensions to [`Metadata`]. +#[cfg(windows)] +pub trait MetadataExt { + /// Returns the value of the `dwFileAttributes` field of this metadata. + fn file_attributes(&self) -> u32; + /// Returns the value of the `ftCreationTime` field of this metadata. + fn creation_time(&self) -> u64; + /// Returns the value of the `ftLastAccessTime` field of this metadata. + fn last_access_time(&self) -> u64; + /// Returns the value of the `ftLastWriteTime` field of this metadata. + fn last_write_time(&self) -> u64; + /// Returns the value of the `nFileSize{High,Low}` fields of this metadata. + fn file_size(&self) -> u64; + /// Returns the value of the `dwVolumeSerialNumber` field of this metadata. + #[cfg(windows_by_handle)] + fn volume_serial_number(&self) -> Option; + /// Returns the value of the `nNumberOfLinks` field of this metadata. + #[cfg(windows_by_handle)] + fn number_of_links(&self) -> Option; + /// Returns the value of the `nFileIndex{Low,High}` fields of this metadata. + #[cfg(windows_by_handle)] + fn file_index(&self) -> Option; +} + #[cfg(unix)] -impl std::os::unix::fs::MetadataExt for Metadata { +impl MetadataExt for Metadata { #[inline] fn dev(&self) -> u64 { - self.ext.dev() + crate::fs::MetadataExt::dev(&self.ext) } #[inline] fn ino(&self) -> u64 { - self.ext.ino() + crate::fs::MetadataExt::ino(&self.ext) } #[inline] fn mode(&self) -> u32 { - self.ext.mode() + crate::fs::MetadataExt::mode(&self.ext) } #[inline] fn nlink(&self) -> u64 { - self.ext.nlink() + crate::fs::MetadataExt::nlink(&self.ext) } #[inline] fn uid(&self) -> u32 { - self.ext.uid() + crate::fs::MetadataExt::uid(&self.ext) } #[inline] fn gid(&self) -> u32 { - self.ext.gid() + crate::fs::MetadataExt::gid(&self.ext) } #[inline] fn rdev(&self) -> u64 { - self.ext.rdev() + crate::fs::MetadataExt::rdev(&self.ext) } #[inline] fn size(&self) -> u64 { - self.ext.size() + crate::fs::MetadataExt::size(&self.ext) } #[inline] fn atime(&self) -> i64 { - self.ext.atime() + crate::fs::MetadataExt::atime(&self.ext) } #[inline] fn atime_nsec(&self) -> i64 { - self.ext.atime_nsec() + crate::fs::MetadataExt::atime_nsec(&self.ext) } #[inline] fn mtime(&self) -> i64 { - self.ext.mtime() + crate::fs::MetadataExt::mtime(&self.ext) } #[inline] fn mtime_nsec(&self) -> i64 { - self.ext.mtime_nsec() + crate::fs::MetadataExt::mtime_nsec(&self.ext) } #[inline] fn ctime(&self) -> i64 { - self.ext.ctime() + crate::fs::MetadataExt::ctime(&self.ext) } #[inline] fn ctime_nsec(&self) -> i64 { - self.ext.ctime_nsec() + crate::fs::MetadataExt::ctime_nsec(&self.ext) } #[inline] fn blksize(&self) -> u64 { - self.ext.blksize() + crate::fs::MetadataExt::blksize(&self.ext) } #[inline] fn blocks(&self) -> u64 { - self.ext.blocks() + crate::fs::MetadataExt::blocks(&self.ext) } } #[cfg(target_os = "wasi")] -impl std::os::wasi::fs::MetadataExt for Metadata { +impl MetadataExt for Metadata { #[inline] fn dev(&self) -> u64 { - self.ext.dev() + crate::fs::MetadataExt::dev(&self.ext) } #[inline] fn ino(&self) -> u64 { - self.ext.ino() + crate::fs::MetadataExt::ino(&self.ext) } #[inline] fn nlink(&self) -> u64 { - self.ext.nlink() + crate::fs::MetadataExt::nlink(&self.ext) } #[inline] fn size(&self) -> u64 { - self.ext.size() + crate::fs::MetadataExt::size(&self.ext) } #[inline] fn atim(&self) -> u64 { - self.ext.atim() + crate::fs::MetadataExt::atim(&self.ext) } #[inline] fn mtim(&self) -> u64 { - self.ext.mtim() + crate::fs::MetadataExt::mtim(&self.ext) } #[inline] fn ctim(&self) -> u64 { - self.ext.ctim() + crate::fs::MetadataExt::ctim(&self.ext) } } #[cfg(target_os = "vxworks")] -impl std::os::vxworks::fs::MetadataExt for Metadata { +impl MetadataExt for Metadata { #[inline] fn dev(&self) -> u64 { self.ext.dev() @@ -402,8 +488,8 @@ impl std::os::vxworks::fs::MetadataExt for Metadata { } } -#[cfg(all(windows, windows_by_handle))] -impl std::os::windows::fs::MetadataExt for Metadata { +#[cfg(windows)] +impl MetadataExt for Metadata { #[inline] fn file_attributes(&self) -> u32 { self.ext.file_attributes() @@ -430,16 +516,19 @@ impl std::os::windows::fs::MetadataExt for Metadata { } #[inline] + #[cfg(windows_by_handle)] fn volume_serial_number(&self) -> Option { self.ext.volume_serial_number() } #[inline] + #[cfg(windows_by_handle)] fn number_of_links(&self) -> Option { self.ext.number_of_links() } #[inline] + #[cfg(windows_by_handle)] fn file_index(&self) -> Option { self.ext.file_index() } diff --git a/cap-primitives/src/fs/mod.rs b/cap-primitives/src/fs/mod.rs index 09050d9b..63a6cf41 100644 --- a/cap-primitives/src/fs/mod.rs +++ b/cap-primitives/src/fs/mod.rs @@ -75,14 +75,16 @@ pub use file_type::_WindowsFileTypeExt; pub use follow_symlinks::FollowSymlinks; pub use hard_link::hard_link; pub use is_file_read_write::is_file_read_write; -pub use metadata::Metadata; #[cfg(windows)] pub use metadata::_WindowsByHandle; +pub use metadata::{Metadata, MetadataExt}; pub use open::open; pub use open_ambient::open_ambient; pub use open_dir::*; pub use open_options::OpenOptions; pub use permissions::Permissions; +#[cfg(unix)] +pub use permissions::PermissionsExt; pub use read_dir::{read_base_dir, read_dir, ReadDir}; pub use read_link::{read_link, read_link_contents}; pub use remove_dir::remove_dir; diff --git a/cap-primitives/src/fs/permissions.rs b/cap-primitives/src/fs/permissions.rs index be356b68..eefaae66 100644 --- a/cap-primitives/src/fs/permissions.rs +++ b/cap-primitives/src/fs/permissions.rs @@ -1,5 +1,5 @@ #[cfg(not(windows))] -use crate::fs::PermissionsExt; +use crate::fs::ImplPermissionsExt; #[cfg(unix)] use rustix::fs::RawMode; use std::{fs, io}; @@ -17,7 +17,7 @@ pub struct Permissions { pub(crate) readonly: bool, #[cfg(any(unix, target_os = "vxworks"))] - pub(crate) ext: PermissionsExt, + pub(crate) ext: ImplPermissionsExt, } impl Permissions { @@ -29,7 +29,7 @@ impl Permissions { readonly: std.readonly(), #[cfg(any(unix, target_os = "vxworks"))] - ext: PermissionsExt::from_std(std), + ext: ImplPermissionsExt::from_std(std), } } @@ -90,29 +90,44 @@ impl Permissions { } } +/// Unix-specific extensions to [`Permissions`]. #[cfg(unix)] -impl std::os::unix::fs::PermissionsExt for Permissions { +pub trait PermissionsExt { + /// Returns the underlying raw `st_mode` bits that contain the standard + /// Unix permissions for this file. + fn mode(&self) -> u32; + + /// Sets the underlying raw bits for this set of permissions. + fn set_mode(&mut self, mode: u32); + + /// Creates a new instance of `Permissions` from the given set of Unix + /// permission bits. + fn from_mode(mode: u32) -> Self; +} + +#[cfg(unix)] +impl PermissionsExt for Permissions { #[inline] fn mode(&self) -> u32 { - self.ext.mode() + std::os::unix::fs::PermissionsExt::mode(&self.ext) } #[inline] fn set_mode(&mut self, mode: u32) { - self.ext.set_mode(mode) + std::os::unix::fs::PermissionsExt::set_mode(&mut self.ext, mode) } #[inline] fn from_mode(mode: u32) -> Self { Self { - readonly: PermissionsExt::readonly(mode as RawMode), - ext: PermissionsExt::from_mode(mode), + readonly: ImplPermissionsExt::readonly(mode as RawMode), + ext: std::os::unix::fs::PermissionsExt::from_mode(mode), } } } #[cfg(target_os = "vxworks")] -impl std::os::unix::fs::PermissionsExt for Permissions { +impl PermissionsExt for Permissions { #[inline] fn mode(&self) -> u32 { self.ext.mode() @@ -126,8 +141,8 @@ impl std::os::unix::fs::PermissionsExt for Permissions { #[inline] fn from_mode(mode: u32) -> Self { Self { - readonly: PermissionsExt::readonly(mode), - ext: PermissionsExt::from(mode), + readonly: ImplPermissionsExt::readonly(mode), + ext: ImplPermissionsExt::from(mode), } } } diff --git a/cap-primitives/src/rustix/freebsd/fs/set_permissions_impl.rs b/cap-primitives/src/rustix/freebsd/fs/set_permissions_impl.rs index 9df0a752..ab9bc311 100644 --- a/cap-primitives/src/rustix/freebsd/fs/set_permissions_impl.rs +++ b/cap-primitives/src/rustix/freebsd/fs/set_permissions_impl.rs @@ -1,6 +1,5 @@ -use crate::fs::Permissions; +use crate::fs::{Permissions, PermissionsExt}; use rustix::fs::{chmodat, AtFlags, Mode}; -use std::os::unix::fs::PermissionsExt; use std::path::Path; use std::{fs, io}; diff --git a/cap-primitives/src/rustix/freebsd/fs/stat_impl.rs b/cap-primitives/src/rustix/freebsd/fs/stat_impl.rs index 3a7620cb..aa4bbe65 100644 --- a/cap-primitives/src/rustix/freebsd/fs/stat_impl.rs +++ b/cap-primitives/src/rustix/freebsd/fs/stat_impl.rs @@ -1,4 +1,4 @@ -use crate::fs::{manually, FollowSymlinks, Metadata, MetadataExt}; +use crate::fs::{manually, FollowSymlinks, ImplMetadataExt, Metadata}; use rustix::fs::{statat, AtFlags}; use std::path::Path; use std::{fs, io}; @@ -18,5 +18,5 @@ pub(crate) fn stat_impl( } else { AtFlags::SYMLINK_NOFOLLOW }; - Ok(MetadataExt::from_rustix(statat(start, path, flags)?)) + Ok(ImplMetadataExt::from_rustix(statat(start, path, flags)?)) } diff --git a/cap-primitives/src/rustix/fs/dir_entry_inner.rs b/cap-primitives/src/rustix/fs/dir_entry_inner.rs index cba5238e..35923880 100644 --- a/cap-primitives/src/rustix/fs/dir_entry_inner.rs +++ b/cap-primitives/src/rustix/fs/dir_entry_inner.rs @@ -1,12 +1,13 @@ use crate::fs::{ - FileType, FollowSymlinks, ImplFileTypeExt, Metadata, OpenOptions, ReadDir, ReadDirInner, + FileType, FollowSymlinks, ImplFileTypeExt, Metadata, MetadataExt, OpenOptions, ReadDir, + ReadDirInner, }; use rustix::fs::DirEntry; use std::ffi::{OsStr, OsString}; #[cfg(unix)] -use std::os::unix::{ffi::OsStrExt, fs::MetadataExt}; +use std::os::unix::ffi::OsStrExt; #[cfg(target_os = "wasi")] -use std::os::wasi::{ffi::OsStrExt, fs::MetadataExt}; +use std::os::wasi::ffi::OsStrExt; use std::{fmt, fs, io}; pub(crate) struct DirEntryInner { diff --git a/cap-primitives/src/rustix/fs/is_same_file.rs b/cap-primitives/src/rustix/fs/is_same_file.rs index 382e436b..c6acf9ca 100644 --- a/cap-primitives/src/rustix/fs/is_same_file.rs +++ b/cap-primitives/src/rustix/fs/is_same_file.rs @@ -1,5 +1,4 @@ -use crate::fs::Metadata; -use rustix::fs::MetadataExt; +use crate::fs::{Metadata, MetadataExt}; use std::{fs, io}; /// Determine if `a` and `b` refer to the same inode on the same device. diff --git a/cap-primitives/src/rustix/fs/metadata_ext.rs b/cap-primitives/src/rustix/fs/metadata_ext.rs index 7091017e..0cd9de03 100644 --- a/cap-primitives/src/rustix/fs/metadata_ext.rs +++ b/cap-primitives/src/rustix/fs/metadata_ext.rs @@ -1,6 +1,6 @@ #![allow(clippy::useless_conversion)] -use crate::fs::{ImplFileTypeExt, Metadata, PermissionsExt}; +use crate::fs::{ImplFileTypeExt, ImplPermissionsExt, Metadata}; use crate::time::{Duration, SystemClock, SystemTime}; #[cfg(target_os = "linux")] use rustix::fs::{makedev, Statx, StatxFlags}; @@ -8,7 +8,7 @@ use rustix::fs::{RawMode, Stat}; use std::{fs, io}; #[derive(Debug, Clone)] -pub(crate) struct MetadataExt { +pub(crate) struct ImplMetadataExt { dev: u64, ino: u64, #[cfg(not(target_os = "wasi"))] @@ -45,7 +45,7 @@ pub(crate) struct MetadataExt { ctim: u64, } -impl MetadataExt { +impl ImplMetadataExt { /// Constructs a new instance of `Self` from the given [`std::fs::File`] /// and [`std::fs::Metadata`]. #[inline] @@ -106,9 +106,9 @@ impl MetadataExt { file_type: ImplFileTypeExt::from_raw_mode(stat.st_mode as RawMode), len: u64::try_from(stat.st_size).unwrap(), #[cfg(not(target_os = "wasi"))] - permissions: PermissionsExt::from_raw_mode(stat.st_mode as RawMode), + permissions: ImplPermissionsExt::from_raw_mode(stat.st_mode as RawMode), #[cfg(target_os = "wasi")] - permissions: PermissionsExt::default(), + permissions: ImplPermissionsExt::default(), #[cfg(not(any(target_os = "netbsd", target_os = "wasi")))] modified: system_time_from_rustix( @@ -238,7 +238,7 @@ impl MetadataExt { Metadata { file_type: ImplFileTypeExt::from_raw_mode(RawMode::from(statx.stx_mode)), len: u64::try_from(statx.stx_size).unwrap(), - permissions: PermissionsExt::from_raw_mode(RawMode::from(statx.stx_mode)), + permissions: ImplPermissionsExt::from_raw_mode(RawMode::from(statx.stx_mode)), modified: if statx.stx_mask & StatxFlags::MTIME.bits() != 0 { system_time_from_rustix(statx.stx_mtime.tv_sec, statx.stx_mtime.tv_nsec as _) } else { @@ -295,7 +295,7 @@ fn system_time_from_rustix(sec: i64, nsec: u64) -> Option { } } -impl rustix::fs::MetadataExt for MetadataExt { +impl crate::fs::MetadataExt for ImplMetadataExt { #[inline] fn dev(&self) -> u64 { self.dev diff --git a/cap-primitives/src/rustix/fs/mod.rs b/cap-primitives/src/rustix/fs/mod.rs index ac45f84a..1ef574b9 100644 --- a/cap-primitives/src/rustix/fs/mod.rs +++ b/cap-primitives/src/rustix/fs/mod.rs @@ -113,10 +113,10 @@ pub(crate) use is_file_read_write_impl::is_file_read_write_impl; pub(crate) use is_root_dir::is_root_dir; #[allow(unused_imports)] pub(crate) use is_same_file::{is_different_file, is_different_file_metadata, is_same_file}; -pub(crate) use metadata_ext::MetadataExt; +pub(crate) use metadata_ext::ImplMetadataExt; pub(crate) use open_options_ext::OpenOptionsExt; pub(crate) use open_unchecked::{open_ambient_impl, open_unchecked}; -pub(crate) use permissions_ext::PermissionsExt; +pub(crate) use permissions_ext::ImplPermissionsExt; pub(crate) use read_dir_inner::ReadDirInner; pub(crate) use read_link_unchecked::read_link_unchecked; pub(crate) use remove_dir_all_impl::{remove_dir_all_impl, remove_open_dir_all_impl}; diff --git a/cap-primitives/src/rustix/fs/permissions_ext.rs b/cap-primitives/src/rustix/fs/permissions_ext.rs index a84ed905..4b6e04b4 100644 --- a/cap-primitives/src/rustix/fs/permissions_ext.rs +++ b/cap-primitives/src/rustix/fs/permissions_ext.rs @@ -3,13 +3,13 @@ use rustix::fs::RawMode; use std::fs; #[derive(Debug, Clone, Eq, PartialEq)] -pub(crate) struct PermissionsExt { +pub(crate) struct ImplPermissionsExt { #[cfg(not(target_os = "wasi"))] mode: RawMode, } #[cfg(not(target_os = "wasi"))] -impl PermissionsExt { +impl ImplPermissionsExt { /// Constructs a new instance of `Self` from the given /// [`std::fs::Permissions`]. #[inline] @@ -50,7 +50,7 @@ impl PermissionsExt { } #[cfg(not(target_os = "wasi"))] -impl std::os::unix::fs::PermissionsExt for PermissionsExt { +impl std::os::unix::fs::PermissionsExt for ImplPermissionsExt { fn mode(&self) -> u32 { self.mode as u32 } @@ -67,7 +67,7 @@ impl std::os::unix::fs::PermissionsExt for PermissionsExt { } #[cfg(target_os = "wasi")] -impl PermissionsExt { +impl ImplPermissionsExt { pub(crate) fn default() -> Permissions { Permissions { readonly: false } } diff --git a/cap-primitives/src/rustix/fs/set_symlink_permissions_unchecked.rs b/cap-primitives/src/rustix/fs/set_symlink_permissions_unchecked.rs index 4c5ff30a..fb3a8f02 100644 --- a/cap-primitives/src/rustix/fs/set_symlink_permissions_unchecked.rs +++ b/cap-primitives/src/rustix/fs/set_symlink_permissions_unchecked.rs @@ -1,7 +1,7 @@ use crate::fs::Permissions; -use rustix::fs::{chmodat, AtFlags, Mode}; #[cfg(unix)] -use std::os::unix::fs::PermissionsExt; +use crate::fs::PermissionsExt; +use rustix::fs::{chmodat, AtFlags, Mode}; use std::path::Path; use std::{fs, io}; diff --git a/cap-primitives/src/rustix/fs/stat_unchecked.rs b/cap-primitives/src/rustix/fs/stat_unchecked.rs index 6c35bd3f..f10b926b 100644 --- a/cap-primitives/src/rustix/fs/stat_unchecked.rs +++ b/cap-primitives/src/rustix/fs/stat_unchecked.rs @@ -1,4 +1,4 @@ -use crate::fs::{FollowSymlinks, Metadata, MetadataExt}; +use crate::fs::{FollowSymlinks, ImplMetadataExt, Metadata}; use rustix::fs::{statat, AtFlags}; use std::path::Path; use std::{fs, io}; @@ -50,7 +50,7 @@ pub(crate) fn stat_unchecked( if state == 0 { STATX_STATE.store(2, Ordering::Relaxed); } - return Ok(MetadataExt::from_rustix_statx(statx)); + return Ok(ImplMetadataExt::from_rustix_statx(statx)); } Err(rustix::io::Errno::NOSYS) => STATX_STATE.store(1, Ordering::Relaxed), Err(rustix::io::Errno::PERM) if state == 0 => { @@ -75,5 +75,5 @@ pub(crate) fn stat_unchecked( } } - Ok(statat(start, path, atflags).map(MetadataExt::from_rustix)?) + Ok(statat(start, path, atflags).map(ImplMetadataExt::from_rustix)?) } diff --git a/cap-primitives/src/rustix/linux/fs/file_metadata.rs b/cap-primitives/src/rustix/linux/fs/file_metadata.rs index 3ad72a69..4f24a5dc 100644 --- a/cap-primitives/src/rustix/linux/fs/file_metadata.rs +++ b/cap-primitives/src/rustix/linux/fs/file_metadata.rs @@ -1,4 +1,4 @@ -use crate::fs::{Metadata, MetadataExt}; +use crate::fs::{ImplMetadataExt, Metadata}; use rustix::fs::{statat, AtFlags}; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering::Relaxed; @@ -23,5 +23,5 @@ pub(super) fn file_metadata(file: &fs::File) -> io::Result { } // If `fstat` with `O_PATH` isn't supported, use `statat` with `AT_EMPTY_PATH`. - Ok(statat(file, "", AtFlags::EMPTY_PATH).map(MetadataExt::from_rustix)?) + Ok(statat(file, "", AtFlags::EMPTY_PATH).map(ImplMetadataExt::from_rustix)?) } diff --git a/cap-primitives/src/windows/fs/is_same_file.rs b/cap-primitives/src/windows/fs/is_same_file.rs index 86a6d9eb..9bf7aba4 100644 --- a/cap-primitives/src/windows/fs/is_same_file.rs +++ b/cap-primitives/src/windows/fs/is_same_file.rs @@ -1,12 +1,12 @@ +use crate::fs::ImplMetadataExt; #[cfg(windows_by_handle)] use crate::fs::Metadata; -use crate::fs::MetadataExt; use std::{fs, io}; /// Determine if `a` and `b` refer to the same inode on the same device. pub(crate) fn is_same_file(a: &fs::File, b: &fs::File) -> io::Result { - let a_metadata = MetadataExt::from(a, &a.metadata()?)?; - let b_metadata = MetadataExt::from(b, &b.metadata()?)?; + let a_metadata = ImplMetadataExt::from(a, &a.metadata()?)?; + let b_metadata = ImplMetadataExt::from(b, &b.metadata()?)?; Ok(a_metadata.is_same_file(&b_metadata)) } @@ -15,7 +15,7 @@ pub(crate) fn is_same_file(a: &fs::File, b: &fs::File) -> io::Result { #[cfg(windows_by_handle)] #[allow(dead_code)] pub(crate) fn is_same_file_metadata(a: &Metadata, b: &Metadata) -> io::Result { - use std::os::windows::fs::MetadataExt; + use crate::fs::MetadataExt; Ok(a.volume_serial_number() == b.volume_serial_number() && a.file_index() == b.file_index()) } diff --git a/cap-primitives/src/windows/fs/metadata_ext.rs b/cap-primitives/src/windows/fs/metadata_ext.rs index 64797c77..ed692855 100644 --- a/cap-primitives/src/windows/fs/metadata_ext.rs +++ b/cap-primitives/src/windows/fs/metadata_ext.rs @@ -1,24 +1,21 @@ #![allow(clippy::useless_conversion)] +use crate::fs::MetadataExt; use std::{fs, io}; #[derive(Debug, Clone)] -pub(crate) struct MetadataExt { +pub(crate) struct ImplMetadataExt { file_attributes: u32, - #[cfg(windows_by_handle)] creation_time: u64, - #[cfg(windows_by_handle)] last_access_time: u64, - #[cfg(windows_by_handle)] last_write_time: u64, - #[cfg(windows_by_handle)] file_size: u64, volume_serial_number: Option, number_of_links: Option, file_index: Option, } -impl MetadataExt { +impl ImplMetadataExt { /// Constructs a new instance of `Self` from the given [`std::fs::File`] /// and [`std::fs::Metadata`]. #[inline] @@ -105,13 +102,9 @@ impl MetadataExt { use std::os::windows::fs::MetadataExt; Self { file_attributes: std.file_attributes(), - #[cfg(windows_by_handle)] creation_time: std.creation_time(), - #[cfg(windows_by_handle)] last_access_time: std.last_access_time(), - #[cfg(windows_by_handle)] last_write_time: std.last_write_time(), - #[cfg(windows_by_handle)] file_size: std.file_size(), volume_serial_number, number_of_links, @@ -147,9 +140,7 @@ impl MetadataExt { } } -#[cfg(windows_by_handle)] -impl std::os::windows::fs::MetadataExt for MetadataExt { - #[inline] +impl MetadataExt for ImplMetadataExt { fn file_attributes(&self) -> u32 { self.file_attributes } @@ -175,16 +166,19 @@ impl std::os::windows::fs::MetadataExt for MetadataExt { } #[inline] + #[cfg(windows_by_handle)] fn volume_serial_number(&self) -> Option { self.volume_serial_number } #[inline] + #[cfg(windows_by_handle)] fn number_of_links(&self) -> Option { self.number_of_links } #[inline] + #[cfg(windows_by_handle)] fn file_index(&self) -> Option { self.file_index } diff --git a/cap-std/build.rs b/cap-std/build.rs index a11d1016..92d4b576 100644 --- a/cap-std/build.rs +++ b/cap-std/build.rs @@ -22,20 +22,60 @@ fn use_feature(feature: &str) { /// Test whether the rustc at `var("RUSTC")` supports the given feature. fn has_feature(feature: &str) -> bool { + can_compile(&format!( + "#![allow(stable_features)]\n#![feature({})]", + feature + )) +} + +/// Test whether the rustc at `var("RUSTC")` can compile the given code. +fn can_compile>(test: T) -> bool { + use std::process::Stdio; + let out_dir = var("OUT_DIR").unwrap(); let rustc = var("RUSTC").unwrap(); + let target = var("TARGET").unwrap(); + + // Use `RUSTC_WRAPPER` if it's set, unless it's set to an empty string, + // as documented [here]. + // [here]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-reads + let wrapper = var("RUSTC_WRAPPER") + .ok() + .and_then(|w| if w.is_empty() { None } else { Some(w) }); - let mut child = std::process::Command::new(rustc) - .arg("--crate-type=rlib") // Don't require `main`. + let mut cmd = if let Some(wrapper) = wrapper { + let mut cmd = std::process::Command::new(wrapper); + // The wrapper's first argument is supposed to be the path to rustc. + cmd.arg(rustc); + cmd + } else { + std::process::Command::new(rustc) + }; + + cmd.arg("--crate-type=rlib") // Don't require `main`. .arg("--emit=metadata") // Do as little as possible but still parse. + .arg("--target") + .arg(target) .arg("--out-dir") - .arg(out_dir) // Put the output somewhere inconsequential. + .arg(out_dir); // Put the output somewhere inconsequential. + + // If Cargo wants to set RUSTFLAGS, use that. + if let Ok(rustflags) = var("CARGO_ENCODED_RUSTFLAGS") { + if !rustflags.is_empty() { + for arg in rustflags.split('\x1f') { + cmd.arg(arg); + } + } + } + + let mut child = cmd .arg("-") // Read from stdin. - .stdin(std::process::Stdio::piped()) // Stdin is a pipe. + .stdin(Stdio::piped()) // Stdin is a pipe. + .stderr(Stdio::null()) // Errors from feature detection aren't interesting and can be confusing. .spawn() .unwrap(); - writeln!(child.stdin.take().unwrap(), "#![feature({})]", feature).unwrap(); + writeln!(child.stdin.take().unwrap(), "{}", test.as_ref()).unwrap(); child.wait().unwrap().success() } diff --git a/cap-std/src/fs/mod.rs b/cap-std/src/fs/mod.rs index 94883112..33c45682 100644 --- a/cap-std/src/fs/mod.rs +++ b/cap-std/src/fs/mod.rs @@ -38,3 +38,6 @@ pub use cap_primitives::fs::{DirBuilder, FileType, Metadata, OpenOptions, Permis // Re-export conditional types from `cap_primitives`. #[cfg(any(unix, target_os = "vxworks", all(windows, windows_file_type_ext)))] pub use cap_primitives::fs::FileTypeExt; +pub use cap_primitives::fs::MetadataExt; +#[cfg(unix)] +pub use cap_primitives::fs::PermissionsExt; diff --git a/cap-std/src/fs_utf8/mod.rs b/cap-std/src/fs_utf8/mod.rs index c64220a6..996a7415 100644 --- a/cap-std/src/fs_utf8/mod.rs +++ b/cap-std/src/fs_utf8/mod.rs @@ -27,6 +27,9 @@ pub use crate::fs::{DirBuilder, FileType, Metadata, OpenOptions, Permissions}; // Re-export conditional types from `cap_primitives`. #[cfg(any(unix, target_os = "vxworks", all(windows, windows_file_type_ext)))] pub use cap_primitives::fs::FileTypeExt; +pub use cap_primitives::fs::MetadataExt; +#[cfg(unix)] +pub use cap_primitives::fs::PermissionsExt; // Re-export `camino` to make it easy for users to depend on the same // version we do, because we use its types in our public API. diff --git a/cap-tempfile/src/tempfile.rs b/cap-tempfile/src/tempfile.rs index 88da2247..6dbd2a2c 100644 --- a/cap-tempfile/src/tempfile.rs +++ b/cap-tempfile/src/tempfile.rs @@ -265,7 +265,8 @@ mod test { // Test that we created with the right permissions #[cfg(any(target_os = "android", target_os = "linux"))] { - use rustix::fs::{MetadataExt, Mode}; + use cap_std::fs_utf8::MetadataExt; + use rustix::fs::Mode; let umask = get_process_umask()?; let metadata = tf.as_file().metadata().unwrap(); let mode = metadata.mode(); diff --git a/tests/dir-entry-ext.rs b/tests/dir-entry-ext.rs index 32a326d1..5b36b6a4 100644 --- a/tests/dir-entry-ext.rs +++ b/tests/dir-entry-ext.rs @@ -15,7 +15,7 @@ fn test_dir_entry_ext() { // First try with the regular `metadata()`. All nones. #[cfg(all(windows, windows_by_handle))] for entry in check!(tmpdir.entries()) { - use std::os::windows::fs::MetadataExt; + use cap_std::fs::MetadataExt; let entry = check!(entry); assert!(check!(entry.metadata()).volume_serial_number().is_none()); assert!(check!(entry.metadata()).number_of_links().is_none()); diff --git a/tests/fs.rs b/tests/fs.rs index 483ef0c9..e14b4f41 100644 --- a/tests/fs.rs +++ b/tests/fs.rs @@ -334,7 +334,7 @@ fn file_test_io_read_write_at() { #[cfg(unix)] #[cfg_attr(any(target_os = "macos", target_os = "ios"), ignore)] fn set_get_unix_permissions() { - use std::os::unix::fs::PermissionsExt; + use cap_std::fs::PermissionsExt; let tmpdir = tmpdir(); let filename = "set_get_unix_permissions"; diff --git a/tests/fs_additional.rs b/tests/fs_additional.rs index 06cefa80..65dd0b6d 100644 --- a/tests/fs_additional.rs +++ b/tests/fs_additional.rs @@ -967,7 +967,10 @@ fn check_metadata(std: &std::fs::Metadata, cap: &cap_std::fs::Metadata) { #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; - assert_eq!(std.permissions().mode(), cap.permissions().mode()); + assert_eq!( + std.permissions().mode(), + cap_std::fs::PermissionsExt::mode(&cap.permissions()) + ); } // If the standard library supports file modified/accessed/created times, @@ -1022,28 +1025,28 @@ fn check_metadata(std: &std::fs::Metadata, cap: &cap_std::fs::Metadata) { #[cfg(unix)] { use std::os::unix::fs::MetadataExt; - assert_eq!(std.dev(), cap.dev()); - assert_eq!(std.ino(), cap.ino()); - assert_eq!(std.mode(), cap.mode()); - assert_eq!(std.nlink(), cap.nlink()); - assert_eq!(std.uid(), cap.uid()); - assert_eq!(std.gid(), cap.gid()); - assert_eq!(std.rdev(), cap.rdev()); - assert_eq!(std.size(), cap.size()); + assert_eq!(std.dev(), cap_std::fs::MetadataExt::dev(cap)); + assert_eq!(std.ino(), cap_std::fs::MetadataExt::ino(cap)); + assert_eq!(std.mode(), cap_std::fs::MetadataExt::mode(cap)); + assert_eq!(std.nlink(), cap_std::fs::MetadataExt::nlink(cap)); + assert_eq!(std.uid(), cap_std::fs::MetadataExt::uid(cap)); + assert_eq!(std.gid(), cap_std::fs::MetadataExt::gid(cap)); + assert_eq!(std.rdev(), cap_std::fs::MetadataExt::rdev(cap)); + assert_eq!(std.size(), cap_std::fs::MetadataExt::size(cap)); assert!( ((std.atime() - i64::from(ACCESS_TOLERANCE_SEC)) ..(std.atime() + i64::from(ACCESS_TOLERANCE_SEC))) - .contains(&cap.atime()), + .contains(&cap_std::fs::MetadataExt::atime(cap)), "std atime {}, cap atime {}", std.atime(), - cap.atime() + cap_std::fs::MetadataExt::atime(cap) ); - assert!((0..1_000_000_000).contains(&cap.atime_nsec())); - assert_eq!(std.mtime(), cap.mtime()); - assert_eq!(std.mtime_nsec(), cap.mtime_nsec()); - assert_eq!(std.ctime(), cap.ctime()); - assert_eq!(std.ctime_nsec(), cap.ctime_nsec()); - assert_eq!(std.blksize(), cap.blksize()); - assert_eq!(std.blocks(), cap.blocks()); + assert!((0..1_000_000_000).contains(&cap_std::fs::MetadataExt::atime_nsec(cap))); + assert_eq!(std.mtime(), cap_std::fs::MetadataExt::mtime(cap)); + assert_eq!(std.mtime_nsec(), cap_std::fs::MetadataExt::mtime_nsec(cap)); + assert_eq!(std.ctime(), cap_std::fs::MetadataExt::ctime(cap)); + assert_eq!(std.ctime_nsec(), cap_std::fs::MetadataExt::ctime_nsec(cap)); + assert_eq!(std.blksize(), cap_std::fs::MetadataExt::blksize(cap)); + assert_eq!(std.blocks(), cap_std::fs::MetadataExt::blocks(cap)); } } diff --git a/tests/fs_utf8.rs b/tests/fs_utf8.rs index 8d88e7c0..3af9eb4f 100644 --- a/tests/fs_utf8.rs +++ b/tests/fs_utf8.rs @@ -335,7 +335,7 @@ fn file_test_io_read_write_at() { #[cfg(unix)] #[cfg_attr(any(target_os = "macos", target_os = "ios"), ignore)] fn set_get_unix_permissions() { - use std::os::unix::fs::PermissionsExt; + use cap_std::fs::PermissionsExt; let tmpdir = tmpdir(); let filename = "set_get_unix_permissions";