use std::{
collections::HashSet,
env,
fs::File,
io::{self, BufReader, Seek, SeekFrom},
path::{Path, PathBuf},
};
use tectonic_errors::Result;
use tectonic_status_base::{tt_warning, StatusBackend};
use super::{
try_open_file, InputFeatures, InputHandle, InputOrigin, IoProvider, OpenResult, OutputHandle,
TectonicIoError,
};
#[derive(Debug)]
pub struct FilesystemPrimaryInputIo {
path: PathBuf,
}
impl FilesystemPrimaryInputIo {
pub fn new<P: AsRef<Path>>(path: P) -> FilesystemPrimaryInputIo {
FilesystemPrimaryInputIo {
path: path.as_ref().to_owned(),
}
}
}
impl IoProvider for FilesystemPrimaryInputIo {
fn input_open_primary(&mut self, status: &mut dyn StatusBackend) -> OpenResult<InputHandle> {
match self.input_open_primary_with_abspath(status) {
OpenResult::Ok((ih, _path)) => OpenResult::Ok(ih),
OpenResult::Err(e) => OpenResult::Err(e),
OpenResult::NotAvailable => OpenResult::NotAvailable,
}
}
fn input_open_primary_with_abspath(
&mut self,
_status: &mut dyn StatusBackend,
) -> OpenResult<(InputHandle, Option<PathBuf>)> {
let f = match try_open_file(&self.path) {
OpenResult::Ok(f) => f,
OpenResult::NotAvailable => return OpenResult::NotAvailable,
OpenResult::Err(e) => return OpenResult::Err(e),
};
let handle = InputHandle::new("", BufReader::new(f), InputOrigin::Filesystem);
let path = match make_abspath(&self.path) {
Ok(m) => m,
Err(e) => return OpenResult::Err(e),
};
OpenResult::Ok((handle, Some(path)))
}
}
pub struct FilesystemIo {
root: PathBuf,
writes_allowed: bool,
absolute_allowed: bool,
hidden_input_paths: HashSet<PathBuf>,
reported_paths: HashSet<PathBuf>,
}
impl FilesystemIo {
pub fn new(
root: &Path,
writes_allowed: bool,
absolute_allowed: bool,
hidden_input_paths: HashSet<PathBuf>,
) -> FilesystemIo {
FilesystemIo {
root: PathBuf::from(root),
writes_allowed,
absolute_allowed,
hidden_input_paths,
reported_paths: HashSet::new(),
}
}
pub fn root(&self) -> &Path {
&self.root
}
fn construct_path(&mut self, name: &str) -> Result<PathBuf> {
let path = Path::new(name);
if path.is_absolute() && !self.absolute_allowed {
return Err(TectonicIoError::PathForbidden(path.to_owned()).into());
}
let mut combined = PathBuf::from(&self.root);
combined.push(path);
Ok(combined)
}
}
impl IoProvider for FilesystemIo {
fn output_open_name(&mut self, name: &str) -> OpenResult<OutputHandle> {
if !self.writes_allowed {
return OpenResult::NotAvailable;
}
let path = match self.construct_path(name) {
Ok(p) => p,
Err(e) => return OpenResult::Err(e),
};
let f = match File::create(path) {
Ok(f) => f,
Err(e) => return OpenResult::Err(e.into()),
};
OpenResult::Ok(OutputHandle::new(name, f))
}
fn output_open_stdout(&mut self) -> OpenResult<OutputHandle> {
OpenResult::NotAvailable
}
fn input_open_name(
&mut self,
name: &str,
status: &mut dyn StatusBackend,
) -> OpenResult<InputHandle> {
match self.input_open_name_with_abspath(name, status) {
OpenResult::Ok((h, _path)) => OpenResult::Ok(h),
OpenResult::Err(e) => OpenResult::Err(e),
OpenResult::NotAvailable => OpenResult::NotAvailable,
}
}
#[allow(clippy::if_same_then_else)]
fn input_open_name_with_abspath(
&mut self,
name: &str,
status: &mut dyn StatusBackend,
) -> OpenResult<(InputHandle, Option<PathBuf>)> {
let path = match self.construct_path(name) {
Ok(p) => p,
Err(e) => return OpenResult::Err(e),
};
if self.hidden_input_paths.contains(&path) {
return OpenResult::NotAvailable;
}
let f = match File::open(&path) {
Ok(f) => f,
Err(e) => {
return if e.kind() == io::ErrorKind::NotFound {
OpenResult::NotAvailable
} else if let Some(libc::ENOTDIR) = e.raw_os_error() {
OpenResult::NotAvailable
} else {
OpenResult::Err(e.into())
};
}
};
let name_path = Path::new(name);
if name_path.is_absolute() && !self.reported_paths.contains(name_path) {
tt_warning!(
status,
"accessing absolute path `{}`; build may not be reproducible in other environments",
name_path.display()
);
self.reported_paths.insert(name_path.to_owned());
}
let md = match f.metadata() {
Ok(m) => m,
Err(e) => return OpenResult::Err(e.into()),
};
if md.is_dir() {
return OpenResult::NotAvailable;
}
let path = match make_abspath(path) {
Ok(m) => m,
Err(e) => return OpenResult::Err(e),
};
let handle = InputHandle::new(name, BufReader::new(f), InputOrigin::Filesystem);
OpenResult::Ok((handle, Some(path)))
}
}
impl InputFeatures for File {
fn get_size(&mut self) -> Result<usize> {
Ok(self.metadata()?.len() as usize)
}
fn get_unix_mtime(&mut self) -> Result<Option<i64>> {
let sys_time = self.metadata()?.modified()?;
let dur = sys_time.duration_since(std::time::SystemTime::UNIX_EPOCH)?;
Ok(Some(dur.as_secs() as i64))
}
fn try_seek(&mut self, pos: SeekFrom) -> Result<u64> {
Ok(self.seek(pos)?)
}
}
impl InputFeatures for BufReader<File> {
fn get_size(&mut self) -> Result<usize> {
Ok(self.get_mut().metadata()?.len() as usize)
}
fn get_unix_mtime(&mut self) -> Result<Option<i64>> {
let sys_time = self.get_mut().metadata()?.modified()?;
let dur = sys_time.duration_since(std::time::SystemTime::UNIX_EPOCH)?;
Ok(Some(dur.as_secs() as i64))
}
fn try_seek(&mut self, pos: SeekFrom) -> Result<u64> {
Ok(self.seek(pos)?)
}
}
fn make_abspath<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
let cwd = env::current_dir()?;
Ok(cwd.join(path.as_ref()))
}