use std::fs::File;
use std::io::{self, BufWriter, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::AtomicU64;
use super::zipfile::ZWrite;
use crate::compat::host::{self, CleanupGuard, Host};
const ZE_TEMP: i32 = 10;
const ZE_WRITE: i32 = 14;
const ZE_CREAT: i32 = 15;
const ZBSZ: usize = 16384;
pub(crate) struct FileSink {
f: BufWriter<Charged>,
pos: u64,
stdio: Option<(usize, bool)>,
err: Option<(Option<i32>, io::ErrorKind)>,
}
impl FileSink {
pub(crate) fn new(f: File, budget: Option<Arc<AtomicU64>>) -> FileSink {
FileSink::with_capacity(ZBSZ, f, budget)
}
pub(crate) fn with_capacity(cap: usize, f: File, budget: Option<Arc<AtomicU64>>) -> FileSink {
FileSink {
f: BufWriter::with_capacity(
cap,
Charged {
f,
budget,
#[cfg(test)]
limit: FILE_SIZE_LIMIT.with(|l| l.get()),
},
),
pos: 0,
stdio: None,
err: None,
}
}
pub(crate) fn stdio(
cap: usize,
f: File,
budget: Option<Arc<AtomicU64>>,
bsd: bool,
) -> FileSink {
let mut s = FileSink::with_capacity(cap, f, budget);
s.stdio = Some((cap, bsd));
s
}
fn fwrite(&mut self, mut buf: &[u8], cap: usize, bsd: bool) -> io::Result<()> {
if !bsd {
let space = cap - self.f.buffer().len();
if buf.len() <= space {
return self.f.write_all(buf);
}
self.f.write_all(&buf[..space])?;
self.f.flush()?;
let rest = &buf[space..];
let direct = rest.len() - rest.len() % cap;
self.f.write_all(&rest[..direct])?;
return self.f.write_all(&rest[direct..]);
}
while !buf.is_empty() {
let space = cap - self.f.buffer().len();
if !self.f.buffer().is_empty() && buf.len() > space {
self.f.write_all(&buf[..space])?;
self.f.flush()?;
buf = &buf[space..];
} else if buf.len() >= cap {
let n = self.f.get_mut().write(&buf[..cap])?;
if n == 0 {
return Err(io::Error::from(io::ErrorKind::WriteZero));
}
buf = &buf[n..];
} else {
return self.f.write_all(buf);
}
}
Ok(())
}
fn failed(&mut self, e: io::Error) -> io::Error {
if self.err.is_none() {
self.err = Some((e.raw_os_error(), e.kind()));
}
e
}
}
pub(crate) struct Charged {
f: File,
budget: Option<Arc<AtomicU64>>,
#[cfg(test)]
limit: Option<u64>,
}
#[cfg(test)]
thread_local! {
pub(crate) static FILE_SIZE_LIMIT: std::cell::Cell<Option<u64>> =
const { std::cell::Cell::new(None) };
}
impl Write for Charged {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
#[cfg(test)]
let buf = match self.limit.as_mut() {
Some(left) => {
if *left == 0 && !buf.is_empty() {
#[cfg(unix)]
return Err(io::Error::from_raw_os_error(libc::EFBIG));
#[cfg(not(unix))]
return Err(io::Error::other("file too large"));
}
let n = (buf.len() as u64).min(*left) as usize;
*left -= n as u64;
&buf[..n]
}
None => buf,
};
let Some(b) = &self.budget else {
return self.f.write(buf);
};
let left = b.load(std::sync::atomic::Ordering::Relaxed);
let want = (buf.len() as u64).min(left);
if want == 0 && !buf.is_empty() {
host::take(b, 1)?;
}
host::take(b, want)?;
let n = self.f.write(&buf[..want as usize]);
if let Ok(n) = &n {
b.fetch_add(want - *n as u64, std::sync::atomic::Ordering::Relaxed);
}
n
}
fn flush(&mut self) -> io::Result<()> {
self.f.flush()
}
}
impl Write for FileSink {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let r = match self.stdio {
Some((cap, bsd)) => self.fwrite(buf, cap, bsd),
None => self.f.write_all(buf),
};
if let Err(e) = r {
return Err(self.failed(e));
}
self.pos += buf.len() as u64;
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
self.f.flush().map_err(|e| self.failed(e))
}
}
impl ZWrite for FileSink {
fn seekable(&self) -> bool {
true
}
fn offset(&self) -> u64 {
self.pos
}
fn seek_to(&mut self, at: u64) -> io::Result<()> {
self.flush()?;
self.f.get_mut().f.seek(SeekFrom::Start(at)).map(|_| ())
}
fn write_here(&mut self, bytes: &[u8]) -> io::Result<()> {
self.f.write_all(bytes).map_err(|e| self.failed(e))
}
fn seek_end(&mut self) -> io::Result<()> {
self.flush()?;
let pos = self.pos;
self.f.get_mut().f.seek(SeekFrom::Start(pos)).map(|_| ())
}
fn seek_here(&mut self) -> io::Result<()> {
self.flush()?;
let f = &mut self.f.get_mut().f;
let here = f.stream_position()?;
f.seek(SeekFrom::Start(here)).map(|_| ())
}
fn ferror(&self) -> Option<io::Error> {
self.err.map(|(code, kind)| match code {
Some(c) => io::Error::from_raw_os_error(c),
None => io::Error::from(kind),
})
}
fn seek_raw(&mut self, at: u64) -> io::Result<()> {
self.flush()?;
self.f.get_mut().f.seek(SeekFrom::Start(at)).map(|_| ())
}
fn tell_raw(&mut self) -> io::Result<u64> {
self.flush()?;
self.f.get_mut().f.stream_position()
}
fn set_offset(&mut self, at: u64) {
self.pos = at;
}
}
pub(crate) struct Closed;
impl Write for Closed {
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
Err(io::Error::from_raw_os_error(9))
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl ZWrite for Closed {
fn seekable(&self) -> bool {
false
}
fn offset(&self) -> u64 {
0
}
fn seek_to(&mut self, _at: u64) -> io::Result<()> {
Err(io::Error::from_raw_os_error(9))
}
fn write_here(&mut self, _bytes: &[u8]) -> io::Result<()> {
Err(io::Error::from_raw_os_error(9))
}
fn seek_end(&mut self) -> io::Result<()> {
Err(io::Error::from_raw_os_error(9))
}
fn seek_here(&mut self) -> io::Result<()> {
Err(io::Error::from_raw_os_error(9))
}
fn ferror(&self) -> Option<io::Error> {
None
}
}
pub(crate) struct StreamSink {
pub(crate) w: BufWriter<Box<dyn Write + Send>>,
pub(crate) pos: u64,
err: Option<(Option<i32>, io::ErrorKind)>,
}
impl StreamSink {
pub(crate) fn new(w: BufWriter<Box<dyn Write + Send>>) -> StreamSink {
StreamSink {
w,
pos: 0,
err: None,
}
}
}
impl Write for StreamSink {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if let Err(e) = self.w.write_all(buf) {
if self.err.is_none() {
self.err = Some((e.raw_os_error(), e.kind()));
}
return Err(e);
}
self.pos += buf.len() as u64;
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
self.w.flush()
}
}
impl ZWrite for StreamSink {
fn seekable(&self) -> bool {
false
}
fn offset(&self) -> u64 {
self.pos
}
fn seek_to(&mut self, _at: u64) -> io::Result<()> {
Err(io::Error::from_raw_os_error(29))
}
fn write_here(&mut self, _bytes: &[u8]) -> io::Result<()> {
Err(io::Error::from_raw_os_error(29))
}
fn seek_end(&mut self) -> io::Result<()> {
Err(io::Error::from_raw_os_error(29))
}
fn seek_here(&mut self) -> io::Result<()> {
Err(io::Error::from_raw_os_error(29))
}
fn ferror(&self) -> Option<io::Error> {
self.err.map(|(code, kind)| match code {
Some(c) => io::Error::from_raw_os_error(c),
None => io::Error::from(kind),
})
}
}
pub(crate) fn stdout_sink(host: &Host) -> Box<dyn ZWrite> {
#[cfg(unix)]
if matches!(host, Host::Real) {
use std::os::fd::AsFd;
if let Ok(fd) = io::stdout().as_fd().try_clone_to_owned() {
let mut f = File::from(fd);
if let Ok(p) = f.stream_position()
&& f.seek(SeekFrom::Start(p)).is_ok()
{
return Box::new(FileSink::new(f, None));
}
}
}
Box::new(StreamSink::new(host.out_writer(None)))
}
pub(crate) struct Temp {
pub(crate) path: Option<PathBuf>,
}
impl Drop for Temp {
fn drop(&mut self) {
if let Some(p) = self.path.take() {
let _ = std::fs::remove_file(p);
}
}
}
pub(crate) fn mkstemp(host: &Host, dir: &[u8]) -> (Vec<u8>, io::Result<(File, CleanupGuard)>) {
mkstemp_with(matches!(host, Host::Real), &|b: &[u8]| host.path(b), dir)
}
pub(crate) fn mkstemp_with(
real: bool,
resolve: &dyn Fn(&[u8]) -> PathBuf,
dir: &[u8],
) -> (Vec<u8>, io::Result<(File, CleanupGuard)>) {
const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let mut seed = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_nanos() as u64)
^ (u64::from(std::process::id()) << 32);
let mut name = Vec::new();
for _ in 0..100 {
name = dir.to_vec();
name.extend_from_slice(super::messages::TEMP_TEMPLATE.as_bytes());
for _ in 0..6 {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
name.push(CHARS[(seed % CHARS.len() as u64) as usize]);
}
match host::create_new_armed_if(real, &resolve(&name), host::ZIP_SIGNALS) {
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue,
r => return (name, r),
}
}
(name, Err(io::Error::from(io::ErrorKind::AlreadyExists)))
}
pub(crate) fn replace(d: &Path, s: &Path) -> Result<(), (i32, io::Error)> {
host::with_zip_signals_held(|| {
let r = replace_now(d, s);
if r.is_err() {
host::disarm_cleanup();
}
r
})
}
#[cfg(all(test, unix))]
pub(crate) static RAISE_MID_REPLACE: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
#[cfg(all(test, unix))]
fn mid_replace() {
if RAISE_MID_REPLACE.load(std::sync::atomic::Ordering::SeqCst) {
unsafe { libc::raise(libc::SIGINT) };
}
}
fn replace_now(d: &Path, s: &Path) -> Result<(), (i32, io::Error)> {
if let Ok(t) = std::fs::symlink_metadata(d) {
#[cfg(unix)]
let copy = {
use std::os::unix::fs::MetadataExt;
t.nlink() > 1 || t.file_type().is_symlink()
};
#[cfg(not(unix))]
let copy = t.file_type().is_symlink();
if !copy {
std::fs::remove_file(d).map_err(|e| (ZE_CREAT, e))?;
#[cfg(all(test, unix))]
mid_replace();
} else {
return copy_over(d, s);
}
}
match std::fs::rename(s, d) {
Ok(()) => Ok(()),
Err(e) if e.raw_os_error() == Some(18) => copy_over(d, s),
Err(e) => Err((ZE_CREAT, e)),
}
}
fn copy_over(d: &Path, s: &Path) -> Result<(), (i32, io::Error)> {
let mut f = File::open(s).map_err(|e| (ZE_TEMP, e))?;
let mut g = File::create(d).map_err(|e| (ZE_CREAT, e))?;
#[cfg(all(test, unix))]
mid_replace();
if let Err(e) = io::copy(&mut f, &mut g).and_then(|_| g.flush()) {
let _ = std::fs::remove_file(d);
return Err((ZE_WRITE, e));
}
let _ = std::fs::remove_file(s);
Ok(())
}