Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use custom traits instead of libstd's MetadataExt and PermissionsExt. #343

Merged
merged 6 commits into from
Jan 10, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cap-async-std/src/fs/mod.rs
Original file line number Diff line number Diff line change
@@ -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")]
3 changes: 3 additions & 0 deletions cap-async-std/src/fs_utf8/mod.rs
Original file line number Diff line number Diff line change
@@ -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.
50 changes: 45 additions & 5 deletions cap-fs-ext/build.rs
Original file line number Diff line number Diff line change
@@ -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<T: AsRef<str>>(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()
}
6 changes: 3 additions & 3 deletions cap-fs-ext/src/metadata_ext.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}

50 changes: 45 additions & 5 deletions cap-primitives/build.rs
Original file line number Diff line number Diff line change
@@ -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<T: AsRef<str>>(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()
}
155 changes: 122 additions & 33 deletions cap-primitives/src/fs/metadata.rs
Original file line number Diff line number Diff line change
@@ -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<SystemTime>,
pub(crate) accessed: Option<SystemTime>,
pub(crate) created: Option<SystemTime>,
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<Self> {
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")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems easy for us to somehow miss future extensions to these traits in std, especially slightly more obscure OSes. This seems to be the main downside of this. But eh, in the end if there's some obscure new API and we don't notice, it can just be added.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. All of cap-std already faces this challenge; it's just the nature of what cap-std being what it is.

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<u32>;
/// Returns the value of the `nNumberOfLinks` field of this metadata.
#[cfg(windows_by_handle)]
fn number_of_links(&self) -> Option<u32>;
/// Returns the value of the `nFileIndex{Low,High}` fields of this metadata.
#[cfg(windows_by_handle)]
fn file_index(&self) -> Option<u64>;
}

#[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<u32> {
self.ext.volume_serial_number()
}

#[inline]
#[cfg(windows_by_handle)]
fn number_of_links(&self) -> Option<u32> {
self.ext.number_of_links()
}

#[inline]
#[cfg(windows_by_handle)]
fn file_index(&self) -> Option<u64> {
self.ext.file_index()
}
4 changes: 3 additions & 1 deletion cap-primitives/src/fs/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
37 changes: 26 additions & 11 deletions cap-primitives/src/fs/permissions.rs
Original file line number Diff line number Diff line change
@@ -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),
}
}
}
3 changes: 1 addition & 2 deletions cap-primitives/src/rustix/freebsd/fs/set_permissions_impl.rs
Original file line number Diff line number Diff line change
@@ -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};

4 changes: 2 additions & 2 deletions cap-primitives/src/rustix/freebsd/fs/stat_impl.rs
Original file line number Diff line number Diff line change
@@ -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)?))
}
7 changes: 4 additions & 3 deletions cap-primitives/src/rustix/fs/dir_entry_inner.rs
Original file line number Diff line number Diff line change
@@ -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 {
3 changes: 1 addition & 2 deletions cap-primitives/src/rustix/fs/is_same_file.rs
Original file line number Diff line number Diff line change
@@ -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.
14 changes: 7 additions & 7 deletions cap-primitives/src/rustix/fs/metadata_ext.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#![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};
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<SystemTime> {
}
}

impl rustix::fs::MetadataExt for MetadataExt {
impl crate::fs::MetadataExt for ImplMetadataExt {
#[inline]
fn dev(&self) -> u64 {
self.dev
4 changes: 2 additions & 2 deletions cap-primitives/src/rustix/fs/mod.rs
Original file line number Diff line number Diff line change
@@ -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};
8 changes: 4 additions & 4 deletions cap-primitives/src/rustix/fs/permissions_ext.rs
Original file line number Diff line number Diff line change
@@ -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 }
}
Original file line number Diff line number Diff line change
@@ -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};

6 changes: 3 additions & 3 deletions cap-primitives/src/rustix/fs/stat_unchecked.rs
Original file line number Diff line number Diff line change
@@ -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)?)
}
4 changes: 2 additions & 2 deletions cap-primitives/src/rustix/linux/fs/file_metadata.rs
Original file line number Diff line number Diff line change
@@ -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<Metadata> {
}

// 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)?)
}
8 changes: 4 additions & 4 deletions cap-primitives/src/windows/fs/is_same_file.rs
Original file line number Diff line number Diff line change
@@ -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<bool> {
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<bool> {
#[cfg(windows_by_handle)]
#[allow(dead_code)]
pub(crate) fn is_same_file_metadata(a: &Metadata, b: &Metadata) -> io::Result<bool> {
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())
}

20 changes: 7 additions & 13 deletions cap-primitives/src/windows/fs/metadata_ext.rs
Original file line number Diff line number Diff line change
@@ -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<u32>,
number_of_links: Option<u32>,
file_index: Option<u64>,
}

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<u32> {
self.volume_serial_number
}

#[inline]
#[cfg(windows_by_handle)]
fn number_of_links(&self) -> Option<u32> {
self.number_of_links
}

#[inline]
#[cfg(windows_by_handle)]
fn file_index(&self) -> Option<u64> {
self.file_index
}
50 changes: 45 additions & 5 deletions cap-std/build.rs
Original file line number Diff line number Diff line change
@@ -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<T: AsRef<str>>(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()
}
3 changes: 3 additions & 0 deletions cap-std/src/fs/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
3 changes: 3 additions & 0 deletions cap-std/src/fs_utf8/mod.rs
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 2 additions & 1 deletion cap-tempfile/src/tempfile.rs
Original file line number Diff line number Diff line change
@@ -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();
2 changes: 1 addition & 1 deletion tests/dir-entry-ext.rs
Original file line number Diff line number Diff line change
@@ -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());
2 changes: 1 addition & 1 deletion tests/fs.rs
Original file line number Diff line number Diff line change
@@ -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";
39 changes: 21 additions & 18 deletions tests/fs_additional.rs
Original file line number Diff line number Diff line change
@@ -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));
}
}
2 changes: 1 addition & 1 deletion tests/fs_utf8.rs
Original file line number Diff line number Diff line change
@@ -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";