use std::ffi::CString;
use std::io;
use std::marker::PhantomData;
use std::mem;
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::time::{Duration, SystemTime};
use io_uring::{opcode, types};
use crate::buf::{IoBuf, IoBufMut};
use crate::op::Op;
use crate::runtime;
fn os_err(res: i32) -> io::Error {
io::Error::from_raw_os_error(-res)
}
fn c_path(path: &Path) -> io::Result<CString> {
CString::new(path.as_os_str().as_bytes())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "path contains a NUL byte"))
}
fn cwd() -> types::Fd {
types::Fd(libc::AT_FDCWD)
}
async fn path_op(path: CString, sqe: io_uring::squeue::Entry) -> io::Result<i32> {
let (res, _, _path) = Op::new(sqe, path).await;
if res < 0 {
Err(os_err(res))
} else {
Ok(res)
}
}
#[derive(Clone, Debug)]
pub struct OpenOptions {
read: bool,
write: bool,
append: bool,
truncate: bool,
create: bool,
create_new: bool,
mode: u32,
custom_flags: i32,
}
impl OpenOptions {
pub fn new() -> Self {
Self {
read: false,
write: false,
append: false,
truncate: false,
create: false,
create_new: false,
mode: 0o666,
custom_flags: 0,
}
}
pub fn read(mut self, read: bool) -> Self {
self.read = read;
self
}
pub fn write(mut self, write: bool) -> Self {
self.write = write;
self
}
pub fn append(mut self, append: bool) -> Self {
self.append = append;
self
}
pub fn truncate(mut self, truncate: bool) -> Self {
self.truncate = truncate;
self
}
pub fn create(mut self, create: bool) -> Self {
self.create = create;
self
}
pub fn create_new(mut self, create_new: bool) -> Self {
self.create_new = create_new;
self
}
pub fn mode(mut self, mode: u32) -> Self {
self.mode = mode;
self
}
pub fn custom_flags(mut self, flags: i32) -> Self {
self.custom_flags = flags;
self
}
fn flags(&self) -> io::Result<i32> {
let invalid = || io::Error::from_raw_os_error(libc::EINVAL);
let access = match (self.read, self.write, self.append) {
(true, false, false) => libc::O_RDONLY,
(false, true, false) => libc::O_WRONLY,
(true, true, false) => libc::O_RDWR,
(false, _, true) => libc::O_WRONLY | libc::O_APPEND,
(true, _, true) => libc::O_RDWR | libc::O_APPEND,
(false, false, false) => return Err(invalid()),
};
let writable = self.write || self.append;
if !writable && (self.truncate || self.create || self.create_new) {
return Err(invalid());
}
if self.append && self.truncate && !self.create_new {
return Err(invalid());
}
let creation = match (self.create, self.truncate, self.create_new) {
(_, _, true) => libc::O_CREAT | libc::O_EXCL,
(false, false, false) => 0,
(true, false, false) => libc::O_CREAT,
(false, true, false) => libc::O_TRUNC,
(true, true, false) => libc::O_CREAT | libc::O_TRUNC,
};
Ok(access | creation | libc::O_CLOEXEC | self.custom_flags)
}
pub async fn open<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
let flags = self.flags()?;
let path = c_path(path.as_ref())?;
let sqe = opcode::OpenAt::new(cwd(), path.as_ptr())
.flags(flags)
.mode(self.mode)
.build();
let fd = path_op(path, sqe).await?;
Ok(File::from_fd(fd))
}
}
impl Default for OpenOptions {
fn default() -> Self {
Self::new()
}
}
pub struct File {
fd: RawFd,
_not_send: PhantomData<*const ()>,
}
impl File {
pub async fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
OpenOptions::new().read(true).open(path).await
}
pub async fn create<P: AsRef<Path>>(path: P) -> io::Result<Self> {
OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path)
.await
}
pub fn options() -> OpenOptions {
OpenOptions::new()
}
pub fn from_std(file: std::fs::File) -> Self {
Self::from_fd(file.into_raw_fd())
}
fn from_fd(fd: RawFd) -> Self {
Self {
fd,
_not_send: PhantomData,
}
}
pub async fn read_at<B: IoBufMut>(&self, mut buf: B, offset: u64) -> (io::Result<usize>, B) {
let len = buf.capacity().min(u32::MAX as usize) as u32;
let sqe = opcode::Read::new(types::Fd(self.fd), buf.as_mut_ptr(), len)
.offset(offset)
.build();
let (res, _, mut buf) = Op::new(sqe, buf).await;
if res < 0 {
return (Err(os_err(res)), buf);
}
unsafe { buf.set_init(res as usize) };
(Ok(res as usize), buf)
}
pub async fn write_at<B: IoBuf>(&self, buf: B, offset: u64) -> (io::Result<usize>, B) {
self.write_from(buf, 0, offset).await
}
pub async fn write_all_at<B: IoBuf>(&self, mut buf: B, offset: u64) -> (io::Result<()>, B) {
let mut written = 0;
while written < buf.len() {
let (res, b) = self.write_from(buf, written, offset + written as u64).await;
buf = b;
match res {
Ok(0) => return (Err(io::ErrorKind::WriteZero.into()), buf),
Ok(n) => written += n,
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return (Err(e), buf),
}
}
(Ok(()), buf)
}
async fn write_from<B: IoBuf>(
&self,
buf: B,
skip: usize,
offset: u64,
) -> (io::Result<usize>, B) {
debug_assert!(skip <= buf.len());
let len = (buf.len() - skip).min(u32::MAX as usize) as u32;
let ptr = unsafe { buf.as_ptr().add(skip) };
let sqe = opcode::Write::new(types::Fd(self.fd), ptr, len)
.offset(offset)
.build();
let (res, _, buf) = Op::new(sqe, buf).await;
if res < 0 {
(Err(os_err(res)), buf)
} else {
(Ok(res as usize), buf)
}
}
pub async fn sync_all(&self) -> io::Result<()> {
self.fsync(types::FsyncFlags::empty()).await
}
pub async fn sync_data(&self) -> io::Result<()> {
self.fsync(types::FsyncFlags::DATASYNC).await
}
async fn fsync(&self, flags: types::FsyncFlags) -> io::Result<()> {
let sqe = opcode::Fsync::new(types::Fd(self.fd)).flags(flags).build();
let (res, _, ()) = Op::new(sqe, ()).await;
if res < 0 {
Err(os_err(res))
} else {
Ok(())
}
}
pub async fn metadata(&self) -> io::Result<Metadata> {
statx(types::Fd(self.fd), CString::default(), libc::AT_EMPTY_PATH).await
}
pub fn len(&self) -> io::Result<u64> {
let file = mem::ManuallyDrop::new(unsafe { std::fs::File::from_raw_fd(self.fd) });
Ok(file.metadata()?.len())
}
pub fn is_empty(&self) -> io::Result<bool> {
Ok(self.len()? == 0)
}
pub async fn close(self) -> io::Result<()> {
let fd = self.fd;
mem::forget(self);
close(fd).await
}
}
impl AsRawFd for File {
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
impl Drop for File {
fn drop(&mut self) {
let fd = self.fd;
let closed = runtime::try_with_worker(|w| match w.driver.try_borrow_mut() {
Ok(mut driver) => {
driver.close_fd(fd);
true
}
Err(_) => false,
})
.unwrap_or(false);
if !closed {
unsafe { libc::close(fd) };
}
}
}
impl std::fmt::Debug for File {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("File").field("fd", &self.fd).finish()
}
}
#[cfg(not(giong_tsan))]
async fn close(fd: RawFd) -> io::Result<()> {
let sqe = opcode::Close::new(types::Fd(fd)).build();
let (res, _, ()) = Op::new(sqe, ()).await;
if res < 0 {
Err(os_err(res))
} else {
Ok(())
}
}
#[cfg(giong_tsan)]
async fn close(fd: RawFd) -> io::Result<()> {
if unsafe { libc::close(fd) } < 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct Metadata {
len: u64,
mode: u32,
modified: SystemTime,
}
impl Metadata {
pub fn len(&self) -> u64 {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn is_file(&self) -> bool {
self.mode & libc::S_IFMT == libc::S_IFREG
}
pub fn is_dir(&self) -> bool {
self.mode & libc::S_IFMT == libc::S_IFDIR
}
pub fn is_symlink(&self) -> bool {
self.mode & libc::S_IFMT == libc::S_IFLNK
}
pub fn mode(&self) -> u32 {
self.mode
}
pub fn modified(&self) -> SystemTime {
self.modified
}
}
async fn statx(dir: types::Fd, path: CString, flags: i32) -> io::Result<Metadata> {
let out: Box<libc::statx> = Box::new(unsafe { mem::zeroed() });
let mask = libc::STATX_TYPE | libc::STATX_MODE | libc::STATX_SIZE | libc::STATX_MTIME;
let mut resources = (path, out);
let sqe = opcode::Statx::new(
dir,
resources.0.as_ptr(),
(&mut *resources.1 as *mut libc::statx).cast(),
)
.flags(flags)
.mask(mask)
.build();
let (res, _, (_path, out)) = Op::new(sqe, resources).await;
if res < 0 {
return Err(os_err(res));
}
let since_epoch = Duration::new(out.stx_mtime.tv_sec.unsigned_abs(), out.stx_mtime.tv_nsec);
let modified = if out.stx_mtime.tv_sec >= 0 {
SystemTime::UNIX_EPOCH + since_epoch
} else {
SystemTime::UNIX_EPOCH - Duration::from_secs(out.stx_mtime.tv_sec.unsigned_abs())
+ Duration::from_nanos(out.stx_mtime.tv_nsec.into())
};
Ok(Metadata {
len: out.stx_size,
mode: out.stx_mode.into(),
modified,
})
}
pub async fn metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> {
statx(cwd(), c_path(path.as_ref())?, 0).await
}
pub async fn symlink_metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> {
statx(cwd(), c_path(path.as_ref())?, libc::AT_SYMLINK_NOFOLLOW).await
}
pub async fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
let file = File::open(path).await?;
let hint = file.metadata().await.map_or(0, |m| m.len()) as usize;
let mut data = Vec::with_capacity(hint.saturating_add(1));
let mut chunk = Vec::new();
loop {
if data.len() == data.capacity() {
data.reserve(data.capacity().clamp(4096, 1 << 20));
}
if data.is_empty() {
let (res, buf) = file.read_at(data, 0).await;
data = buf;
if res? == 0 {
return Ok(data);
}
} else {
chunk.clear();
chunk.reserve((data.capacity() - data.len()).min(1 << 20));
let (res, buf) = file.read_at(chunk, data.len() as u64).await;
chunk = buf;
if res? == 0 {
return Ok(data);
}
data.extend_from_slice(&chunk);
}
}
}
pub async fn write<P: AsRef<Path>, B: IoBuf>(path: P, data: B) -> io::Result<()> {
let file = File::create(path).await?;
file.write_all_at(data, 0).await.0?;
file.close().await
}
pub async fn remove_file<P: AsRef<Path>>(path: P) -> io::Result<()> {
let path = c_path(path.as_ref())?;
let sqe = opcode::UnlinkAt::new(cwd(), path.as_ptr()).build();
path_op(path, sqe).await.map(drop)
}
pub async fn remove_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
let path = c_path(path.as_ref())?;
let sqe = opcode::UnlinkAt::new(cwd(), path.as_ptr())
.flags(libc::AT_REMOVEDIR)
.build();
path_op(path, sqe).await.map(drop)
}
pub async fn create_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
let path = c_path(path.as_ref())?;
let sqe = opcode::MkDirAt::new(cwd(), path.as_ptr())
.mode(0o777)
.build();
path_op(path, sqe).await.map(drop)
}
pub async fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
let paths = (c_path(from.as_ref())?, c_path(to.as_ref())?);
let sqe = opcode::RenameAt::new(cwd(), paths.0.as_ptr(), cwd(), paths.1.as_ptr()).build();
let (res, _, _paths) = Op::new(sqe, paths).await;
if res < 0 {
Err(os_err(res))
} else {
Ok(())
}
}