mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user