mod aligned_buf;
#[cfg(feature = "std")]
mod direct_io;
mod mem_fs;
#[cfg(feature = "std")]
mod std_fs;
pub use aligned_buf::AlignedBuf;
#[cfg(all(target_os = "linux", feature = "io-uring"))]
mod io_uring_fs;
#[cfg(all(target_os = "linux", feature = "io-uring-raw"))]
mod io_uring_raw;
pub use mem_fs::MemFs;
#[cfg(feature = "std")]
pub use std_fs::StdFs;
#[cfg(feature = "std")]
pub(crate) use std_fs::is_cross_device;
#[cfg(all(target_os = "linux", feature = "io-uring"))]
pub use io_uring_fs::{IoUringFs, is_io_uring_available};
#[cfg(all(target_os = "linux", feature = "io-uring-raw"))]
pub use io_uring_raw::{
IoUringRaw, IoUringRawFile, IoUringRawFs, O_CREAT, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY,
close_raw, open_raw,
};
use crate::io::{self, Read, Seek, Write};
use crate::path::{Path, PathBuf};
#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, string::String, vec::Vec};
#[expect(
clippy::struct_excessive_bools,
reason = "mirrors std::fs::OpenOptions which uses bool flags for each mode"
)]
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct FsOpenOptions {
pub read: bool,
pub write: bool,
pub create: bool,
pub create_new: bool,
pub truncate: bool,
pub append: bool,
pub direct_io: bool,
}
impl Default for FsOpenOptions {
fn default() -> Self {
Self::new()
}
}
impl FsOpenOptions {
#[must_use]
pub const fn new() -> Self {
Self {
read: false,
write: false,
create: false,
create_new: false,
truncate: false,
append: false,
direct_io: false,
}
}
#[must_use]
pub const fn read(mut self, read: bool) -> Self {
self.read = read;
self
}
#[must_use]
pub const fn write(mut self, write: bool) -> Self {
self.write = write;
self
}
#[must_use]
pub const fn create(mut self, create: bool) -> Self {
self.create = create;
self
}
#[must_use]
pub const fn create_new(mut self, create_new: bool) -> Self {
self.create_new = create_new;
self
}
#[must_use]
pub const fn truncate(mut self, truncate: bool) -> Self {
self.truncate = truncate;
self
}
#[must_use]
pub const fn append(mut self, append: bool) -> Self {
self.append = append;
self
}
#[must_use]
pub const fn direct_io(mut self, direct_io: bool) -> Self {
self.direct_io = direct_io;
self
}
}
#[derive(Clone, Debug)]
pub struct FsMetadata {
pub len: u64,
pub is_dir: bool,
pub is_file: bool,
}
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
#[expect(
clippy::struct_excessive_bools,
reason = "a capability profile is a flat set of independent yes/no FS guarantees; \
each flag is queried on its own, so distinct bools read clearer than \
bitflags or a state machine (same rationale as FsOpenOptions)"
)]
pub struct FsCapabilities {
pub per_block_integrity_on_read: bool,
pub background_scrub: bool,
pub copy_on_write: bool,
pub reflink: bool,
pub native_snapshot: bool,
}
#[derive(Clone, Debug)]
pub struct FsDirEntry {
pub path: PathBuf,
pub file_name: String,
pub is_dir: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum FileHint {
#[default]
Default,
Sequential,
Random,
WriteOnce,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum SyncMode {
#[default]
Normal,
Full,
}
pub trait FsFile: Read + Write + Seek + Send + Sync {
fn sync_all(&self) -> io::Result<()>;
fn sync_data(&self) -> io::Result<()>;
fn sync_all_with(&self, mode: SyncMode) -> io::Result<()> {
let _ = mode;
self.sync_all()
}
fn sync_data_with(&self, mode: SyncMode) -> io::Result<()> {
let _ = mode;
self.sync_data()
}
fn metadata(&self) -> io::Result<FsMetadata>;
fn set_len(&self, size: u64) -> io::Result<()>;
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>;
fn lock_exclusive(&self) -> io::Result<()>;
fn try_lock_exclusive(&self) -> io::Result<bool> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"FsFile::try_lock_exclusive is not implemented for this backend; \
override it (real lock, or Ok(true) for single-process backends) \
or disable the directory lock via Config::with_directory_lock(false)",
))
}
fn hint(&self, _hint: FileHint) -> io::Result<()> {
Ok(())
}
}
pub trait Fs: Send + Sync + 'static {
fn open(&self, path: &Path, opts: &FsOpenOptions) -> io::Result<Box<dyn FsFile>>;
fn create_dir_all(&self, path: &Path) -> io::Result<()>;
fn create_dir(&self, path: &Path) -> io::Result<()> {
let _ = path;
Err(io::Error::new(
io::ErrorKind::Unsupported,
"Fs::create_dir is not implemented for this backend",
))
}
fn read_dir(&self, path: &Path) -> io::Result<Vec<FsDirEntry>>;
fn remove_file(&self, path: &Path) -> io::Result<()>;
fn remove_dir_all(&self, path: &Path) -> io::Result<()>;
fn rename(&self, from: &Path, to: &Path) -> io::Result<()>;
fn metadata(&self, path: &Path) -> io::Result<FsMetadata>;
fn sync_directory(&self, path: &Path) -> io::Result<()>;
fn sync_directory_with(&self, path: &Path, mode: SyncMode) -> io::Result<()> {
let _ = mode;
self.sync_directory(path)
}
fn exists(&self, path: &Path) -> io::Result<bool>;
fn hard_link(&self, src: &Path, dst: &Path) -> io::Result<()> {
let _ = (src, dst);
Err(io::Error::new(
io::ErrorKind::Unsupported,
"Fs::hard_link is not implemented for this backend",
))
}
fn backend_id(&self) -> Option<u64> {
None
}
fn capabilities(&self, path: &Path) -> FsCapabilities {
let _ = path;
FsCapabilities::default()
}
fn try_disable_cow(&self, path: &Path) -> io::Result<()> {
let _ = path;
Ok(())
}
fn reflink_file(&self, src: &Path, dst: &Path) -> io::Result<()> {
copy_file_streamed(self, src, dst)
}
fn truncate_file(&self, path: &Path) -> io::Result<()> {
self.open(path, &FsOpenOptions::new().write(true))?
.set_len(0)
}
fn hard_link_count(&self, path: &Path) -> io::Result<u64> {
let _ = path;
Err(io::Error::new(
io::ErrorKind::Unsupported,
"hard_link_count is not supported by this backend",
))
}
}
pub(crate) fn copy_file_streamed<F: Fs + ?Sized>(fs: &F, src: &Path, dst: &Path) -> io::Result<()> {
let mut src_file = fs.open(src, &FsOpenOptions::new().read(true))?;
let mut dst_file = fs.open(dst, &FsOpenOptions::new().write(true).create_new(true))?;
let mut buf = vec![0u8; 64 * 1024].into_boxed_slice();
let result: io::Result<()> = (|| {
loop {
let n = match src_file.read(&mut buf) {
Ok(0) => break,
Ok(n) => n,
#[cfg(feature = "std")]
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
#[cfg(not(feature = "std"))]
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e.into()),
};
let chunk = buf.get(..n).ok_or_else(|| {
io::Error::other("read returned more bytes than the buffer holds")
})?;
dst_file.write_all(chunk)?;
}
dst_file.sync_all()?;
Ok(())
})();
if let Err(e) = result {
drop(dst_file);
let _ = fs.remove_file(dst);
return Err(e);
}
Ok(())
}