use super::{FileHint, Fs, FsDirEntry, FsFile, FsMetadata, FsOpenOptions, SyncMode};
use crate::io;
use crate::path::Path;
#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, string::String, vec::Vec};
use std::fs::{File, OpenOptions};
#[cfg(target_os = "macos")]
fn normal_fsync(file: &File) -> io::Result<()> {
use std::os::unix::io::AsRawFd;
let rc = unsafe { libc::fsync(file.as_raw_fd()) };
if rc == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error().into())
}
}
#[cfg(not(target_os = "macos"))]
fn normal_fsync(file: &File) -> io::Result<()> {
File::sync_all(file).map_err(io::Error::from)
}
#[derive(Clone, Copy, Debug, Default)]
pub struct StdFs;
impl FsFile for File {
fn sync_all(&self) -> io::Result<()> {
Self::sync_all(self).map_err(io::Error::from)
}
fn sync_data(&self) -> io::Result<()> {
Self::sync_data(self).map_err(io::Error::from)
}
fn sync_all_with(&self, mode: SyncMode) -> io::Result<()> {
match mode {
SyncMode::Full => Self::sync_all(self).map_err(io::Error::from),
SyncMode::Normal => normal_fsync(self),
}
}
fn sync_data_with(&self, mode: SyncMode) -> io::Result<()> {
match mode {
SyncMode::Full => Self::sync_data(self).map_err(io::Error::from),
SyncMode::Normal => normal_fsync(self),
}
}
fn metadata(&self) -> io::Result<FsMetadata> {
let m = Self::metadata(self)?;
Ok(FsMetadata {
len: m.len(),
is_dir: m.is_dir(),
is_file: m.is_file(),
})
}
fn set_len(&self, size: u64) -> io::Result<()> {
Self::set_len(self, size).map_err(io::Error::from)
}
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
let mut filled = 0usize;
while filled < buf.len() {
#[expect(clippy::expect_used, reason = "filled < buf.len() by loop guard")]
let remaining = buf.get_mut(filled..).expect("filled < buf.len()");
let off = offset.checked_add(filled as u64).ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "read offset overflow")
})?;
let n = {
#[cfg(unix)]
{
use std::os::unix::fs::FileExt;
match FileExt::read_at(self, remaining, off) {
Ok(n) => n,
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e.into()),
}
}
#[cfg(windows)]
{
use std::os::windows::fs::FileExt;
match FileExt::seek_read(self, remaining, off) {
Ok(n) => n,
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e.into()),
}
}
#[cfg(not(any(unix, windows)))]
{
let _ = (remaining, off);
return Err(io::Error::new(
io::ErrorKind::Unsupported,
"read_at is not supported on this platform",
));
}
};
if n == 0 {
break; }
filled += n;
}
Ok(filled)
}
fn lock_exclusive(&self) -> io::Result<()> {
sys::lock_exclusive(self).map_err(io::Error::from)
}
fn try_lock_exclusive(&self) -> io::Result<bool> {
sys::try_lock_exclusive(self).map_err(io::Error::from)
}
fn hint(&self, hint: FileHint) -> io::Result<()> {
sys::fadvise(self, hint).map_err(io::Error::from)
}
}
impl Fs for StdFs {
fn open(&self, path: &Path, opts: &FsOpenOptions) -> io::Result<Box<dyn FsFile>> {
let mut builder = OpenOptions::new();
builder
.read(opts.read)
.write(opts.write)
.create(opts.create)
.create_new(opts.create_new)
.truncate(opts.truncate)
.append(opts.append);
#[cfg(feature = "std")]
super::direct_io::apply_direct_io_flag(&mut builder, opts.direct_io);
let file = builder.open(path)?;
Ok(Box::new(file))
}
fn create_dir_all(&self, path: &Path) -> io::Result<()> {
std::fs::create_dir_all(path).map_err(io::Error::from)
}
fn create_dir(&self, path: &Path) -> io::Result<()> {
std::fs::create_dir(path).map_err(io::Error::from)
}
fn read_dir(&self, path: &Path) -> io::Result<Vec<FsDirEntry>> {
std::fs::read_dir(path)?
.map(|res| {
let entry = res?;
let file_type = entry.file_type()?;
let file_name_os = entry.file_name();
let file_name = file_name_os.into_string().map_err(|os| {
#[expect(
clippy::unnecessary_debug_formatting,
reason = "OsString has no Display impl - Debug is required"
)]
let msg = format!("non-UTF-8 filename in directory {}: {os:?}", path.display());
io::Error::new(io::ErrorKind::InvalidData, msg)
})?;
Ok(FsDirEntry {
path: entry.path(),
file_name,
is_dir: file_type.is_dir(),
})
})
.collect()
}
fn remove_file(&self, path: &Path) -> io::Result<()> {
std::fs::remove_file(path).map_err(io::Error::from)
}
fn remove_dir_all(&self, path: &Path) -> io::Result<()> {
std::fs::remove_dir_all(path).map_err(io::Error::from)
}
fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
std::fs::rename(from, to).map_err(io::Error::from)
}
fn metadata(&self, path: &Path) -> io::Result<FsMetadata> {
let m = std::fs::metadata(path)?;
Ok(FsMetadata {
len: m.len(),
is_dir: m.is_dir(),
is_file: m.is_file(),
})
}
#[cfg(unix)]
fn available_space(&self, path: &Path) -> io::Result<u64> {
super::statvfs_available_space(path).map_err(io::Error::from)
}
#[cfg(windows)]
fn available_space(&self, path: &Path) -> io::Result<u64> {
available_space_sys::disk_free_available(path).map_err(io::Error::from)
}
fn sync_directory(&self, path: &Path) -> io::Result<()> {
#[cfg(not(target_os = "windows"))]
{
let dir = File::open(path)?;
if !dir.metadata()?.is_dir() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"sync_directory: path is not a directory",
));
}
dir.sync_all().map_err(io::Error::from)
}
#[cfg(target_os = "windows")]
{
let _ = path;
Ok(())
}
}
fn sync_directory_with(&self, path: &Path, mode: SyncMode) -> io::Result<()> {
#[cfg(not(target_os = "windows"))]
{
let dir = File::open(path)?;
if !dir.metadata()?.is_dir() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"sync_directory: path is not a directory",
));
}
match mode {
SyncMode::Full => dir.sync_all().map_err(io::Error::from),
SyncMode::Normal => normal_fsync(&dir),
}
}
#[cfg(target_os = "windows")]
{
let _ = (path, mode);
Ok(())
}
}
fn exists(&self, path: &Path) -> io::Result<bool> {
path.try_exists().map_err(io::Error::from)
}
fn hard_link(&self, src: &Path, dst: &Path) -> io::Result<()> {
std::fs::hard_link(src, dst).map_err(|e| {
if is_std_cross_device(&e) {
io::Error::from_kind(io::ErrorKind::CrossesDevices)
} else {
e.into()
}
})
}
fn backend_id(&self) -> Option<u64> {
Some(KERNEL_BACKEND_ID)
}
#[cfg(unix)]
fn volume_id(&self, path: &Path) -> Option<u64> {
super::unix_volume_id(path)
}
fn hard_link_count(&self, path: &Path) -> io::Result<u64> {
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
Ok(std::fs::metadata(path)?.nlink())
}
#[cfg(not(unix))]
{
let _ = path;
Err(io::Error::new(
io::ErrorKind::Unsupported,
"hard_link_count requires a Unix target",
))
}
}
#[cfg(target_os = "macos")]
fn capabilities(&self, path: &Path) -> super::FsCapabilities {
macos_caps::capabilities(path)
}
#[cfg(target_os = "macos")]
fn reflink_file(&self, src: &Path, dst: &Path) -> io::Result<()> {
match macos_caps::clonefile(src, dst) {
Ok(()) => Ok(()),
Err(e) if macos_caps::clone_should_fall_back(&e) => {
super::copy_file_streamed(self, src, dst)
}
Err(e) => Err(e.into()),
}
}
#[cfg(target_os = "linux")]
fn capabilities(&self, path: &Path) -> super::FsCapabilities {
linux_caps::capabilities(path)
}
#[cfg(target_os = "linux")]
fn reflink_file(&self, src: &Path, dst: &Path) -> io::Result<()> {
match linux_caps::ficlone(src, dst) {
Ok(()) => Ok(()),
Err(e) if linux_caps::clone_should_fall_back(&e) => {
super::copy_file_streamed(self, src, dst)
}
Err(e) => Err(e.into()),
}
}
#[cfg(target_os = "linux")]
fn try_disable_cow(&self, path: &Path) -> io::Result<()> {
linux_caps::try_disable_cow(path).map_err(io::Error::from)
}
#[cfg(target_os = "linux")]
fn punch_hole(&self, path: &Path, offset: u64, len: u64) -> io::Result<()> {
linux_caps::punch_hole(path, offset, len).map_err(io::Error::from)
}
}
pub const KERNEL_BACKEND_ID: u64 = 0x4b45_524e_454c_5f46;
fn is_std_cross_device(err: &std::io::Error) -> bool {
#[cfg(unix)]
{
const EXDEV: i32 = 18;
if err.raw_os_error() == Some(EXDEV) {
return true;
}
}
matches!(
err.kind(),
std::io::ErrorKind::CrossesDevices | std::io::ErrorKind::Unsupported
)
}
#[expect(
clippy::redundant_pub_crate,
reason = "re-exported crate-wide via fs::mod; pub(crate) communicates the intended scope"
)]
pub(crate) fn is_cross_device(err: &io::Error) -> bool {
matches!(
err.kind(),
io::ErrorKind::CrossesDevices | io::ErrorKind::Unsupported
)
}
#[cfg(unix)]
mod sys {
use super::FileHint;
use std::ffi::c_int;
use std::fs::File;
use std::io;
use std::os::unix::io::AsRawFd;
const LOCK_EX: c_int = 2;
const LOCK_NB: c_int = 4;
unsafe extern "C" {
fn flock(fd: c_int, operation: c_int) -> c_int;
}
pub(super) fn lock_exclusive(file: &File) -> io::Result<()> {
let fd = file.as_raw_fd();
loop {
#[expect(unsafe_code, reason = "flock FFI call with valid fd")]
let ret = unsafe { flock(fd, LOCK_EX) };
if ret == 0 {
return Ok(());
}
let err = std::io::Error::last_os_error();
if err.kind() == std::io::ErrorKind::Interrupted {
continue;
}
return Err(err);
}
}
pub(super) fn try_lock_exclusive(file: &File) -> io::Result<bool> {
let fd = file.as_raw_fd();
loop {
#[expect(unsafe_code, reason = "flock FFI call with valid fd")]
let ret = unsafe { flock(fd, LOCK_EX | LOCK_NB) };
if ret == 0 {
return Ok(true);
}
let err = std::io::Error::last_os_error();
if err.kind() == std::io::ErrorKind::Interrupted {
continue;
}
if err.kind() == std::io::ErrorKind::WouldBlock {
return Ok(false);
}
return Err(err);
}
}
#[cfg(not(target_os = "macos"))]
const POSIX_FADV_NORMAL: c_int = 0;
#[cfg(not(target_os = "macos"))]
const POSIX_FADV_RANDOM: c_int = 1;
#[cfg(not(target_os = "macos"))]
const POSIX_FADV_SEQUENTIAL: c_int = 2;
#[cfg(not(target_os = "macos"))]
const POSIX_FADV_DONTNEED: c_int = 4;
#[cfg(not(target_os = "macos"))]
const EINVAL: c_int = 22;
#[cfg(all(
any(target_os = "linux", target_os = "android"),
target_pointer_width = "32",
any(target_env = "gnu", target_env = "")
))]
unsafe extern "C" {
fn posix_fadvise64(fd: c_int, offset: i64, len: i64, advice: c_int) -> c_int;
}
#[cfg(all(
not(target_os = "macos"),
not(all(
any(target_os = "linux", target_os = "android"),
target_pointer_width = "32",
any(target_env = "gnu", target_env = "")
))
))]
unsafe extern "C" {
fn posix_fadvise(fd: c_int, offset: i64, len: i64, advice: c_int) -> c_int;
}
#[cfg(not(target_os = "macos"))]
pub(super) fn fadvise(file: &File, hint: FileHint) -> io::Result<()> {
let advice = match hint {
FileHint::Default => POSIX_FADV_NORMAL,
FileHint::Sequential => POSIX_FADV_SEQUENTIAL,
FileHint::Random => POSIX_FADV_RANDOM,
FileHint::WriteOnce => POSIX_FADV_DONTNEED,
};
let fd = file.as_raw_fd();
#[expect(unsafe_code, reason = "posix_fadvise FFI call with valid fd")]
let ret = unsafe {
#[cfg(all(
any(target_os = "linux", target_os = "android"),
target_pointer_width = "32",
any(target_env = "gnu", target_env = "")
))]
{
posix_fadvise64(fd, 0, 0, advice)
}
#[cfg(not(all(
any(target_os = "linux", target_os = "android"),
target_pointer_width = "32",
any(target_env = "gnu", target_env = "")
)))]
{
posix_fadvise(fd, 0, 0, advice)
}
};
if ret == 0 || ret == EINVAL {
Ok(())
} else {
Err(std::io::Error::from_raw_os_error(ret))
}
}
#[cfg(target_os = "macos")]
#[expect(
clippy::unnecessary_wraps,
reason = "FsFile::hint signature requires io::Result<()>; macOS branch is a no-op until we wire fcntl(F_RDADVISE)"
)]
pub(super) fn fadvise(_file: &File, _hint: FileHint) -> io::Result<()> {
Ok(())
}
}
#[cfg(windows)]
mod sys {
use super::FileHint;
use std::fs::File;
use std::io;
use std::os::windows::io::AsRawHandle;
#[expect(
clippy::unnecessary_wraps,
reason = "FsFile::hint signature requires io::Result<()>; Windows branch is a no-op until we thread the hint through FsOpenOptions for CreateFile flags"
)]
pub(super) fn fadvise(_file: &File, _hint: FileHint) -> io::Result<()> {
Ok(())
}
pub(super) fn lock_exclusive(file: &File) -> io::Result<()> {
use core::ptr;
const LOCKFILE_EXCLUSIVE_LOCK: u32 = 0x0000_0002;
#[allow(non_snake_case, reason = "FFI name matches Windows API")]
unsafe extern "system" {
fn LockFileEx(
h_file: *mut std::ffi::c_void,
dw_flags: u32,
dw_reserved: u32,
n_number_of_bytes_to_lock_low: u32,
n_number_of_bytes_to_lock_high: u32,
lp_overlapped: *mut Overlapped,
) -> i32;
}
#[repr(C)]
struct Overlapped {
internal: usize,
internal_high: usize,
offset: u32,
offset_high: u32,
h_event: *mut std::ffi::c_void,
}
let handle = file.as_raw_handle();
let mut overlapped = Overlapped {
internal: 0,
internal_high: 0,
offset: 0,
offset_high: 0,
h_event: ptr::null_mut(),
};
#[expect(unsafe_code, reason = "LockFileEx FFI call with valid handle")]
let ret = unsafe {
LockFileEx(
handle as *mut std::ffi::c_void,
LOCKFILE_EXCLUSIVE_LOCK,
0,
u32::MAX,
u32::MAX,
&mut overlapped,
)
};
if ret == 0 {
return Err(std::io::Error::last_os_error().into());
}
Ok(())
}
pub(super) fn try_lock_exclusive(file: &File) -> io::Result<bool> {
use core::ptr;
const LOCKFILE_EXCLUSIVE_LOCK: u32 = 0x0000_0002;
const LOCKFILE_FAIL_IMMEDIATELY: u32 = 0x0000_0001;
const ERROR_LOCK_VIOLATION: i32 = 33;
#[allow(non_snake_case, reason = "FFI name matches Windows API")]
unsafe extern "system" {
fn LockFileEx(
h_file: *mut std::ffi::c_void,
dw_flags: u32,
dw_reserved: u32,
n_number_of_bytes_to_lock_low: u32,
n_number_of_bytes_to_lock_high: u32,
lp_overlapped: *mut Overlapped,
) -> i32;
}
#[repr(C)]
struct Overlapped {
internal: usize,
internal_high: usize,
offset: u32,
offset_high: u32,
h_event: *mut std::ffi::c_void,
}
let handle = file.as_raw_handle();
let mut overlapped = Overlapped {
internal: 0,
internal_high: 0,
offset: 0,
offset_high: 0,
h_event: ptr::null_mut(),
};
#[expect(unsafe_code, reason = "LockFileEx FFI call with valid handle")]
let ret = unsafe {
LockFileEx(
handle as *mut std::ffi::c_void,
LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,
0,
u32::MAX,
u32::MAX,
&mut overlapped,
)
};
if ret == 0 {
let err = std::io::Error::last_os_error();
if err.raw_os_error() == Some(ERROR_LOCK_VIOLATION) {
return Ok(false);
}
return Err(err);
}
Ok(true)
}
}
#[cfg(not(any(unix, windows)))]
mod sys {
use super::FileHint;
use std::fs::File;
use std::io;
pub(super) fn lock_exclusive(_file: &File) -> io::Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"file locking is not supported on this platform",
))
}
pub(super) fn try_lock_exclusive(_file: &File) -> io::Result<bool> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"file locking is not supported on this platform",
))
}
#[expect(
clippy::unnecessary_wraps,
reason = "FsFile::hint signature requires io::Result<()>; unsupported platforms have no fallible path"
)]
pub(super) fn fadvise(_file: &File, _hint: FileHint) -> io::Result<()> {
Ok(())
}
}
#[cfg(target_os = "macos")]
mod macos_caps {
use crate::fs::FsCapabilities;
use core::mem::MaybeUninit;
use std::ffi::{CStr, CString};
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
fn to_cstring(path: &Path) -> io::Result<CString> {
CString::new(path.as_os_str().as_bytes()).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"path contains an interior NUL byte",
)
})
}
fn fs_type_name(path: &Path) -> io::Result<String> {
let c = to_cstring(path)?;
let mut buf = MaybeUninit::<libc::statfs>::uninit();
#[expect(unsafe_code, reason = "statfs FFI to read the filesystem type name")]
let rc = unsafe { libc::statfs(c.as_ptr(), buf.as_mut_ptr()) };
if rc != 0 {
return Err(std::io::Error::last_os_error());
}
#[expect(unsafe_code, reason = "read initialized statfs.f_fstypename")]
let name = unsafe {
let st = buf.assume_init();
CStr::from_ptr(st.f_fstypename.as_ptr())
.to_string_lossy()
.into_owned()
};
Ok(name)
}
pub(super) fn capabilities(path: &Path) -> FsCapabilities {
if matches!(fs_type_name(path).as_deref(), Ok("apfs")) {
FsCapabilities {
copy_on_write: true,
reflink: true,
native_snapshot: true,
..FsCapabilities::default()
}
} else {
FsCapabilities::default()
}
}
pub(super) fn clonefile(src: &Path, dst: &Path) -> io::Result<()> {
let s = to_cstring(src)?;
let d = to_cstring(dst)?;
#[expect(unsafe_code, reason = "clonefile FFI for an O(1) APFS clone")]
let rc = unsafe { libc::clonefile(s.as_ptr(), d.as_ptr(), 0) };
if rc != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(())
}
pub(super) fn clone_should_fall_back(err: &io::Error) -> bool {
const ENOTSUP: i32 = 45;
const EXDEV: i32 = 18;
matches!(err.raw_os_error(), Some(ENOTSUP | EXDEV))
|| matches!(
err.kind(),
io::ErrorKind::Unsupported | io::ErrorKind::CrossesDevices
)
}
}
#[cfg(target_os = "linux")]
mod linux_caps {
use crate::fs::FsCapabilities;
use std::fs::{File, OpenOptions};
use std::io;
use std::os::unix::io::AsRawFd;
use std::path::Path;
#[cfg(target_pointer_width = "64")]
use core::mem::MaybeUninit;
#[cfg(target_pointer_width = "64")]
use std::ffi::CString;
#[cfg(target_pointer_width = "64")]
use std::os::unix::ffi::OsStrExt;
#[cfg(target_pointer_width = "64")]
fn to_cstring(path: &Path) -> io::Result<CString> {
CString::new(path.as_os_str().as_bytes()).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"path contains an interior NUL byte",
)
})
}
#[cfg(target_pointer_width = "64")]
pub(super) fn capabilities(path: &Path) -> FsCapabilities {
const BTRFS: i64 = 0x9123_683E;
const ZFS: i64 = 0x2FC1_2FC1;
const XFS: i64 = 0x5846_5342;
const EXT_FAMILY: i64 = 0x0000_EF53; const TMPFS: i64 = 0x0102_1994;
let Ok(c) = to_cstring(path) else {
return FsCapabilities::default();
};
let mut buf = MaybeUninit::<libc::statfs>::uninit();
#[expect(unsafe_code, reason = "statfs FFI to read f_type")]
let rc = unsafe { libc::statfs(c.as_ptr(), buf.as_mut_ptr()) };
if rc != 0 {
return FsCapabilities::default();
}
#[expect(unsafe_code, reason = "read initialized statfs.f_type")]
#[allow(
clippy::unnecessary_cast,
clippy::cast_possible_wrap,
reason = "f_type is i64 on glibc (no-op cast) and u64 on musl (cast required)"
)]
let f_type = unsafe { buf.assume_init() }.f_type as i64;
match f_type {
BTRFS | ZFS => FsCapabilities {
per_block_integrity_on_read: true,
background_scrub: true,
copy_on_write: true,
reflink: true,
native_snapshot: true,
punch_hole: true,
},
XFS => FsCapabilities {
copy_on_write: true,
reflink: true,
punch_hole: true,
..FsCapabilities::default()
},
EXT_FAMILY | TMPFS => FsCapabilities {
punch_hole: true,
..FsCapabilities::default()
},
_ => FsCapabilities::default(),
}
}
#[cfg(target_pointer_width = "64")]
pub(super) fn punch_hole(path: &Path, offset: u64, len: u64) -> io::Result<()> {
let off = i64::try_from(offset)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "punch offset exceeds i64"))?;
let length = i64::try_from(len)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "punch len exceeds i64"))?;
let f = OpenOptions::new().write(true).open(path)?;
#[expect(
unsafe_code,
reason = "fallocate(PUNCH_HOLE) FFI for in-place extent reclaim"
)]
let rc = unsafe {
libc::fallocate(
f.as_raw_fd(),
libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE,
off,
length,
)
};
if rc != 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
#[cfg(not(target_pointer_width = "64"))]
pub(super) fn punch_hole(_path: &Path, _offset: u64, _len: u64) -> io::Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"punch_hole unsupported on 32-bit",
))
}
#[cfg(not(target_pointer_width = "64"))]
pub(super) fn capabilities(_path: &Path) -> FsCapabilities {
FsCapabilities::default()
}
pub(super) fn ficlone(src: &Path, dst: &Path) -> io::Result<()> {
let src_f = File::open(src)?;
let dst_f = OpenOptions::new().write(true).create_new(true).open(dst)?;
#[expect(unsafe_code, reason = "ioctl(FICLONE) for an O(1) reflink clone")]
let rc = unsafe {
libc::ioctl(
dst_f.as_raw_fd(),
libc::FICLONE as _,
src_f.as_raw_fd(),
)
};
if rc != 0 {
let err = std::io::Error::last_os_error();
drop(dst_f);
let _ = std::fs::remove_file(dst);
return Err(err);
}
Ok(())
}
pub(super) fn clone_should_fall_back(err: &io::Error) -> bool {
const EOPNOTSUPP: i32 = 95;
const EXDEV: i32 = 18;
const EINVAL: i32 = 22;
matches!(err.raw_os_error(), Some(EOPNOTSUPP | EXDEV | EINVAL))
|| matches!(
err.kind(),
io::ErrorKind::Unsupported | io::ErrorKind::CrossesDevices
)
}
#[cfg(any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch64",
target_arch = "s390x"
))]
pub(super) fn try_disable_cow(path: &Path) -> io::Result<()> {
const FS_IOC_GETFLAGS: libc::c_ulong = 0x8008_6601;
const FS_IOC_SETFLAGS: libc::c_ulong = 0x4008_6602;
const FS_NOCOW_FL: libc::c_int = 0x0080_0000;
let f = OpenOptions::new().read(true).write(true).open(path)?;
let mut flags: libc::c_int = 0;
#[expect(unsafe_code, reason = "ioctl(FS_IOC_GETFLAGS) to read inode flags")]
#[allow(
clippy::unnecessary_cast,
clippy::cast_possible_truncation,
reason = "ioctl request is c_ulong on glibc (no-op) but c_int on musl; \
the 32-bit request code's low bits are preserved by truncation"
)]
let rc = unsafe { libc::ioctl(f.as_raw_fd(), FS_IOC_GETFLAGS as _, &raw mut flags) };
if rc != 0 {
return ignore_if_unsupported(std::io::Error::last_os_error());
}
if flags & FS_NOCOW_FL != 0 {
return Ok(()); }
flags |= FS_NOCOW_FL;
#[expect(unsafe_code, reason = "ioctl(FS_IOC_SETFLAGS) to set FS_NOCOW_FL")]
#[allow(
clippy::unnecessary_cast,
clippy::cast_possible_truncation,
reason = "ioctl request is c_ulong on glibc (no-op) but c_int on musl; \
the 32-bit request code's low bits are preserved by truncation"
)]
let rc = unsafe { libc::ioctl(f.as_raw_fd(), FS_IOC_SETFLAGS as _, &raw const flags) };
if rc != 0 {
return ignore_if_unsupported(std::io::Error::last_os_error());
}
Ok(())
}
#[cfg(not(any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch64",
target_arch = "s390x"
)))]
pub(super) fn try_disable_cow(_path: &Path) -> io::Result<()> {
Ok(())
}
#[cfg(any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "riscv32",
target_arch = "riscv64",
target_arch = "loongarch64",
target_arch = "s390x"
))]
fn ignore_if_unsupported(err: io::Error) -> io::Result<()> {
const ENOTTY: i32 = 25;
const EOPNOTSUPP: i32 = 95;
const EINVAL: i32 = 22;
if matches!(err.raw_os_error(), Some(ENOTTY | EOPNOTSUPP | EINVAL))
|| matches!(err.kind(), io::ErrorKind::Unsupported)
{
Ok(())
} else {
Err(err)
}
}
}
#[cfg(windows)]
mod available_space_sys {
use std::io;
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
#[allow(non_snake_case, reason = "Win32 API signature")]
unsafe extern "system" {
fn GetDiskFreeSpaceExW(
lpDirectoryName: *const u16,
lpFreeBytesAvailableToCaller: *mut u64,
lpTotalNumberOfBytes: *mut u64,
lpTotalNumberOfFreeBytes: *mut u64,
) -> i32;
}
pub(super) fn disk_free_available(path: &Path) -> io::Result<u64> {
let mut wide: Vec<u16> = path.as_os_str().encode_wide().collect();
if wide.contains(&0) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"path contains an interior NUL byte",
));
}
wide.push(0);
let mut avail: u64 = 0;
#[expect(unsafe_code, reason = "GetDiskFreeSpaceExW FFI for free space")]
let rc = unsafe {
GetDiskFreeSpaceExW(
wide.as_ptr(),
&mut avail,
core::ptr::null_mut(),
core::ptr::null_mut(),
)
};
if rc == 0 {
return Err(io::Error::last_os_error());
}
Ok(avail)
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::indexing_slicing,
clippy::useless_vec,
reason = "test code"
)]
mod tests;