use std::ffi::OsString;
use std::fs::{self, File, Metadata};
use std::io::{self, BufRead, BufWriter, Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use super::run::CHUNK;
pub(super) fn path(b: &[u8]) -> PathBuf {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStringExt;
PathBuf::from(OsString::from_vec(b.to_vec()))
}
#[cfg(not(unix))]
{
PathBuf::from(String::from_utf8_lossy(b).into_owned())
}
}
pub(super) fn strerror(e: &io::Error) -> Vec<u8> {
match e.raw_os_error() {
Some(n) => errno_text(n),
None => e.to_string().into_bytes(),
}
}
pub(super) fn errno_text(n: i32) -> Vec<u8> {
let s = io::Error::from_raw_os_error(n).to_string();
let suffix = format!(" (os error {n})");
s.strip_suffix(&suffix).unwrap_or(&s).as_bytes().to_vec()
}
pub(super) fn c_remove(p: &Path) {
if fs::remove_file(p).is_err() {
let _ = fs::remove_dir(p);
}
}
pub(super) fn not_a_standard_file(p: &Path) -> bool {
fs::symlink_metadata(p).map_or(true, |m| !m.file_type().is_file())
}
pub(super) fn count_hard_links(p: &Path) -> u64 {
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
fs::symlink_metadata(p).map_or(0, |m| m.nlink().saturating_sub(1))
}
#[cfg(not(unix))]
{
let _ = p;
0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Kind {
Regular,
Device,
Pipe,
}
pub(super) fn kind_of(f: &File) -> Kind {
#[cfg(unix)]
{
use std::os::unix::fs::FileTypeExt;
match f.metadata().map(|m| m.file_type()) {
Ok(t) if t.is_fifo() || t.is_socket() => Kind::Pipe,
Ok(t) if t.is_char_device() || t.is_block_device() => Kind::Device,
_ => Kind::Regular,
}
}
#[cfg(not(unix))]
{
let _ = f;
Kind::Regular
}
}
pub(super) fn stdin_file() -> io::Result<File> {
#[cfg(unix)]
{
use std::os::fd::AsFd;
Ok(File::from(io::stdin().as_fd().try_clone_to_owned()?))
}
#[cfg(not(unix))]
{
Err(io::Error::from(io::ErrorKind::Unsupported))
}
}
pub(super) fn stdout_writer() -> Box<dyn Write + Send> {
#[cfg(unix)]
{
use std::os::fd::AsFd;
if let Ok(fd) = io::stdout().as_fd().try_clone_to_owned() {
return Box::new(File::from(fd));
}
}
Box::new(io::stdout())
}
pub(super) struct Input {
src: InputSrc,
buf: Box<[u8; CHUNK]>,
pos: usize,
len: usize,
eof: bool,
pub(super) kind: Kind,
}
pub(super) enum InputSrc {
File(File),
Stdin(io::Stdin),
Bytes(io::Cursor<Vec<u8>>),
}
impl Read for InputSrc {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
InputSrc::File(f) => f.read(buf),
InputSrc::Stdin(s) => s.read(buf),
InputSrc::Bytes(c) => c.read(buf),
}
}
}
impl Input {
pub(super) fn file(f: File) -> Self {
let kind = kind_of(&f);
Self::new(InputSrc::File(f), kind)
}
pub(super) fn stdin() -> Self {
match stdin_file() {
Ok(f) => Self::file(f),
Err(_) => Self::new(InputSrc::Stdin(io::stdin()), Kind::Pipe),
}
}
pub(super) fn bytes(data: Vec<u8>) -> Self {
Self::new(InputSrc::Bytes(io::Cursor::new(data)), Kind::Pipe)
}
fn new(src: InputSrc, kind: Kind) -> Self {
Self {
src,
buf: Box::new([0; CHUNK]),
pos: 0,
len: 0,
eof: false,
kind,
}
}
fn read_src(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.eof {
return Ok(0);
}
loop {
match self.src.read(buf) {
Ok(0) => {
self.eof = true;
return Ok(0);
}
Ok(n) => return Ok(n),
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
}
pub(super) fn copy_raw(&mut self, out: &mut dyn Write) -> io::Result<()> {
if let InputSrc::File(f) = &mut self.src
&& f.seek(SeekFrom::Start(0)).is_ok()
{
self.eof = false;
}
self.pos = 0;
self.len = 0;
let mut buf = [0u8; CHUNK];
loop {
let n = self.read_src(&mut buf)?;
if n == 0 {
return Ok(());
}
out.write_all(&buf[..n])?;
}
}
}
impl Read for Input {
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
let avail = self.fill_buf()?;
let n = avail.len().min(out.len());
out[..n].copy_from_slice(&avail[..n]);
self.consume(n);
Ok(n)
}
}
impl BufRead for Input {
fn fill_buf(&mut self) -> io::Result<&[u8]> {
if self.pos == self.len {
let mut n = 0;
let mut chunk = [0u8; CHUNK];
while n < CHUNK {
match self.read_src(&mut chunk[n..])? {
0 => break,
k => n += k,
}
}
self.buf[..n].copy_from_slice(&chunk[..n]);
self.pos = 0;
self.len = n;
}
Ok(&self.buf[self.pos..self.len])
}
fn consume(&mut self, n: usize) {
self.pos = (self.pos + n).min(self.len);
}
}
pub(super) struct Counting<W> {
pub(super) inner: W,
pub(super) n: Arc<AtomicU64>,
}
impl<W: Write> Write for Counting<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let k = self.inner.write(buf)?;
self.n.fetch_add(k as u64, Ordering::Relaxed);
Ok(k)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
pub(super) enum Host {
Real,
Sandbox(Sandbox),
}
pub(super) struct Sandbox {
pub(super) dir: PathBuf,
pub(super) stdin: Option<Vec<u8>>,
pub(super) stdout: Arc<Mutex<Vec<u8>>>,
pub(super) budget: Arc<AtomicU64>,
}
impl Host {
pub(super) fn path(&self, b: &[u8]) -> PathBuf {
match self {
Host::Real => path(b),
Host::Sandbox(s) => s.dir.join(path(b)),
}
}
pub(super) fn stdin_is_tty(&self) -> bool {
matches!(self, Host::Real) && crate::compat::stdin_is_tty()
}
pub(super) fn stdout_is_tty(&self) -> bool {
matches!(self, Host::Real) && crate::compat::stdout_is_tty()
}
pub(super) fn stdin(&mut self) -> Input {
match self {
Host::Real => Input::stdin(),
Host::Sandbox(s) => Input::bytes(s.stdin.take().unwrap_or_default()),
}
}
pub(super) fn charge(&self, n: u64) -> io::Result<()> {
match self {
Host::Real => Ok(()),
Host::Sandbox(s) => take(&s.budget, n),
}
}
pub(super) fn out_writer(&self, f: Option<File>) -> BufWriter<Box<dyn Write + Send>> {
let w: Box<dyn Write + Send> = match (self, f) {
(Host::Real, Some(f)) => Box::new(f),
(Host::Real, None) => stdout_writer(),
(Host::Sandbox(s), f) => {
let inner: Box<dyn Write + Send> = match f {
Some(f) => Box::new(f),
None => Box::new(Captured(s.stdout.clone())),
};
Box::new(Capped {
inner,
budget: s.budget.clone(),
})
}
};
BufWriter::with_capacity(64 * 1024, w)
}
}
struct Captured(Arc<Mutex<Vec<u8>>>);
impl Write for Captured {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut v = self.0.lock().unwrap_or_else(|p| p.into_inner());
v.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
struct Capped {
inner: Box<dyn Write + Send>,
budget: Arc<AtomicU64>,
}
impl Write for Capped {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let want = buf.len() as u64;
take(&self.budget, want)?;
let n = match self.inner.write(buf) {
Ok(n) => n,
Err(e) => {
self.budget.fetch_add(want, Ordering::Relaxed);
return Err(e);
}
};
self.budget.fetch_add(want - n as u64, Ordering::Relaxed);
Ok(n)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
fn take(budget: &AtomicU64, n: u64) -> io::Result<()> {
let mut left = budget.load(Ordering::Relaxed);
loop {
let rest = left.checked_sub(n).ok_or_else(no_space)?;
match budget.compare_exchange_weak(left, rest, Ordering::Relaxed, Ordering::Relaxed) {
Ok(_) => return Ok(()),
Err(now) => left = now,
}
}
}
fn no_space() -> io::Error {
#[cfg(unix)]
{
io::Error::from_raw_os_error(libc::ENOSPC)
}
#[cfg(not(unix))]
{
io::Error::from(io::ErrorKind::StorageFull)
}
}
pub(super) fn set_times(meta: &Metadata, to: &Path) -> io::Result<()> {
let mut times = fs::FileTimes::new();
if let Ok(t) = meta.accessed() {
times = times.set_accessed(t);
}
if let Ok(t) = meta.modified() {
times = times.set_modified(t);
}
File::options().write(true).open(to)?.set_times(times)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_capped_writer_never_wraps_its_budget() {
let budget = Arc::new(AtomicU64::new(10));
let mut w = Capped {
inner: Box::new(io::sink()),
budget: budget.clone(),
};
assert_eq!(w.write(b"1234567").unwrap(), 7);
assert_eq!(budget.load(Ordering::Relaxed), 3);
let e = w.write(b"1234567").unwrap_err();
assert_eq!(e.kind(), no_space().kind());
assert_eq!(budget.load(Ordering::Relaxed), 3);
assert_eq!(w.write(b"123").unwrap(), 3);
assert_eq!(budget.load(Ordering::Relaxed), 0);
assert!(w.write(b"x").is_err());
assert_eq!(w.write(b"").unwrap(), 0);
}
}