use crate::modules::input::Token;
use crate::{
RuntimeError,
constants::{FILE_CHUNK, INLINE_PAYLOAD},
executor,
futures::{
file::{
dir_entry::DirEntry,
metadata::{FileKind, Metadata},
},
task::sealed,
task::{Nothing, Task},
},
modules::{c_path::c_path, fd::Fd, int_check::IntCheck, retried::retried},
};
use std::{
ffi::{CStr, CString, OsStr},
io::Error,
mem,
os::unix::ffi::OsStrExt,
path::{Path, PathBuf},
ptr, slice,
sync::Arc,
};
const _: () = assert!(mem::size_of::<Result<Vec<u8>, RuntimeError>>() <= INLINE_PAYLOAD);
const _: () = assert!(mem::size_of::<Result<usize, RuntimeError>>() <= INLINE_PAYLOAD);
const _: () = assert!(mem::size_of::<Result<Metadata, RuntimeError>>() <= INLINE_PAYLOAD);
const _: () = assert!(mem::size_of::<Result<Vec<DirEntry>, RuntimeError>>() <= INLINE_PAYLOAD);
const _: () = assert!(mem::size_of::<Result<(), RuntimeError>>() <= INLINE_PAYLOAD);
const _: () = assert!(mem::size_of::<Result<PathBuf, RuntimeError>>() <= INLINE_PAYLOAD);
const _: () = assert!(mem::size_of::<Result<u64, RuntimeError>>() <= INLINE_PAYLOAD);
#[derive(Debug, Clone, Copy)]
enum Extent {
Whole,
Range { offset: u64, len: usize },
}
#[derive(Debug, Clone, Copy)]
enum WriteMode {
Truncate,
Append,
At(u64),
}
#[derive(Debug, Clone, Copy)]
enum PathOp {
Remove,
RemoveDir,
CreateDir,
Rename,
Symlink,
HardLink,
SetPermissions(u32),
SetLen(u64),
CreateDirAll,
RemoveDirAll,
}
#[derive(Debug, Clone, Copy)]
enum Resolve {
ReadLink,
Canonical,
}
struct Dir(*mut libc::DIR);
impl Drop for Dir {
fn drop(&mut self) {
unsafe { libc::closedir(self.0) };
}
}
#[derive(Debug, Clone)]
#[must_use = "a task does nothing until it is run or spawned"]
pub struct ReadTask {
path: Option<CString>,
extent: Extent,
}
#[derive(Debug, Clone)]
#[must_use = "a task does nothing until it is run or spawned"]
pub struct WriteTask {
path: Option<CString>,
data: Arc<[u8]>,
mode: WriteMode,
}
#[derive(Debug, Clone)]
#[must_use = "a task does nothing until it is run or spawned"]
pub struct MetadataTask {
path: Option<CString>,
follow: bool,
}
#[derive(Debug, Clone)]
#[must_use = "a task does nothing until it is run or spawned"]
pub struct ReadDirTask {
path: Option<CString>,
}
#[derive(Debug, Clone)]
#[must_use = "a task does nothing until it is run or spawned"]
pub struct PathTask {
path: Option<CString>,
other: Option<CString>,
op: PathOp,
}
#[derive(Debug, Clone)]
#[must_use = "a task does nothing until it is run or spawned"]
pub struct PathBufTask {
path: Option<CString>,
resolve: Resolve,
}
#[derive(Debug, Clone)]
#[must_use = "a task does nothing until it is run or spawned"]
pub struct CopyTask {
from: Option<CString>,
to: Option<CString>,
}
impl PathBufTask {
pub(crate) fn read_link(path: impl AsRef<Path>) -> Self {
Self {
path: c_path(path),
resolve: Resolve::ReadLink,
}
}
pub(crate) fn canonical(path: impl AsRef<Path>) -> Self {
Self {
path: c_path(path),
resolve: Resolve::Canonical,
}
}
}
impl CopyTask {
pub(crate) fn new(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Self {
Self {
from: c_path(from),
to: c_path(to),
}
}
}
impl ReadTask {
pub(crate) fn whole(path: impl AsRef<Path>) -> Self {
Self {
path: c_path(path),
extent: Extent::Whole,
}
}
pub(crate) fn range(path: impl AsRef<Path>, offset: u64, len: usize) -> Self {
Self {
path: c_path(path),
extent: Extent::Range { offset, len },
}
}
}
impl WriteTask {
pub(crate) fn truncate(path: impl AsRef<Path>, data: impl Into<Arc<[u8]>>) -> Self {
Self {
path: c_path(path),
data: data.into(),
mode: WriteMode::Truncate,
}
}
pub(crate) fn append(path: impl AsRef<Path>, data: impl Into<Arc<[u8]>>) -> Self {
Self {
path: c_path(path),
data: data.into(),
mode: WriteMode::Append,
}
}
pub(crate) fn at(path: impl AsRef<Path>, offset: u64, data: impl Into<Arc<[u8]>>) -> Self {
Self {
path: c_path(path),
data: data.into(),
mode: WriteMode::At(offset),
}
}
}
impl MetadataTask {
pub(crate) fn following(path: impl AsRef<Path>) -> Self {
Self {
path: c_path(path),
follow: true,
}
}
pub(crate) fn link(path: impl AsRef<Path>) -> Self {
Self {
path: c_path(path),
follow: false,
}
}
}
impl ReadDirTask {
pub(crate) fn new(path: impl AsRef<Path>) -> Self {
Self { path: c_path(path) }
}
}
impl PathTask {
pub(crate) fn remove(path: impl AsRef<Path>) -> Self {
Self::one(path, PathOp::Remove)
}
pub(crate) fn remove_dir(path: impl AsRef<Path>) -> Self {
Self::one(path, PathOp::RemoveDir)
}
pub(crate) fn create_dir(path: impl AsRef<Path>) -> Self {
Self::one(path, PathOp::CreateDir)
}
pub(crate) fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Self {
Self {
path: c_path(from),
other: c_path(to),
op: PathOp::Rename,
}
}
pub(crate) fn symlink(target: impl AsRef<Path>, link: impl AsRef<Path>) -> Self {
Self {
path: c_path(link),
other: c_path(target),
op: PathOp::Symlink,
}
}
pub(crate) fn hard_link(existing: impl AsRef<Path>, new: impl AsRef<Path>) -> Self {
Self {
path: c_path(existing),
other: c_path(new),
op: PathOp::HardLink,
}
}
pub(crate) fn set_permissions(path: impl AsRef<Path>, mode: u32) -> Self {
Self::one(path, PathOp::SetPermissions(mode))
}
pub(crate) fn set_len(path: impl AsRef<Path>, len: u64) -> Self {
Self::one(path, PathOp::SetLen(len))
}
pub(crate) fn create_dir_all(path: impl AsRef<Path>) -> Self {
Self::one(path, PathOp::CreateDirAll)
}
pub(crate) fn remove_dir_all(path: impl AsRef<Path>) -> Self {
Self::one(path, PathOp::RemoveDirAll)
}
fn one(path: impl AsRef<Path>, op: PathOp) -> Self {
Self {
path: c_path(path),
other: None,
op,
}
}
}
impl sealed::Sealed for ReadTask {}
impl sealed::Sealed for WriteTask {}
impl sealed::Sealed for MetadataTask {}
impl sealed::Sealed for ReadDirTask {}
impl sealed::Sealed for PathTask {}
impl sealed::Sealed for PathBufTask {}
impl sealed::Sealed for CopyTask {}
impl Task for ReadTask {
type Output = Result<Vec<u8>, RuntimeError>;
type Input = Nothing;
fn execute(&self, _token: Token, _reactor_id: i32, _task_id: usize) -> Self::Output {
let path = self.path.as_ref().ok_or(RuntimeError::BadPath)?;
let fd = open_at(path, libc::O_RDONLY, 0)?;
if directory(&fd)? {
return Err(RuntimeError::CheckError(Some(libc::EISDIR)));
}
match self.extent {
Extent::Whole => read_whole(&fd),
Extent::Range { offset, len } => read_range(&fd, offset, len),
}
}
fn blocking(&self, _token: Token) -> bool {
true
}
}
impl Task for WriteTask {
type Output = Result<usize, RuntimeError>;
type Input = Nothing;
fn execute(&self, _token: Token, _reactor_id: i32, _task_id: usize) -> Self::Output {
let path = self.path.as_ref().ok_or(RuntimeError::BadPath)?;
let flags = match self.mode {
WriteMode::Truncate => libc::O_WRONLY | libc::O_CREAT | libc::O_TRUNC,
WriteMode::Append => libc::O_WRONLY | libc::O_CREAT | libc::O_APPEND,
WriteMode::At(_) => libc::O_WRONLY | libc::O_CREAT,
};
let fd = open_at(path, flags, 0o666)?;
let at = match self.mode {
WriteMode::At(offset) => Some(offset),
_ => None,
};
write_all(&fd, &self.data, at)
}
fn blocking(&self, _token: Token) -> bool {
true
}
}
impl Task for MetadataTask {
type Output = Result<Metadata, RuntimeError>;
type Input = Nothing;
fn execute(&self, _token: Token, _reactor_id: i32, _task_id: usize) -> Self::Output {
let path = self.path.as_ref().ok_or(RuntimeError::BadPath)?;
let mut raw: libc::stat = unsafe { mem::zeroed() };
retried(|| match self.follow {
true => unsafe { libc::stat(path.as_ptr(), &mut raw) },
false => unsafe { libc::lstat(path.as_ptr(), &mut raw) },
})?;
Ok(Metadata::from_stat(&raw))
}
fn blocking(&self, _token: Token) -> bool {
true
}
}
impl Task for ReadDirTask {
type Output = Result<Vec<DirEntry>, RuntimeError>;
type Input = Nothing;
fn execute(&self, _token: Token, _reactor_id: i32, _task_id: usize) -> Self::Output {
let path = self.path.as_ref().ok_or(RuntimeError::BadPath)?;
let raw = unsafe { libc::opendir(path.as_ptr()) };
if raw.is_null() {
return Err(RuntimeError::CheckError(
Error::last_os_error().raw_os_error(),
));
}
let dir = Dir(raw);
let parent = PathBuf::from(OsStr::from_bytes(path.as_bytes()));
let mut found = Vec::new();
loop {
if executor::cancelled() {
return Err(RuntimeError::Cancelled);
}
unsafe { *libc::__error() = 0 };
let entry = unsafe { libc::readdir(dir.0) };
if entry.is_null() {
let failed = Error::last_os_error().raw_os_error().unwrap_or(0);
if failed != 0 {
return Err(RuntimeError::CheckError(Some(failed)));
}
break;
}
let name = unsafe {
slice::from_raw_parts(
(*entry).d_name.as_ptr().cast::<u8>(),
(*entry).d_namlen as usize,
)
};
if name == b"." || name == b".." {
continue;
}
let path = parent.join(OsStr::from_bytes(name));
let kind = match unsafe { (*entry).d_type } {
libc::DT_REG => FileKind::File,
libc::DT_DIR => FileKind::Dir,
libc::DT_LNK => FileKind::Symlink,
libc::DT_UNKNOWN => kind_of(&path)?,
_ => FileKind::Other,
};
found.push(DirEntry::new(path, kind));
}
Ok(found)
}
fn blocking(&self, _token: Token) -> bool {
true
}
}
impl Task for PathTask {
type Output = Result<(), RuntimeError>;
type Input = Nothing;
fn execute(&self, _token: Token, _reactor_id: i32, _task_id: usize) -> Self::Output {
let path = self.path.as_ref().ok_or(RuntimeError::BadPath)?;
let other = match self.op {
PathOp::Rename | PathOp::Symlink | PathOp::HardLink => {
Some(self.other.as_ref().ok_or(RuntimeError::BadPath)?)
}
_ => None,
};
match self.op {
PathOp::CreateDirAll => return create_dir_all(path),
PathOp::RemoveDirAll => return remove_dir_all(path),
_ => {}
}
let mode = match self.op {
PathOp::SetPermissions(mode) if mode <= 0o7777 => mode as libc::mode_t,
PathOp::SetPermissions(_) => return Err(RuntimeError::BadArgument),
_ => 0,
};
let len = match self.op {
PathOp::SetLen(len) => seek_to(len)?,
_ => 0,
};
retried(|| match (self.op, other) {
(PathOp::Remove, _) => unsafe { libc::unlink(path.as_ptr()) },
(PathOp::RemoveDir, _) => unsafe { libc::rmdir(path.as_ptr()) },
(PathOp::CreateDir, _) => unsafe { libc::mkdir(path.as_ptr(), 0o777) },
(PathOp::SetPermissions(_), _) => unsafe { libc::chmod(path.as_ptr(), mode) },
(PathOp::SetLen(_), _) => unsafe { libc::truncate(path.as_ptr(), len) },
(PathOp::Rename, Some(to)) => unsafe { libc::rename(path.as_ptr(), to.as_ptr()) },
(PathOp::Symlink, Some(target)) => unsafe {
libc::symlink(target.as_ptr(), path.as_ptr())
},
(PathOp::HardLink, Some(new)) => unsafe { libc::link(path.as_ptr(), new.as_ptr()) },
_ => -1,
})?;
Ok(())
}
fn blocking(&self, _token: Token) -> bool {
true
}
}
impl Task for PathBufTask {
type Output = Result<PathBuf, RuntimeError>;
type Input = Nothing;
fn execute(&self, _token: Token, _reactor_id: i32, _task_id: usize) -> Self::Output {
let path = self.path.as_ref().ok_or(RuntimeError::BadPath)?;
match self.resolve {
Resolve::ReadLink => read_link(path),
Resolve::Canonical => canonical(path),
}
}
fn blocking(&self, _token: Token) -> bool {
true
}
}
impl Task for CopyTask {
type Output = Result<u64, RuntimeError>;
type Input = Nothing;
fn execute(&self, _token: Token, _reactor_id: i32, _task_id: usize) -> Self::Output {
let from = self.from.as_ref().ok_or(RuntimeError::BadPath)?;
let to = self.to.as_ref().ok_or(RuntimeError::BadPath)?;
copy(from, to)
}
fn blocking(&self, _token: Token) -> bool {
true
}
}
fn kind_of(path: &Path) -> Result<FileKind, RuntimeError> {
let path = c_path(path).ok_or(RuntimeError::BadPath)?;
let mut raw: libc::stat = unsafe { mem::zeroed() };
retried(|| unsafe { libc::lstat(path.as_ptr(), &mut raw) })?;
Ok(FileKind::from_mode(raw.st_mode))
}
fn read_link(path: &CStr) -> Result<PathBuf, RuntimeError> {
let mut buffer = vec![0u8; libc::PATH_MAX as usize];
loop {
let read = unsafe {
libc::readlink(
path.as_ptr(),
buffer.as_mut_ptr().cast::<libc::c_char>(),
buffer.len(),
)
}
.check();
match read {
Ok(read) if read as usize == buffer.len() => buffer.resize(buffer.len() * 2, 0),
Ok(read) => {
buffer.truncate(read as usize);
return Ok(PathBuf::from(OsStr::from_bytes(&buffer)));
}
Err(RuntimeError::CheckError(Some(libc::EINTR))) => {}
Err(error) => return Err(error),
}
}
}
fn canonical(path: &CStr) -> Result<PathBuf, RuntimeError> {
let resolved = unsafe { libc::realpath(path.as_ptr(), ptr::null_mut()) };
if resolved.is_null() {
return Err(RuntimeError::CheckError(
Error::last_os_error().raw_os_error(),
));
}
let found = PathBuf::from(OsStr::from_bytes(
unsafe { CStr::from_ptr(resolved) }.to_bytes(),
));
unsafe { libc::free(resolved.cast::<libc::c_void>()) };
Ok(found)
}
fn create_dir_all(path: &CStr) -> Result<(), RuntimeError> {
let bytes = path.to_bytes();
if bytes.is_empty() {
return Err(RuntimeError::CheckError(Some(libc::ENOENT)));
}
let ends = bytes
.iter()
.enumerate()
.skip(1)
.filter(|(at, byte)| **byte == b'/' && bytes[at - 1] != b'/')
.map(|(at, _)| at)
.chain([bytes.len()]);
for end in ends {
if executor::cancelled() {
return Err(RuntimeError::Cancelled);
}
let prefix = CString::new(&bytes[..end]).map_err(|_| RuntimeError::BadPath)?;
match retried(|| unsafe { libc::mkdir(prefix.as_ptr(), 0o777) }) {
Ok(_) => {}
Err(RuntimeError::CheckError(Some(libc::EEXIST))) => {
let mut raw: libc::stat = unsafe { mem::zeroed() };
retried(|| unsafe { libc::stat(prefix.as_ptr(), &mut raw) })?;
if raw.st_mode & libc::S_IFMT != libc::S_IFDIR {
return Err(RuntimeError::CheckError(Some(libc::ENOTDIR)));
}
}
Err(error) => return Err(error),
}
}
Ok(())
}
struct Emptying {
fd: Fd,
name: CString,
left: Vec<CString>,
}
fn remove_dir_all(path: &CStr) -> Result<(), RuntimeError> {
let mut raw: libc::stat = unsafe { mem::zeroed() };
retried(|| unsafe { libc::lstat(path.as_ptr(), &mut raw) })?;
if raw.st_mode & libc::S_IFMT != libc::S_IFDIR {
retried(|| unsafe { libc::unlink(path.as_ptr()) })?;
return Ok(());
}
let root = open_dir(libc::AT_FDCWD, path)?;
let left = entries(&root)?;
let mut stack = vec![Emptying {
fd: root,
name: CString::default(),
left,
}];
while let Some(top) = stack.last_mut() {
if executor::cancelled() {
return Err(RuntimeError::Cancelled);
}
let Some(name) = top.left.pop() else {
let done = stack.pop();
if let (Some(parent), Some(done)) = (stack.last(), done) {
retried(|| unsafe {
libc::unlinkat(parent.fd.raw(), done.name.as_ptr(), libc::AT_REMOVEDIR)
})?;
}
continue;
};
let dir = top.fd.raw();
let mut raw: libc::stat = unsafe { mem::zeroed() };
retried(|| unsafe {
libc::fstatat(dir, name.as_ptr(), &mut raw, libc::AT_SYMLINK_NOFOLLOW)
})?;
if raw.st_mode & libc::S_IFMT != libc::S_IFDIR {
retried(|| unsafe { libc::unlinkat(dir, name.as_ptr(), 0) })?;
continue;
}
let fd = open_dir(dir, &name)?;
let left = entries(&fd)?;
stack.push(Emptying { fd, name, left });
}
retried(|| unsafe { libc::rmdir(path.as_ptr()) })?;
Ok(())
}
fn open_dir(at: libc::c_int, name: &CStr) -> Result<Fd, RuntimeError> {
let flags = libc::O_RDONLY | libc::O_DIRECTORY | libc::O_NOFOLLOW | libc::O_CLOEXEC;
loop {
match unsafe { libc::openat(at, name.as_ptr(), flags) }.check() {
Ok(fd) => return Ok(Fd::new(fd)),
Err(RuntimeError::CheckError(Some(libc::EINTR))) => {}
Err(error) => return Err(error),
}
}
}
fn entries(fd: &Fd) -> Result<Vec<CString>, RuntimeError> {
let copy = retried(|| unsafe { libc::fcntl(fd.raw(), libc::F_DUPFD_CLOEXEC, 0) })?;
let raw = unsafe { libc::fdopendir(copy) };
if raw.is_null() {
let failed = Error::last_os_error().raw_os_error();
unsafe { libc::close(copy) };
return Err(RuntimeError::CheckError(failed));
}
unsafe { libc::rewinddir(raw) };
let dir = Dir(raw);
let mut found = Vec::new();
loop {
unsafe { *libc::__error() = 0 };
let entry = unsafe { libc::readdir(dir.0) };
if entry.is_null() {
let failed = Error::last_os_error().raw_os_error().unwrap_or(0);
if failed != 0 {
return Err(RuntimeError::CheckError(Some(failed)));
}
return Ok(found);
}
let name = unsafe {
slice::from_raw_parts(
(*entry).d_name.as_ptr().cast::<u8>(),
(*entry).d_namlen as usize,
)
};
if name == b"." || name == b".." {
continue;
}
found.push(CString::new(name).map_err(|_| RuntimeError::BadPath)?);
}
}
fn copy(from: &CStr, to: &CStr) -> Result<u64, RuntimeError> {
let source = open_at(from, libc::O_RDONLY, 0)?;
if directory(&source)? {
return Err(RuntimeError::CheckError(Some(libc::EISDIR)));
}
let size = hint(&source)? as u64;
if retried(|| unsafe { libc::fclonefileat(source.raw(), libc::AT_FDCWD, to.as_ptr(), 0) })
.is_ok()
{
return Ok(size);
}
let target = open_at(to, libc::O_WRONLY | libc::O_CREAT | libc::O_TRUNC, 0o666)?;
let state = unsafe { libc::copyfile_state_alloc() };
if state.is_null() {
return Err(RuntimeError::CheckError(Some(libc::ENOMEM)));
}
let progress: extern "C" fn(
libc::c_int,
libc::c_int,
libc::copyfile_state_t,
*const libc::c_char,
*const libc::c_char,
*mut libc::c_void,
) -> libc::c_int = copy_progress;
unsafe {
libc::copyfile_state_set(
state,
libc::COPYFILE_STATE_STATUS_CB as u32,
progress as *const libc::c_void,
)
};
let copied = retried(|| unsafe {
libc::fcopyfile(
source.raw(),
target.raw(),
state,
libc::COPYFILE_METADATA | libc::COPYFILE_DATA,
)
});
unsafe { libc::copyfile_state_free(state) };
match copied {
Ok(_) => Ok(size),
Err(RuntimeError::CheckError(Some(libc::ECANCELED))) => Err(RuntimeError::Cancelled),
Err(error) => Err(error),
}
}
extern "C" fn copy_progress(
_what: libc::c_int,
_stage: libc::c_int,
_state: libc::copyfile_state_t,
_from: *const libc::c_char,
_to: *const libc::c_char,
_context: *mut libc::c_void,
) -> libc::c_int {
match executor::cancelled() {
true => libc::COPYFILE_QUIT,
false => libc::COPYFILE_CONTINUE,
}
}
pub(crate) fn open_at(
path: &CStr,
flags: libc::c_int,
mode: libc::c_int,
) -> Result<Fd, RuntimeError> {
loop {
if executor::cancelled() {
return Err(RuntimeError::Cancelled);
}
let raw = unsafe { libc::open(path.as_ptr(), flags | libc::O_CLOEXEC, mode) }.check();
match raw {
Ok(fd) => return Ok(Fd::new(fd)),
Err(RuntimeError::CheckError(Some(libc::EINTR))) => continue,
Err(error) => return Err(error),
}
}
}
pub(crate) fn directory(fd: &Fd) -> Result<bool, RuntimeError> {
let mut raw: libc::stat = unsafe { mem::zeroed() };
retried(|| unsafe { libc::fstat(fd.raw(), &mut raw) })?;
Ok(raw.st_mode & libc::S_IFMT == libc::S_IFDIR)
}
const SHRINK_CEILING: usize = 64 * FILE_CHUNK;
fn read_whole(fd: &Fd) -> Result<Vec<u8>, RuntimeError> {
let mut found = Vec::new();
if let Ok(size) = hint(fd) {
let _ = found.try_reserve(size);
}
loop {
if executor::cancelled() {
return Err(RuntimeError::Cancelled);
}
found.reserve(FILE_CHUNK);
let read = unsafe {
libc::read(
fd.raw(),
found
.spare_capacity_mut()
.as_mut_ptr()
.cast::<libc::c_void>(),
FILE_CHUNK,
)
}
.check();
let got = match read {
Ok(got) => got as usize,
Err(RuntimeError::CheckError(Some(libc::EINTR))) => continue,
Err(error) => return Err(error),
};
if got == 0 {
let spare = found.capacity() - found.len();
if spare >= FILE_CHUNK && found.len() <= SHRINK_CEILING {
found.shrink_to_fit();
}
return Ok(found);
}
unsafe { found.set_len(found.len() + got) };
}
}
pub(crate) fn read_range(fd: &Fd, offset: u64, len: usize) -> Result<Vec<u8>, RuntimeError> {
let mut found = Vec::new();
while found.len() < len {
if executor::cancelled() {
return Err(RuntimeError::Cancelled);
}
let want = (len - found.len()).min(FILE_CHUNK);
let at = seek_to(offset.saturating_add(found.len() as u64))?;
found.reserve(want);
let read = unsafe {
libc::pread(
fd.raw(),
found
.spare_capacity_mut()
.as_mut_ptr()
.cast::<libc::c_void>(),
want,
at,
)
}
.check();
let got = match read {
Ok(got) => got as usize,
Err(RuntimeError::CheckError(Some(libc::EINTR))) => continue,
Err(error) => return Err(error),
};
if got == 0 {
break;
}
unsafe { found.set_len(found.len() + got) };
}
Ok(found)
}
pub(crate) fn write_all(fd: &Fd, data: &[u8], at: Option<u64>) -> Result<usize, RuntimeError> {
let mut done = 0;
while done < data.len() {
if executor::cancelled() {
return Err(RuntimeError::Cancelled);
}
let want = (data.len() - done).min(FILE_CHUNK);
let from = unsafe { data.as_ptr().add(done) }.cast::<libc::c_void>();
let written = match at {
None => unsafe { libc::write(fd.raw(), from, want) }.check(),
Some(offset) => {
let to = seek_to(offset.saturating_add(done as u64))?;
unsafe { libc::pwrite(fd.raw(), from, want, to) }.check()
}
};
let put = match written {
Ok(put) => put as usize,
Err(RuntimeError::CheckError(Some(libc::EINTR))) => continue,
Err(error) => return Err(error),
};
if put == 0 {
return Err(RuntimeError::CheckError(Some(libc::ENOSPC)));
}
done += put;
}
Ok(done)
}
fn seek_to(offset: u64) -> Result<libc::off_t, RuntimeError> {
libc::off_t::try_from(offset).map_err(|_| RuntimeError::CheckError(Some(libc::EINVAL)))
}
pub(crate) fn hint(fd: &Fd) -> Result<usize, RuntimeError> {
let mut raw: libc::stat = unsafe { mem::zeroed() };
retried(|| unsafe { libc::fstat(fd.raw(), &mut raw) })?;
Ok(raw.st_size.max(0) as usize)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::modules::input::token;
#[test]
fn every_file_task_says_it_blocks() {
assert!(ReadTask::whole("a").blocking(token()), "read");
assert!(ReadTask::range("a", 0, 1).blocking(token()), "read_at");
assert!(
WriteTask::truncate("a", b"b".as_slice()).blocking(token()),
"write"
);
assert!(
WriteTask::append("a", b"b".as_slice()).blocking(token()),
"append"
);
assert!(
WriteTask::at("a", 0, b"b".as_slice()).blocking(token()),
"write_at"
);
assert!(MetadataTask::following("a").blocking(token()), "metadata");
assert!(
MetadataTask::link("a").blocking(token()),
"symlink_metadata"
);
assert!(ReadDirTask::new("a").blocking(token()), "read_dir");
assert!(PathTask::remove("a").blocking(token()), "remove");
assert!(PathTask::remove_dir("a").blocking(token()), "remove_dir");
assert!(PathTask::create_dir("a").blocking(token()), "create_dir");
assert!(PathTask::rename("a", "b").blocking(token()), "rename");
}
}