use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;
use super::messages::{self as m, printf};
use super::readzip::{ZFile, ut_mtime};
use super::unix;
use super::zipfile::ZEntry;
use super::zipup::{Wr, write_num_string};
use super::{Exit, Zr};
use crate::compat::host::Host;
const MAXCOM: usize = 256;
pub(crate) fn end_line(z: &mut Zr) {
if z.o.noisy && z.mesg_line_started {
z.mesg_write(b"\n");
z.set_line(false);
}
if z.log.is_some() && z.log_line_started {
z.log_write(b"\n");
z.log_line_started = false;
}
}
pub(crate) fn display_running_stats(z: &mut Zr, w: &Wr) {
if z.mesg_line_started {
z.mesg_write(b"\n");
z.set_line(false);
}
if z.log_line_started {
z.log_write(b"\n");
z.log_line_started = false;
}
let mut parts: Vec<Vec<u8>> = Vec::new();
if z.o.display_volume {
let mut s = Vec::new();
printf(
&mut s,
m::DV,
&[
(z.current_in_disk + 1).to_string().as_bytes(),
(w.out.current_disk + 1).to_string().as_bytes(),
],
);
parts.push(s);
}
if z.o.display_counts {
let mut s = Vec::new();
let a = format!("{:>3}", w.files_so_far);
let b = format!("{:>3}", w.files_total as i64 - w.files_so_far as i64);
printf(&mut s, m::DC, &[a.as_bytes(), b.as_bytes()]);
parts.push(s);
}
if z.o.display_bytes {
let mut s = Vec::new();
let a = format!("{:>4}", write_num_string(w.bytes_so_far));
printf(&mut s, m::DB_START, &[a.as_bytes()]);
if w.bytes_total >= w.bytes_so_far {
let b = format!("{:>4}", write_num_string(w.bytes_total - w.bytes_so_far));
printf(&mut s, m::DB_LEFT, &[b.as_bytes()]);
} else {
let b = format!("{:>4}", write_num_string(w.bytes_so_far - w.bytes_total));
printf(&mut s, m::DB_OVER, &[b.as_bytes()]);
}
parts.push(s);
}
for p in parts {
if z.o.noisy {
z.mesg_write(&p);
z.set_line(true);
}
if z.o.logall {
z.log_write(&p);
z.log_line_started = true;
}
}
}
pub(crate) fn comment_stream(z: &mut Zr) -> Option<Box<dyn BufRead>> {
let from_stderr = z.o.comment_from_stderr || z.o.filter_mode;
match &mut z.host {
Host::Real => {
if from_stderr {
#[cfg(unix)]
{
use std::os::fd::AsFd;
let fl = unsafe { libc::fcntl(2, libc::F_GETFL) };
if fl == -1 || fl & libc::O_ACCMODE == libc::O_WRONLY {
return None;
}
if let Ok(fd) = io::stderr().as_fd().try_clone_to_owned() {
return Some(Box::new(io::BufReader::new(File::from(fd))));
}
}
None
} else {
Some(Box::new(io::stdin().lock()))
}
}
h @ Host::Sandbox(_) => {
if from_stderr {
None
} else {
Some(Box::new(h.stdin()))
}
}
}
}
pub(crate) fn cut_at_nul(mut l: Vec<u8>) -> Vec<u8> {
if let Some(i) = l.iter().position(|&b| b == 0) {
l.truncate(i);
}
l
}
pub(crate) fn comment_line(l: Vec<u8>) -> Vec<u8> {
let mut l = cut_at_nul(l);
if l.last() == Some(&b'\n') {
l.pop();
}
l
}
pub(crate) fn fgets(r: &mut dyn BufRead) -> Option<Vec<u8>> {
let mut line = Vec::new();
while line.len() < MAXCOM {
let buf = match r.fill_buf() {
Ok(b) => b,
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(_) => break,
};
if buf.is_empty() {
break;
}
let room = MAXCOM - line.len();
let take = buf.len().min(room);
if let Some(i) = buf[..take].iter().position(|&b| b == b'\n') {
line.extend_from_slice(&buf[..=i]);
r.consume(i + 1);
return Some(line);
}
line.extend_from_slice(&buf[..take]);
r.consume(take);
}
(!line.is_empty()).then_some(line)
}
pub(crate) fn set_mode(p: &Path, mode: u32) {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(p, std::fs::Permissions::from_mode(mode & 0o7777));
}
#[cfg(not(unix))]
{
let _ = (p, mode);
}
}
pub(crate) fn finish(z: &mut Zr, code: i32, zfiles: &mut [ZFile]) -> Exit {
let zipfile = z.o.zipfile.clone().unwrap_or_default();
if z.o.latest && !zipfile.is_empty() && zipfile != b"-" {
if zfiles.is_empty() {
z.zipwarn(m::ZIP_EMPTY_NO_LATEST.as_bytes(), b"");
} else {
let mut t: u32 = 0;
for zf in zfiles.iter() {
let e = &zf.e;
if e.iname.last() == Some(&b'/') {
continue; }
let tim = ut_mtime(e).map_or(e.tim, |mt| unix::unix2dostime(z, mt) as u32);
t = t.max(tim);
}
if t != 0 {
stamp(z, &zipfile, t);
} else {
z.zipwarn(m::ONLY_DIRS_NO_LATEST.as_bytes(), b"");
}
}
}
if z.o.dispose {
trash(z, zfiles);
}
Exit { code }
}
pub(crate) fn stamp(z: &Zr, name: &[u8], d: u32) {
let t = dos2unixtime(&z.host, d);
let when = if t >= 0 {
std::time::UNIX_EPOCH + std::time::Duration::from_secs(t as u64)
} else {
std::time::UNIX_EPOCH - std::time::Duration::from_secs(t.unsigned_abs())
};
set_times_by_path(&z.host.path(name), t, when);
}
fn set_times_by_path(p: &Path, t: i64, when: std::time::SystemTime) {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
let _ = when;
let Ok(c) = std::ffi::CString::new(p.as_os_str().as_bytes()) else {
return;
};
let tv = libc::timeval {
tv_sec: t as libc::time_t,
tv_usec: 0,
};
let times = [tv, tv];
unsafe { libc::utimes(c.as_ptr(), times.as_ptr()) };
}
#[cfg(not(unix))]
{
let _ = t;
if let Ok(f) = File::options().write(true).open(p) {
let _ = f.set_times(
std::fs::FileTimes::new()
.set_accessed(when)
.set_modified(when),
);
}
}
}
#[allow(clippy::useless_conversion)] pub(crate) fn dos2unixtime(host: &Host, d: u32) -> i64 {
let sec = ((d << 1) & 0x3e) as i64;
let min = ((d >> 5) & 0x3f) as i64;
let hour = ((d >> 11) & 0x1f) as i64;
let mday = ((d >> 16) & 0x1f) as i64;
let mon = ((d >> 21) & 0x0f) as i64 - 1;
let year = ((d >> 25) & 0x7f) as i64 + 80;
#[cfg(unix)]
if matches!(host, Host::Real) {
let mut tm: libc::tm = unsafe { std::mem::zeroed() };
tm.tm_sec = sec as libc::c_int;
tm.tm_min = min as libc::c_int;
tm.tm_hour = hour as libc::c_int;
tm.tm_mday = mday as libc::c_int;
tm.tm_mon = mon as libc::c_int;
tm.tm_year = year as libc::c_int;
tm.tm_isdst = -1;
return i64::from(unsafe { libc::mktime(&mut tm) });
}
let _ = host;
let months = (year + 1900) * 12 + mon;
let (y, m0) = (months.div_euclid(12), months.rem_euclid(12) + 1);
let yy = if m0 <= 2 { y - 1 } else { y };
let era = yy.div_euclid(400);
let yoe = yy - era * 400;
let mp = (m0 + 9) % 12;
let doy = (153 * mp + 2) / 5 + mday - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
let days = era * 146_097 + doe - 719_468;
days * 86400 + hour * 3600 + min * 60 + sec
}
pub(crate) fn trash(z: &mut Zr, zfiles: &mut [ZFile]) {
for zf in zfiles.iter_mut() {
if zf.mark == 1 || zf.trash {
zf.mark = 1;
if zf.e.iname.last() != Some(&b'/') {
let name = zf.e.name.clone();
if z.o.verbose > 0 {
let mut l = Vec::new();
printf(&mut l, m::DELETING_FILE, &[&name]);
z.mesg_write(&l);
}
if std::fs::remove_file(z.host.path(&name)).is_err() {
z.zipwarn(m::ERROR_DELETING.as_bytes(), &name);
}
}
}
}
let mut dirs: Vec<&ZEntry> = Vec::new();
for zf in zfiles.iter() {
let e = &zf.e;
if zf.mark != 0
&& e.iname.last() == Some(&b'/')
&& (dirs.is_empty() || dirs.last().is_some_and(|l| l.name != e.name))
{
dirs.push(e);
}
}
dirs.sort_by(|a, b| b.iname.cmp(&a.iname));
let mut prev: Option<Vec<u8>> = None;
for d in dirs {
let mut p = d.name.clone();
if p.is_empty() {
continue;
}
if p.last() == Some(&b'/') {
p.pop();
}
if prev.as_ref() != Some(&p) {
if z.o.verbose > 0 {
let mut l = Vec::new();
printf(&mut l, m::DELETING_DIR, &[&p]);
z.mesg_write(&l);
}
let _ = std::fs::remove_dir(z.host.path(&p));
}
prev = Some(p);
}
}
#[cfg(test)]
mod tests;