Improve no-follow filesystem behavior across platforms (#39666)

## What changed

- Use `statx` for no-follow metadata on Linux so `created_at_ms` includes the birth time when the filesystem provides it, with a fallback for unavailable or blocked `statx` calls.
- Mark files and directories for deletion explicitly on Windows after opening them without traversing reparse points.
- Cover create, write, metadata, remove, and link-rejection behavior for local and remote filesystems, including sandboxed execution on Linux and Windows.

## Testing

- Add Linux coverage for preserving birth time in no-follow metadata.
- Add cross-platform coverage for sandboxed no-follow operations and removal of files and empty directories.

GitOrigin-RevId: 1bc531669839bf5d033aa4a215220ed1cc5f63d5
This commit is contained in:
pakrym-oai
2026-08-20 08:32:11 +00:00
committed by copyberry
parent fec2dccfcf
commit 2584e88cad
6 changed files with 448 additions and 185 deletions
+74 -8
View File
@@ -3,11 +3,17 @@ use rustix::fs::AtFlags;
use rustix::fs::Mode;
use rustix::fs::OFlags;
use rustix::fs::Stat;
#[cfg(target_os = "linux")]
use rustix::fs::Statx;
#[cfg(target_os = "linux")]
use rustix::fs::StatxFlags;
use rustix::fs::fstat;
use rustix::fs::mkdirat;
use rustix::fs::open;
use rustix::fs::openat;
use rustix::fs::statat;
#[cfg(target_os = "linux")]
use rustix::fs::statx;
use rustix::fs::unlinkat;
use std::ffi::OsString;
use std::io;
@@ -141,16 +147,76 @@ pub(super) async fn write_file(path: PathBuf, contents: Vec<u8>) -> io::Result<(
fn metadata_sync(path: &Path) -> io::Result<FileMetadata> {
let components = components(path)?;
let metadata = if components.is_empty() {
fstat(root()?).map_err(io::Error::from)?
#[cfg(target_os = "linux")]
{
let (parent, leaf, flags) = if components.is_empty() {
(root()?, OsString::new(), AtFlags::EMPTY_PATH)
} else {
let (parent, leaf) = parent(path)?;
(parent, leaf, AtFlags::SYMLINK_NOFOLLOW)
};
let metadata = statx(
&parent,
&leaf,
flags,
StatxFlags::BASIC_STATS | StatxFlags::BTIME,
);
match metadata {
Ok(metadata) => file_metadata_linux(metadata),
Err(error) if error == rustix::io::Errno::NOSYS || error == rustix::io::Errno::PERM => {
let metadata = if components.is_empty() {
fstat(&parent)
} else {
statat(&parent, &leaf, AtFlags::SYMLINK_NOFOLLOW)
}
.map_err(io::Error::from)?;
file_metadata(metadata, /*created_at_ms*/ 0)
}
Err(error) => Err(io::Error::from(error)),
}
}
#[cfg(not(target_os = "linux"))]
if components.is_empty() {
let root = root()?;
let metadata = fstat(&root).map_err(io::Error::from)?;
let created_at_ms = created_at_ms(&metadata);
file_metadata(metadata, created_at_ms)
} else {
let (parent, leaf) = parent(path)?;
statat(&parent, leaf, AtFlags::SYMLINK_NOFOLLOW).map_err(io::Error::from)?
};
file_metadata(metadata)
let metadata =
statat(&parent, &leaf, AtFlags::SYMLINK_NOFOLLOW).map_err(io::Error::from)?;
let created_at_ms = created_at_ms(&metadata);
file_metadata(metadata, created_at_ms)
}
}
fn file_metadata(metadata: Stat) -> io::Result<FileMetadata> {
#[cfg(target_os = "linux")]
fn file_metadata_linux(metadata: Statx) -> io::Result<FileMetadata> {
let kind = u32::from(metadata.stx_mode) & libc::S_IFMT;
if kind == libc::S_IFLNK {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"path contains a symbolic link",
));
}
let created_at_ms = if metadata.stx_mask & StatxFlags::BTIME.bits() == 0 {
0
} else {
unix_time_ms(metadata.stx_btime.tv_sec, metadata.stx_btime.tv_nsec)
};
Ok(FileMetadata {
is_directory: kind == libc::S_IFDIR,
is_file: kind == libc::S_IFREG,
is_symlink: false,
size: metadata.stx_size,
created_at_ms,
modified_at_ms: unix_time_ms(metadata.stx_mtime.tv_sec, metadata.stx_mtime.tv_nsec),
})
}
fn file_metadata(metadata: Stat, created_at_ms: i64) -> io::Result<FileMetadata> {
let kind = metadata.st_mode & libc::S_IFMT;
if kind == libc::S_IFLNK {
return Err(io::Error::new(
@@ -163,7 +229,7 @@ fn file_metadata(metadata: Stat) -> io::Result<FileMetadata> {
is_file: kind == libc::S_IFREG,
is_symlink: false,
size: u64::try_from(metadata.st_size).unwrap_or(0),
created_at_ms: created_at_ms(&metadata),
created_at_ms,
modified_at_ms: unix_time_ms(metadata.st_mtime, metadata.st_mtime_nsec),
})
}
@@ -173,7 +239,7 @@ fn created_at_ms(metadata: &Stat) -> i64 {
unix_time_ms(metadata.st_birthtime, metadata.st_birthtime_nsec)
}
#[cfg(not(target_vendor = "apple"))]
#[cfg(not(any(target_vendor = "apple", target_os = "linux")))]
fn created_at_ms(_metadata: &Stat) -> i64 {
0
}
+20 -7
View File
@@ -5,6 +5,7 @@ use std::io;
use std::io::Write;
use std::mem::size_of;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::io::AsRawHandle;
use std::os::windows::io::FromRawHandle;
use std::os::windows::io::OwnedHandle;
use std::os::windows::io::RawHandle;
@@ -22,12 +23,15 @@ use windows_sys::Win32::Security::SECURITY_QUALITY_OF_SERVICE;
use windows_sys::Win32::Security::SecurityIdentification;
use windows_sys::Win32::Storage::FileSystem::DELETE;
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_NORMAL;
use windows_sys::Win32::Storage::FileSystem::FILE_DISPOSITION_INFO;
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_READ;
use windows_sys::Win32::Storage::FileSystem::FILE_READ_ATTRIBUTES;
use windows_sys::Win32::Storage::FileSystem::FILE_SHARE_DELETE;
use windows_sys::Win32::Storage::FileSystem::FILE_SHARE_READ;
use windows_sys::Win32::Storage::FileSystem::FILE_SHARE_WRITE;
use windows_sys::Win32::Storage::FileSystem::FILE_WRITE_DATA;
use windows_sys::Win32::Storage::FileSystem::FileDispositionInfo;
use windows_sys::Win32::Storage::FileSystem::SetFileInformationByHandle;
use windows_sys::Win32::System::IO::IO_STATUS_BLOCK;
use windows_sys::Win32::System::IO::IO_STATUS_BLOCK_0;
use windows_sys::Win32::System::Kernel::OBJ_CASE_INSENSITIVE;
@@ -36,7 +40,6 @@ use windows_sys::Win32::System::Kernel::OBJ_DONT_REPARSE;
const FILE_DIRECTORY_FILE: u32 = 0x0000_0001;
const FILE_SYNCHRONOUS_IO_NONALERT: u32 = 0x0000_0020;
const FILE_NON_DIRECTORY_FILE: u32 = 0x0000_0040;
const FILE_DELETE_ON_CLOSE: u32 = 0x0000_1000;
const FILE_OPEN: u32 = 1;
const FILE_CREATE: u32 = 2;
const FILE_OPEN_IF: u32 = 3;
@@ -300,12 +303,22 @@ fn remove_sync(path: &Path, recursive: bool, force: bool) -> io::Result<()> {
} else {
FILE_NON_DIRECTORY_FILE
};
open_handle(
path,
DELETE,
FILE_OPEN,
create_options | FILE_DELETE_ON_CLOSE,
)?;
let handle = open_handle(path, DELETE, FILE_OPEN, create_options)?;
let disposition = FILE_DISPOSITION_INFO {
DeleteFile: BOOLEAN_TRUE,
};
let result = unsafe {
SetFileInformationByHandle(
handle.as_raw_handle() as HANDLE,
FileDispositionInfo,
(&raw const disposition).cast(),
size_of::<FILE_DISPOSITION_INFO>() as u32,
)
};
if result == 0 {
return Err(io::Error::last_os_error());
}
drop(handle);
Ok(())
}