use std::ffi::OsStr;
use std::io;
use std::path::PathBuf;
use super::cli::{Action, Filter, ZE_ABORT, ZE_OK, ZE_OPEN, ZE_PARMS};
use super::messages::{self as m, printf};
use super::readzip::{self, Archive, cstr, zsearch};
use super::unix::{
S_IFDIR, S_IFIFO, S_IFLNK, S_IFREG, UnixMeta, meta_of, stdin_meta, unix2dostime,
};
use super::{Exit, Zr};
use super::{flavour, unicode};
const ZE_LOGIC: i32 = 5;
const ZE_NONE: i32 = 12;
const ZE_NAME: i32 = 13;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum FoundKind {
File,
Dir,
Symlink,
Fifo,
Stdin,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub(crate) struct Found {
pub(crate) disk: PathBuf,
pub(crate) name: Vec<u8>,
pub(crate) zname: Vec<u8>,
pub(crate) oname: Vec<u8>,
pub(crate) kind: FoundKind,
pub(crate) meta: UnixMeta,
pub(crate) dostime: u64,
pub(crate) usize: Option<u64>,
pub(crate) dosflag: bool,
}
struct Listed {
name: Vec<u8>,
iname: Vec<u8>,
oname: Vec<u8>,
dosflag: bool,
}
struct Scan {
listed: Vec<Listed>,
zip_stat: Option<Option<std::fs::Metadata>>,
last_err: Option<io::Error>,
ar: Archive,
}
pub(crate) fn ex2in(x: &[u8], pathput: bool, dosify: bool) -> Option<Vec<u8>> {
let at = |i: usize| x.get(i).copied().unwrap_or(0);
let mut t: usize;
if x.starts_with(b"//") && at(2) != 0 && at(2) != b'/' {
let mut n = 2;
while at(n) != 0 && at(n) != b'/' {
n += 1; }
if at(n) != 0 {
n += 1;
while at(n) != 0 && at(n) != b'/' {
n += 1; }
}
if at(n) != 0 {
t = n + 1;
} else {
return None;
}
} else {
t = 0;
}
while at(t) == b'/' {
t += 1; }
while at(t) == b'.' && at(t + 1) == b'/' {
t += 2; }
let mut n = x[t.min(x.len())..].to_vec();
if !pathput {
n = last(&n).to_vec();
}
if dosify {
msname(&mut n);
}
Some(n)
}
pub(crate) fn crashed(z: &mut Zr) -> Exit {
z.mesg_write(b"\n");
z.ziperr(ZE_ABORT, m::ABORTING.as_bytes())
}
fn last(p: &[u8]) -> &[u8] {
match p.iter().rposition(|&b| b == b'/') {
Some(i) => &p[i + 1..],
None => p,
}
}
fn msname(n: &mut Vec<u8>) {
let mut q = Vec::with_capacity(n.len());
let mut f = 0;
for &c in n.iter() {
if matches!(
c,
b' ' | b':'
| b'"'
| b'*'
| b'+'
| b','
| b';'
| b'<'
| b'='
| b'>'
| b'?'
| b'['
| b']'
| b'|'
) {
continue; } else if c == b'/' {
q.push(c);
f = 0; } else if c == b'.' {
if f == 0 {
continue; } else if f < 9 {
q.push(c);
f = 9; } else {
f = 12; }
} else if f < 12 && f != 8 {
f += 1; q.push(c.to_ascii_uppercase());
}
}
*n = q;
}
#[derive(Clone, Copy)]
struct Cx {
no_wild: bool,
allow_regex: bool,
wild_stop_at_dir: bool,
}
fn isshexp(p: &[u8]) -> bool {
let mut i = 0;
while i < p.len() {
if p[i] == b'\\' && i + 1 < p.len() {
i += 1;
} else if matches!(p[i], b'?' | b'*' | b'[') {
return true;
}
i += 1;
}
false
}
fn recmatch(p: &[u8], mut pi: usize, s: &[u8], mut si: usize, cx: Cx) -> i32 {
let pc = |i: usize| p.get(i).copied().unwrap_or(0);
let sc = |i: usize| s.get(i).copied().unwrap_or(0);
let mut c = pc(pi);
pi += 1;
if c == 0 {
return i32::from(sc(si) == 0);
}
if c == b'?' {
return if cx.wild_stop_at_dir {
if sc(si) != 0 && sc(si) != b'/' {
recmatch(p, pi, s, si + 1, cx)
} else {
0
}
} else if sc(si) != 0 {
recmatch(p, pi, s, si + 1, cx)
} else {
0
};
}
if !cx.no_wild && c == b'*' {
if cx.wild_stop_at_dir {
if pc(pi) != b'*' {
let mut s2 = si;
while sc(s2) != 0 && sc(s2) != b'/' {
let r = recmatch(p, pi, s, s2, cx);
if r != 0 {
return r;
}
s2 += 1;
}
if pc(pi) == 0 {
return i32::from(sc(s2) == 0);
}
return if pc(pi) == b'/' || (pc(pi) == b'\\' && pc(pi + 1) == b'/') {
recmatch(p, pi, s, s2, cx)
} else {
2
};
}
pi += 1;
}
if pc(pi) == 0 {
return 1;
}
let rest = p.get(pi..).unwrap_or(&[]);
let srest = s.get(si..).unwrap_or(&[]);
if !isshexp(rest) {
if rest.len() > srest.len() {
return 0;
}
return i32::from(&srest[srest.len() - rest.len()..] == rest);
}
while sc(si) != 0 {
let r = recmatch(p, pi, s, si, cx);
if r != 0 {
return r;
}
si += 1;
}
return 2;
}
if !cx.no_wild && cx.allow_regex && c == b'[' {
if sc(si) == 0 {
return 0; }
let r = pc(pi) == b'!' || pc(pi) == b'^';
pi += usize::from(r);
let mut q = pi;
let mut e = false;
while pc(q) != 0 {
if e {
e = false;
} else if pc(q) == b'\\' {
e = true;
} else if pc(q) == b']' {
break;
}
q += 1;
}
if pc(q) != b']' {
return 0; }
let mut range_start = 0u8;
e = pc(pi) == b'-';
while pi < q {
if !e && pc(pi) == b'\\' {
e = true;
} else if !e && pc(pi) == b'-' {
range_start = pc(pi.wrapping_sub(1));
} else {
let cc = sc(si);
let mut uc = range_start;
if pc(pi + 1) != b'-' {
uc = if uc != 0 { uc } else { pc(pi) };
while uc <= pc(pi) {
if uc == cc {
return if r {
0
} else {
recmatch(p, q + 1, s, si + 1, cx)
};
}
uc = uc.wrapping_add(1);
}
}
range_start = 0;
e = false;
}
pi += 1;
}
return if r {
recmatch(p, q + 1, s, si + 1, cx)
} else {
0
};
}
if !cx.no_wild && c == b'\\' {
c = pc(pi);
pi += 1;
if c == 0 {
return 0; }
}
if c == sc(si) {
recmatch(p, pi, s, si + 1, cx)
} else {
0
}
}
fn shmatch(p: &[u8], s: &[u8], cx: Cx) -> bool {
recmatch(p, 0, s, 0, cx) == 1
}
fn filter(patterns: &[Filter], cx: Cx, name: &[u8]) -> bool {
if patterns.is_empty() {
return true;
}
let icount = patterns.iter().filter(|f| f.flag == b'i').count();
let rcount = patterns.iter().filter(|f| f.flag == b'R').count();
let mut imatch = icount == 0;
let mut rmatch = rcount == 0;
for pat in patterns {
if pat.pattern.is_empty() {
continue; }
let mut p = name;
match pat.flag {
b'R' => {
if rmatch {
continue; }
let mut slashes = pat.pattern.iter().filter(|&&b| b == b'/').count() as i64;
slashes -= name.iter().filter(|&&b| b == b'/').count() as i64;
if slashes < 0 {
for (i, &b) in name.iter().enumerate() {
if b == b'/' {
slashes += 1;
if slashes == 0 {
p = &name[i + 1..];
break;
}
}
}
}
}
b'i' if imatch => continue,
_ => {}
}
if shmatch(&pat.pattern, p, cx) {
match pat.flag {
b'x' => return false,
b'R' => rmatch = true,
_ => imatch = true,
}
}
}
imatch && rmatch
}
pub(crate) fn display(z: &Zr, iname: &[u8]) -> Vec<u8> {
if !flavour::shows_newline_escaped(z.flavour) {
return iname.to_vec();
}
let mut out = Vec::with_capacity(iname.len() * 2);
for &b in iname {
if b < b' ' {
out.push(b'^');
out.push(b'@' + b);
} else {
out.push(b);
}
}
out
}
impl Scan {
fn new(ar: Archive) -> Scan {
Scan {
listed: Vec::new(),
zip_stat: None,
last_err: None,
ar,
}
}
fn lsstat(&mut self, z: &Zr, n: &[u8]) -> Option<(UnixMeta, std::fs::Metadata)> {
let r = if n.is_empty() {
Err(io::Error::from_raw_os_error(2))
} else if z.o.linkput {
std::fs::symlink_metadata(z.host.path(n))
} else {
std::fs::metadata(z.host.path(n))
};
match r {
Ok(md) => Some((meta_of(&md), md)),
Err(e) => {
self.last_err = Some(e);
None
}
}
}
fn procname(&mut self, z: &mut Zr, n: &[u8]) -> Result<bool, Exit> {
if n == b"-" {
self.newname(z, n)?;
return Ok(true);
}
let Some((meta, _)) = self.lsstat(z, n) else {
let Some(p) = ex2in(n, z.o.pathput, z.o.dosify) else {
return Err(crashed(z));
};
return Ok(self.mark_matching(z, &p));
};
let mode = meta.mode;
if (mode & S_IFREG) == S_IFREG || (mode & S_IFLNK) == S_IFLNK {
self.newname(z, n)?;
} else if (mode & S_IFDIR) == S_IFDIR {
let mut p: Vec<u8> = Vec::new();
if n != b"." {
p.extend_from_slice(n);
if p.last() != Some(&b'/') {
p.push(b'/');
}
if z.o.dirnames {
self.newname(z, &p)?;
}
}
if z.o.recurse != 0
&& let Ok(rd) = std::fs::read_dir(z.host.path(n))
{
for e in rd {
let Ok(e) = e else { break };
let mut a = p.clone();
a.extend_from_slice(&os_bytes(&e.file_name()));
if !self.procname(z, &a)? {
z.zipwarn(m::NAME_NOT_MATCHED.as_bytes(), &a);
}
}
}
} else if (mode & S_IFIFO) == S_IFIFO {
if z.o.allow_fifo {
if z.o.noisy {
z.zipwarn(m::READING_FIFO.as_bytes(), n);
}
self.newname(z, n)?;
} else {
z.zipwarn(m::IGNORING_FIFO.as_bytes(), n);
return Ok(true);
}
} else {
z.zipwarn(m::IGNORING_SPECIAL.as_bytes(), n);
}
Ok(true)
}
fn newname(&mut self, z: &mut Zr, name: &[u8]) -> Result<(), Exit> {
let dosflag = z.o.dosify;
let Some(iname) = ex2in(name, z.o.pathput, z.o.dosify) else {
return Err(crashed(z));
};
if iname.is_empty() {
if z.o.pathput && z.o.recurse == 0 {
return Err(z.ziperr(ZE_LOGIC, m::EMPTY_NAME.as_bytes()));
}
return Ok(());
}
let undosm = if dosflag || !z.o.pathput {
ex2in(name, true, false).unwrap_or_default()
} else {
iname.clone()
};
let oname = display(z, &iname);
let cx = cx_of(z);
if let Some(i) = zsearch(&self.ar, &iname) {
if !z.o.filters.is_empty() && !filter(&z.o.filters, cx, &undosm) {
if z.o.verbose > 0 {
let mut l = Vec::new();
printf(&mut l, m::EXCLUDING, &[&oname]);
z.mesg_write(&l);
}
} else {
let zf = &mut self.ar.zfiles[i];
zf.mark = 1;
zf.e.name = name.to_vec();
zf.e.oname = oname;
zf.dosflag = dosflag;
}
} else if z.o.filters.is_empty() || filter(&z.o.filters, cx, &undosm) {
if self.is_the_zip_file(z, name) {
if z.o.verbose > 0 {
z.mesg_write(m::MATCHES_ZIP.as_bytes());
}
return Ok(());
}
self.listed.push(Listed {
name: name.to_vec(),
iname,
oname,
dosflag,
});
}
Ok(())
}
fn mark_matching(&mut self, z: &mut Zr, p: &[u8]) -> bool {
self.mark_matching_as(z, p, false)
}
fn mark_matching_as(&mut self, z: &mut Zr, p: &[u8], show_oname: bool) -> bool {
let cx = cx_of(z);
let mut matched = false;
for i in 0..self.ar.zfiles.len() {
let zf = &mut self.ar.zfiles[i];
if shmatch(p, cstr(&zf.e.iname), cx) {
zf.mark = u8::from(z.o.filters.is_empty() || filter(&z.o.filters, cx, &zf.zname));
if z.o.verbose > 0 {
let mark = zf.mark;
let name = if show_oname {
zf.e.oname.clone()
} else {
zf.e.name.clone()
};
let mut l = Vec::new();
let inex: &[u8] = if mark != 0 { b"in" } else { b"ex" };
printf(&mut l, m::INCLUDING, &[inex, &name]);
z.mesg_write(&l);
}
matched = true;
}
}
if show_oname {
for i in 0..self.ar.zfiles.len() {
let zf = &mut self.ar.zfiles[i];
let Some(zu) = zf.zuname.clone() else {
continue;
};
if !shmatch(p, cstr(&zu), cx) {
continue;
}
zf.mark = u8::from(z.o.filters.is_empty() || filter(&z.o.filters, cx, &zu));
if z.o.verbose > 0 {
let inex: &[u8] = if zf.mark != 0 { b"in" } else { b"ex" };
let (oname, ouname) =
(zf.e.oname.clone(), zf.ouname.clone().unwrap_or_default());
let mut l = Vec::new();
printf(&mut l, m::INCLUDING, &[inex, &oname]);
printf(&mut l, m::ESCAPED_UNICODE, &[&ouname]);
z.mesg_write(&l);
}
matched = true;
}
}
matched
}
fn is_the_zip_file(&mut self, z: &Zr, name: &[u8]) -> bool {
if self.zip_stat.is_none() {
let zipfile = z.o.zipfile.clone().unwrap_or_default();
self.zip_stat = Some(if zipfile == b"-" || zipfile.is_empty() {
None
} else {
std::fs::metadata(z.host.path(&zipfile)).ok()
});
}
let Some(Some(zm)) = &self.zip_stat else {
return false;
};
if name.is_empty() {
return false;
}
match std::fs::metadata(z.host.path(name)) {
Ok(nm) => same_stat(zm, &nm),
Err(_) => false,
}
}
}
#[cfg(unix)]
fn same_stat(a: &std::fs::Metadata, b: &std::fs::Metadata) -> bool {
use std::os::unix::fs::MetadataExt;
a.mode() == b.mode()
&& a.ino() == b.ino()
&& a.dev() == b.dev()
&& a.uid() == b.uid()
&& a.gid() == b.gid()
&& a.size() == b.size()
&& a.mtime() == b.mtime()
&& a.ctime() == b.ctime()
}
#[cfg(not(unix))]
fn same_stat(_a: &std::fs::Metadata, _b: &std::fs::Metadata) -> bool {
false
}
fn os_bytes(s: &OsStr) -> Vec<u8> {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
s.as_bytes().to_vec()
}
#[cfg(not(unix))]
{
s.to_string_lossy().into_owned().into_bytes()
}
}
fn cx_of(z: &Zr) -> Cx {
Cx {
no_wild: z.o.no_wild,
allow_regex: z.o.allow_regex,
wild_stop_at_dir: z.o.wild_stop_at_dir,
}
}
fn filetime(z: &Zr, name: &[u8]) -> Option<(UnixMeta, u64, Option<u64>, FoundKind)> {
let meta = if name == b"-" {
stdin_meta(z)?
} else {
let stripped = name.strip_suffix(b"/").unwrap_or(name);
if stripped.is_empty() {
return None;
}
let r = if z.o.linkput {
std::fs::symlink_metadata(z.host.path(stripped))
} else {
std::fs::metadata(z.host.path(stripped))
};
meta_of(&r.ok()?)
};
let ty = meta.file_type();
let usize = (ty == S_IFREG).then_some(meta.size);
let kind = if name == b"-" {
FoundKind::Stdin
} else if ty == S_IFLNK {
FoundKind::Symlink
} else if ty == S_IFDIR {
FoundKind::Dir
} else if ty == S_IFIFO {
FoundKind::Fifo
} else {
FoundKind::File
};
Some((meta, unix2dostime(z, meta.mtime), usize, kind))
}
pub(crate) fn qsort(
f: flavour::Flavour,
v: &mut [usize],
cmp: &dyn Fn(usize, usize) -> std::cmp::Ordering,
) {
match f {
flavour::Flavour::Ubuntu => v.sort_by(|&a, &b| cmp(a, b)),
#[cfg(target_os = "macos")]
flavour::Flavour::Apple => macos_qsort(v, cmp),
#[cfg(not(target_os = "macos"))]
flavour::Flavour::Apple => bsd_qsort(v, cmp),
}
}
#[cfg(target_os = "macos")]
fn macos_qsort(v: &mut [usize], cmp: &dyn Fn(usize, usize) -> std::cmp::Ordering) {
type Cmp<'a> = &'a dyn Fn(usize, usize) -> std::cmp::Ordering;
extern "C" fn shim(
thunk: *mut libc::c_void,
a: *const libc::c_void,
b: *const libc::c_void,
) -> libc::c_int {
let (cmp, a, b) = unsafe {
(
&*(thunk as *const Cmp),
*(a as *const usize),
*(b as *const usize),
)
};
match cmp(a, b) {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
}
}
let mut thunk: Cmp = cmp;
unsafe {
libc::qsort_r(
v.as_mut_ptr().cast(),
v.len(),
std::mem::size_of::<usize>(),
(&mut thunk as *mut Cmp).cast(),
Some(shim),
);
}
}
#[cfg_attr(target_os = "macos", allow(dead_code))] fn bsd_qsort(mut a: &mut [usize], cmp: &dyn Fn(usize, usize) -> std::cmp::Ordering) {
use std::cmp::Ordering::{Equal, Greater, Less};
let c = |x: usize, y: usize| -> i32 {
match cmp(x, y) {
Less => -1,
Equal => 0,
Greater => 1,
}
};
loop {
let n = a.len();
if n < 8 {
for pm in 1..n {
let mut pl = pm;
while pl > 0 && c(a[pl - 1], a[pl]) > 0 {
a.swap(pl, pl - 1);
pl -= 1;
}
}
return;
}
let med3 = |a: &[usize], x: usize, y: usize, z: usize| -> usize {
if c(a[x], a[y]) < 0 {
if c(a[y], a[z]) < 0 {
y
} else if c(a[x], a[z]) < 0 {
z
} else {
x
}
} else if c(a[y], a[z]) > 0 {
y
} else if c(a[x], a[z]) < 0 {
x
} else {
z
}
};
let mut pm = n / 2;
if n > 7 {
let mut pl = 0;
let mut pn = n - 1;
if n > 40 {
let d = n / 8;
pl = med3(a, pl, pl + d, pl + 2 * d);
pm = med3(a, pm - d, pm, pm + d);
pn = med3(a, pn - 2 * d, pn - d, pn);
}
pm = med3(a, pl, pm, pn);
}
a.swap(0, pm);
let mut pa = 1;
let mut pb = 1;
let mut pc = n - 1;
let mut pd = n - 1;
loop {
while pb <= pc {
let r = c(a[pb], a[0]);
if r > 0 {
break;
}
if r == 0 {
a.swap(pa, pb);
pa += 1;
}
pb += 1;
}
while pb <= pc {
let r = c(a[pc], a[0]);
if r < 0 {
break;
}
if r == 0 {
a.swap(pc, pd);
pd -= 1;
}
pc -= 1;
}
if pb > pc {
break;
}
a.swap(pb, pc);
pb += 1;
pc -= 1;
}
let pn = n;
let mut r = pa.min(pb - pa);
vecswap(a, 0, pb - r, r);
r = (pd - pc).min(pn - pd - 1);
vecswap(a, pb, pn - r, r);
let left = pb - pa;
let right = pd - pc;
if left > 1 {
bsd_qsort(&mut a[..left], cmp);
}
if right > 1 {
let whole = a;
a = &mut whole[pn - right..];
} else {
return;
}
}
}
fn vecswap(a: &mut [usize], i: usize, j: usize, n: usize) {
for k in 0..n {
a.swap(i + k, j + k);
}
}
fn check_dup(z: &mut Zr, listed: &mut Vec<Listed>) -> Result<(), Exit> {
if listed.is_empty() {
return Ok(());
}
let mut order: Vec<usize> = (0..listed.len()).collect();
qsort(z.flavour, &mut order, &|a, b| {
listed[a].name.cmp(&listed[b].name)
});
let mut keep = vec![true; listed.len()];
for w in order.windows(2) {
if listed[w[0]].name == listed[w[1]].name {
keep[w[1]] = false;
}
}
let mut valid: Vec<usize> = order.into_iter().filter(|&i| keep[i]).collect();
qsort(z.flavour, &mut valid, &|a, b| {
listed[a].iname.cmp(&listed[b].iname)
});
for w in valid.windows(2) {
let (a, b) = (&listed[w[0]], &listed[w[1]]);
if a.iname == b.iname {
let mut e = Vec::new();
printf(&mut e, m::DUP_FIRST, &[&a.name]);
e.extend_from_slice(m::DUP_INDENT.as_bytes());
printf(&mut e, m::DUP_SECOND, &[&b.name]);
e.extend_from_slice(m::DUP_INDENT.as_bytes());
printf(&mut e, m::DUP_REPEATED, &[&b.iname]);
if !z.o.pathput {
e.extend_from_slice(m::DUP_JUNK.as_bytes());
}
z.zipwarn(&e, b"");
return Err(z.ziperr(ZE_PARMS, m::CANNOT_REPEAT.as_bytes()));
}
}
let mut i = 0;
listed.retain(|_| {
i += 1;
keep[i - 1]
});
Ok(())
}
pub(crate) struct Collected {
pub(crate) found: Vec<Found>,
pub(crate) ar: Archive,
pub(crate) k: u64,
pub(crate) files_total: u64,
pub(crate) bytes_total: u64,
pub(crate) all_current: bool,
}
pub(crate) fn collect(z: &mut Zr, ar: Archive) -> Result<Collected, Exit> {
let mut sc = Scan::new(ar);
if z.o.have_out && z.o.kk == 3 {
let cx = cx_of(z);
for zf in &mut sc.ar.zfiles {
zf.mark = u8::from(z.o.filters.is_empty() || filter(&z.o.filters, cx, &zf.zname));
}
}
if z.o.filter_mode {
sc.procname(z, b"-")?;
}
let names = std::mem::take(&mut z.o.names);
if !names.is_empty() {
if z.o.action == Action::Archive {
if z.o.show_what_doing {
z.mesg_write(m::SD_SCANNING_ARCHIVE.as_bytes());
}
for n in &names {
let found = if n == b"-" {
z.zipwarn(m::CANNOT_SELECT_STDIN.as_bytes(), b"");
false
} else {
let Some(p) = ex2in(n, z.o.pathput, z.o.dosify) else {
return Err(crashed(z));
};
sc.mark_matching_as(z, &p, true)
};
if !found {
z.zipwarn(m::NOT_IN_ARCHIVE.as_bytes(), n);
}
}
} else {
if z.o.show_what_doing {
z.mesg_write(m::SD_SCANNING_FILES.as_bytes());
}
for n in &names {
if !sc.procname(z, n)? {
miss(z, &mut sc, n, n)?;
}
}
}
}
z.o.names = names;
if z.o.recurse == 2 && !sc.procname(z, b".")? {
miss_r(z, &mut sc)?;
}
if z.o.show_what_doing {
z.mesg_write(m::SD_APPLYING_FILTERS.as_bytes());
}
if z.o.kk != 4
&& z.o.first_listarg == 0
&& matches!(z.o.action, Action::Update | Action::Freshen)
{
let cx = cx_of(z);
for zf in &mut sc.ar.zfiles {
zf.mark = u8::from(z.o.filters.is_empty() || filter(&z.o.filters, cx, &zf.zname));
}
}
if z.o.show_what_doing {
z.mesg_write(m::SD_CHECKING_DUPS.as_bytes());
}
check_dup(z, &mut sc.listed)?;
if z.o.show_what_doing {
z.mesg_write(m::SD_SCANNING_TO_UPDATE.as_bytes());
}
let (before, after) = (z.o.before, z.o.after);
let mut k: u64 = 0;
let mut files_total: u64 = 0;
let mut bytes_total: u64 = 0;
let mut all_current = true;
for i in 0..sc.ar.zfiles.len() {
let zf = &mut sc.ar.zfiles[i];
zf.current = false;
if zf.mark == 0 {
all_current = false;
continue;
}
let csize = zf.e.siz;
let usize_ = zf.e.len;
if matches!(z.o.action, Action::Delete | Action::Archive) {
let z_tim = match readzip::ut_mtime(&zf.e) {
Some(t) => unix2dostime(z, t),
None => u64::from(zf.e.tim),
};
if z_tim < before || (after != 0 && z_tim >= after) {
zf.mark = 0;
} else {
files_total += 1;
zf.e.len = usize_;
bytes_total = bytes_total.wrapping_add(csize);
k += 1;
}
continue;
}
let isdirname = zf.e.name.last() == Some(&b'/');
let (tf, usize_, f_mtime) = readzip::disk_time(z, &zf.e.name);
if tf == 0 {
all_current = false;
}
let older = matches!(z.o.action, Action::Update | Action::Freshen)
&& match readzip::ut_mtime(&zf.e) {
Some(zt) => f_mtime <= zt,
None => tf <= u64::from(zf.e.tim),
};
if tf == 0 || tf < before || (after != 0 && tf >= after) || older {
zf.mark = if z.o.comadd { 2 } else { 0 };
zf.trash = tf != 0 && tf >= before && (after == 0 || tf < after);
let what: &[u8] = if zf.trash {
m::UP_TO_DATE.as_bytes()
} else {
m::MISSING_OR_EARLY.as_bytes()
};
let mut l = Vec::new();
printf(&mut l, m::ZIP_DIAGNOSTIC, &[&zf.e.oname, what]);
if z.o.verbose > 0 {
z.mesg_write(&l);
}
z.log_write(&l);
} else if z.o.diff_mode
&& tf == u64::from(zf.e.tim)
&& ((isdirname && usize_ == -1) || usize_ as u64 == zf.e.len)
{
zf.mark = 0;
} else {
if tf == u64::from(zf.e.tim)
&& ((zf.e.len == 0 && usize_ == -1) || usize_ as u64 == zf.e.len)
{
zf.current = true;
} else {
all_current = false;
}
files_total += 1;
if usize_ >= 0 {
zf.e.len = usize_ as u64;
bytes_total += usize_ as u64;
} else {
zf.e.len = 0;
}
k += 1;
}
}
if z.o.show_what_doing {
let mut line = Vec::new();
printf(
&mut line,
m::SD_FCOUNT,
&[sc.listed.len().to_string().as_bytes()],
);
z.mesg_write(&line);
}
let zipfile = z.o.zipfile.clone().unwrap_or_default();
let mut found = Vec::new();
for l in std::mem::take(&mut sc.listed) {
if matches!(z.o.action, Action::Delete | Action::Freshen) {
continue;
}
let Some((meta, tf, usize, kind)) = filetime(z, &l.name) else {
continue; };
if tf < before || (after != 0 && tf >= after) || (l.iname == zipfile && !z.o.zip_to_stdout)
{
continue;
}
files_total += 1;
bytes_total += usize.unwrap_or(0);
found.push(Found {
disk: z.host.path(l.name.strip_suffix(b"/").unwrap_or(&l.name)),
name: l.name,
zname: l.iname,
oname: l.oname,
kind,
meta,
dostime: tf,
usize,
dosflag: l.dosflag,
});
}
if z.mesg_line_started {
z.mesg_write(b"\n");
z.set_line(false);
}
Ok(Collected {
found,
ar: sc.ar,
k,
files_total,
bytes_total,
all_current,
})
}
fn miss(z: &mut Zr, sc: &mut Scan, shown: &[u8], n: &[u8]) -> Result<(), Exit> {
z.zipwarn(m::NAME_NOT_MATCHED.as_bytes(), shown);
if z.o.bad_open_is_error {
return Err(open_error(z, sc, n));
}
Ok(())
}
fn miss_r(z: &mut Zr, sc: &mut Scan) -> Result<(), Exit> {
z.zipwarn(m::NAME_NOT_MATCHED.as_bytes(), m::FOR_R_NAME.as_bytes());
if z.o.bad_open_is_error {
return Err(open_error(z, sc, m::R_NAME.as_bytes()));
}
Ok(())
}
fn open_error(z: &mut Zr, sc: &mut Scan, name: &[u8]) -> Exit {
let e = sc
.last_err
.take()
.unwrap_or_else(|| io::Error::from_raw_os_error(2));
z.ziperr_os(ZE_OPEN, name, &e)
}
pub(crate) fn show_files(z: &mut Zr, c: &mut Collected) -> Exit {
let sf = z.o.show_files;
let mut count = 0u64;
let mut bytes = 0u64;
let listing = matches!(sf, 1 | 3 | 5);
if z.o.noisy && listing {
if z.mesg_line_started {
z.mesg_write(b"\n");
z.set_line(false);
}
z.mesg_write(heading(z).as_bytes());
}
if z.log.is_some() {
if z.log_line_started {
z.log_write(b"\n");
z.log_line_started = false;
}
z.log_write(heading(z).as_bytes());
}
let listing_names = matches!(sf, 1 | 3);
for i in 0..c.ar.zfiles.len() {
let zf = &c.ar.zfiles[i];
if !(zf.mark != 0 || z.o.kk == 3) {
continue;
}
count += 1;
if (zf.e.len as i64) > 0 {
bytes = bytes.wrapping_add(zf.e.len);
}
let (oname, ouname) = (zf.e.oname.clone(), zf.ouname.clone());
let mut line = Vec::new();
printf(&mut line, m::SF_ENTRY, &[&oname]);
if z.o.noisy && listing_names {
z.mesg_write(&line);
}
if z.log.is_some() && !matches!(sf, 5 | 6) {
z.log_write(&line);
}
if matches!(sf, 3 | 4)
&& let Some(u) = &ouname
{
let mut l = Vec::new();
printf(&mut l, m::ESCAPED_UNICODE, &[u]);
if z.o.noisy && sf == 3 {
z.mesg_write(&l);
}
if z.log.is_some() {
z.log_write(&l);
}
}
if matches!(sf, 5 | 6) {
let mut l = Vec::new();
printf(&mut l, m::SF_ENTRY, &[ouname.as_ref().unwrap_or(&oname)]);
if z.o.noisy && sf == 5 {
z.mesg_write(&l);
}
if z.log.is_some() {
z.log_write(&l);
}
}
}
for f in &c.found {
count += 1;
let len = f.usize.unwrap_or(0);
if (len as i64) > 0 {
bytes = bytes.wrapping_add(len);
}
let shown = if z.o.unicode_escape_all {
match unicode::local_to_escape(z.using_utf8, &f.zname) {
Some(e) => e,
None => return unicode::segv(z),
}
} else {
f.oname.clone()
};
let mut line = Vec::new();
printf(&mut line, m::SF_ENTRY, &[&shown]);
if z.o.noisy && listing {
z.mesg_write(&line);
}
if z.log.is_some() {
z.log_write(&line);
}
}
let mut line = Vec::new();
printf(
&mut line,
m::SF_TOTAL,
&[count.to_string().as_bytes(), bytes.to_string().as_bytes()],
);
if z.o.noisy || z.log.is_none() {
z.mesg_write(&line);
}
if z.log.is_some() {
z.log_write(&line);
}
z.o.dispose = false;
super::write::finish(z, ZE_OK, &mut c.ar.zfiles)
}
fn heading(z: &Zr) -> &'static str {
if z.o.kk == 3 {
m::ARCHIVE_CONTAINS
} else {
match z.o.action {
Action::Delete => m::WOULD_DELETE,
Action::Freshen => m::WOULD_FRESHEN,
Action::Archive => m::WOULD_COPY,
_ => m::WOULD_ADD,
}
}
}
pub(crate) fn run(z: &mut Zr) -> Exit {
crate::compat::host::install_zip_abort(&z.host, m::INTERRUPTED_ABORTING);
let line = z.mesg_line_started;
z.set_line(line);
if z.o.show_what_doing {
z.mesg_write(m::SD_READING_ARCHIVE.as_bytes());
}
if z.o.fix == 2 {
return super::fix::run(z);
}
let zipfile = z.o.zipfile.clone().unwrap_or_default();
let ar = match readzip::read_archive(z) {
Ok(a) => a,
Err(e) => return e,
};
if z.o.split_method == -1 {
z.o.split_method = 0;
} else if z.o.fix == 0 && z.o.split_method == 0 && z.total_disks > 1 {
let ip = z.o.in_path.clone().unwrap_or_default();
let first = match super::split::get_in_split_path(z, &ip, 0) {
Ok(p) => p,
Err(x) => return x,
};
match std::fs::metadata(z.host.path(&first)) {
Ok(md) => {
z.o.split_method = 1;
z.o.split_size = md.len();
}
Err(_) => {
z.zipwarn(m::NO_INFO_INPUT_SPLIT.as_bytes(), &first);
return Exit { code: ZE_OPEN };
}
}
}
if z.o.noisy_splits && z.o.split_size > 0 {
z.zipmessage(
m::SPLITSIZE.as_bytes(),
z.o.split_size.to_string().as_bytes(),
);
}
z.current_in_disk = 0;
if !ar.exists && z.o.show_files != 0 && (z.o.kk == 3 || z.o.action == Action::Archive) {
return z.ziperr_os(ZE_OPEN, &zipfile, &io::Error::from_raw_os_error(2));
}
if ar.zfiles.is_empty() && (z.o.action != Action::Add || z.o.grow) {
z.zipwarn(&zipfile, m::NOT_FOUND_OR_EMPTY.as_bytes());
}
let mut c = match collect(z, ar) {
Ok(c) => c,
Err(e) => return e,
};
if z.o.show_files != 0 {
return show_files(z, &mut c);
}
let has_files = !c.ar.zfiles.is_empty();
if c.k == 0
&& c.found.is_empty()
&& !z.o.diff_mode
&& (has_files || !z.o.allow_empty_archive)
&& !(has_files
&& (z.o.latest
|| z.o.fix != 0
|| z.o.adjust
|| z.o.junk_sfx
|| z.o.comadd
|| z.o.zipedit))
{
if z.o.test && (has_files || c.ar.zipbeg != 0) {
if let Err(e) = super::update::check_zipfile(z, &zipfile) {
return e;
}
return super::write::finish(z, ZE_OK, &mut c.ar.zfiles);
}
if matches!(z.o.action, Action::Update | Action::Freshen) {
return super::write::finish(z, ZE_NONE, &mut c.ar.zfiles);
} else if !has_files && (z.o.latest || z.o.fix != 0 || z.o.adjust || z.o.junk_sfx) {
return z.ziperr(ZE_NAME, &zipfile);
} else if z.o.recurse != 0 && z.o.filters.is_empty() && z.o.first_listarg > 0 {
let mut hint = m::TRY_ZIP.as_bytes().to_vec();
let first = usize::try_from(z.o.first_listarg).unwrap_or(0);
for a in z.o.args.iter().take(first).skip(1) {
hint.push(b' ');
hint.extend_from_slice(a);
}
hint.extend_from_slice(m::TRY_DOT_I.as_bytes());
for a in z.o.args.iter().skip(first) {
hint.push(b' ');
hint.extend_from_slice(a);
}
return z.ziperr(ZE_NONE, &hint);
}
return z.ziperr(ZE_NONE, &zipfile);
}
if z.o.filesync && c.all_current && c.found.is_empty() {
z.zipmessage(m::ARCHIVE_IS_CURRENT.as_bytes(), b"");
return super::write::finish(z, ZE_OK, &mut c.ar.zfiles);
}
super::update::run(z, c)
}
#[cfg(test)]
mod tests;