You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

189 lines
4.3 KiB

use super::*;
use std::{
fs,io,
path::Path,
fmt, error,
};
use fs::File;
use std::borrow::Cow;
use std::os::unix::io::AsRawFd as _;
fn fallocate(file: &mut File, to: usize) -> Result<(), OutputError>
{
let fd = file.as_raw_fd();
unsafe {
if libc::fallocate(fd, 0, 0, to as libc::off_t) == 0 {
Ok(())
} else {
let errno = *libc::__errno_location();
Err(OutputError::Allocate(errno))
}
}
}
/// A reserved output file.
#[derive(Debug)]
pub struct Output(File);
impl Output
{
/// Consume into a normal file.
#[inline] pub fn into_inner(self) -> File
{
self.0
}
/// Allocate this reserved output file `to` bytes, the map it and return.
pub fn complete(self, to: usize) -> Result<map::MemoryMapMut, OutputError>
{
let mut file = self.0;
fallocate(&mut file, to)?;
map::MemoryMapMut::map(file)
.map_err(OutputError::Map)
.and_then(|map| if map.len() < to {
Err(OutputError::Size{expected: to, got: map.len()})
} else {
Ok(map)
})
}
}
/// Create a reserved output fd
#[inline] pub fn output(path: impl AsRef<Path>) -> Result<Output, OutputError>
{
fs::OpenOptions::new()
.create(true)
.write(true)
.read(true)
//.truncate(true) //TODO: Remove this, and truncate any excess space that may exist from a clobbered previous file at the end
.open(path)
.map(Output)
.map_err(OutputError::Open)
}
fn errno_str<'a>(errno: libc::c_int) -> Cow<'a, str>
{
unsafe {
let ptr = libc::strerror(errno) as *const libc::c_char;
if ptr.is_null() {
return Cow::Borrowed("Unknown");
}
let cstr = std::ffi::CStr::from_ptr(ptr);
cstr.to_string_lossy()
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum OutputError {
Open(io::Error),
Allocate(libc::c_int),
Map(io::Error),
Size{expected: usize, got: usize},
}
impl error::Error for OutputError
{
fn source(&self) -> Option<&(dyn error::Error + 'static)>
{
match &self {
Self::Open(io)
| Self::Map(io)
=> Some(io),
_ => None,
}
}
}
impl fmt::Display for OutputError
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
match self {
Self::Open(io) => write!(f, "Failed to open file: {}", io),
Self::Allocate(errno) => write!(f, "fallocate() failed with error {}: {}", errno, errno_str(*errno)),
Self::Map(io) => write!(f, "mmap() failed: {}", io),
Self::Size{expected, got} => write!(f, "mapped file size mismatch: expected at least {}, got {}", expected, got),
}
}
}
#[derive(Debug)]
pub struct Input(pub File, pub fs::Metadata);
impl Input
{
fn try_from_file(file: File) -> Result<Self,InputError>
{
let stat = file.metadata().map_err(InputError::Stat)?;
Ok(Self(file,stat))
}
/// Open an input file
pub fn open(filename: impl AsRef<Path>) -> Result<Self, InputError>
{
let file = fs::OpenOptions::new()
.read(true)
.open(filename).map_err(InputError::Open)?;
Self::try_from_file(file)
}
}
/// Open all these inputs and return them.
///
/// If one or more fail, then the operation will terminate early and return the error.
pub fn input<U: AsRef<Path> + fmt::Debug, I>(inputs: I) -> Result<Vec<Input>, InputError<U>>
where I: IntoIterator<Item = U>
{
let inputs = inputs.into_iter();
let mut outputs = {
match inputs.size_hint() {
(0, Some(0)) => Vec::default(),
(0, None) => Vec::new(),
(_, Some(x)) | (x, None) => Vec::with_capacity(x),
}
};
for input in inputs
{
outputs.push(Input::open(&input)
.map_err(move |inner| InputError::WithData(Box::new(inner), input))?);
}
Ok(outputs)
}
#[derive(Debug)]
#[non_exhaustive]
pub enum InputError<T = std::convert::Infallible>
where T: fmt::Debug
{
Open(io::Error),
Stat(io::Error),
WithData(Box<InputError>, T),
}
impl<T> error::Error for InputError<T>
where T: fmt::Debug
{
fn source(&self) -> Option<&(dyn error::Error + 'static)>
{
match &self {
Self::Open(io) | Self::Stat(io) => Some(io),
Self::WithData(inner, _) => inner.source(),
}
}
}
impl<T> fmt::Display for InputError<T>
where T: fmt::Debug
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
match self {
Self::Open(io) => write!(f, "failed to open file for reading: {}", io),
Self::Stat(io) => write!(f, "failed to stat file: {}", io),
Self::WithData(inner, msg) => write!(f, "for {:?}: {}", msg, inner),
}
}
}