use std::io;
use super::super::extract::{Ex, Info};
use super::super::fileio::{self, c_str};
use super::super::messages as m;
use super::super::process::{CentralEntry, EB_UT_FL_ATIME, EB_UT_FL_MTIME, IzTimes};
use super::super::{PK_COOL, PK_OK, PK_WARN, Uz, flavour};
use super::mapname::{self, filtattr, perror};
use crate::compat::host::Host;
const AMIGA_: u8 = 1;
const VMS_: u8 = 2;
const UNIX_: u8 = 3;
const ATARI_: u8 = 5;
const QDOS_: u8 = 12;
const ACORN_: u8 = 13;
const BEOS_: u8 = 16;
const TANDEM_: u8 = 17;
const THEOS_: u8 = 18;
const ATHEOS_: u8 = 30;
const FS_FAT_: u8 = 0;
const EF_ASIUNIX: u16 = 0x756e;
const EF_PKVMS: u16 = 0x000c;
const EB_ASI_MODE: usize = 4;
pub(crate) fn s_islnk(mode: u32) -> bool {
mode & 0o170000 == 0o120000
}
fn symlink_host(hn: u8) -> bool {
matches!(hn, UNIX_ | ATARI_ | ATHEOS_ | BEOS_ | VMS_)
}
#[allow(clippy::useless_conversion)]
fn umask(host: &Host) -> u32 {
match host {
#[cfg(unix)]
Host::Real => {
unsafe {
let m = libc::umask(0);
libc::umask(m);
u32::from(m)
}
}
_ => 0o022,
}
}
pub(crate) fn mapattr(uz: &Uz, e: &CentralEntry, info: &mut Info) {
let mut tmp = e.external_file_attributes as u32;
info.file_attr = 0;
let hostnum = info.pinfo.hostnum;
let mut dos = false;
match hostnum {
AMIGA_ => {
tmp = (tmp >> 17) & 7; info.file_attr = (tmp << 6) | (tmp << 3) | tmp;
}
THEOS_ | UNIX_ | VMS_ | ACORN_ | ATARI_ | ATHEOS_ | BEOS_ | QDOS_ | TANDEM_ => {
if hostnum == THEOS_ {
tmp &= 0xF1FF_FFFF;
if tmp & 0xF000_0000 != 0x4000_0000 {
tmp &= 0x01FF_FFFF; } else {
tmp &= 0x41FF_FFFF; }
}
let mut r = false;
info.file_attr = tmp >> 16;
if info.file_attr == 0
&& let Some(ef) = e.extra.as_deref()
{
let mut ef = ef;
while !r && ef.len() >= 4 {
let eb_id = fileio::makeword(ef);
let eb_len = usize::from(fileio::makeword(&ef[2..]));
if eb_len > ef.len() - 4 {
break;
}
match eb_id {
EF_ASIUNIX if eb_len >= EB_ASI_MODE + 2 => {
info.file_attr = u32::from(fileio::makeword(&ef[4 + EB_ASI_MODE..]));
ef = &ef[..eb_len + 4];
}
EF_ASIUNIX | EF_PKVMS => r = true,
_ => {}
}
ef = &ef[eb_len + 4..];
}
}
if !r {
info.symlink = s_islnk(info.file_attr) && symlink_host(hostnum);
return;
}
info.file_attr = tmp >> 16;
dos = true;
}
FS_FAT_ => {
info.file_attr = tmp >> 16;
dos = true;
}
_ => dos = true,
}
if dos {
if tmp & 0x10 == 0 && e.filename.last() == Some(&b'/') {
tmp |= 0x10;
}
tmp = (u32::from(tmp & 1 == 0) << 1) | ((tmp & 0x10) >> 4);
if info.file_attr & 0o700 == (0o400 | (tmp << 6)) {
info.symlink = s_islnk(info.file_attr) && hostnum == FS_FAT_;
return;
}
info.file_attr = 0o444 | (tmp << 6) | (tmp << 3) | tmp;
}
info.file_attr &= !umask(&uz.host);
}
pub(crate) fn get_extattribs(uz: &Uz, ex: &Ex) -> (IzTimes, Option<[u64; 2]>) {
let mut z = IzTimes::default();
let mut ug = [0u64; 2];
let dos = uz.lrec.last_mod_dos_datetime;
let owner = flavour::iz_have_uxuidgid(uz.flavour).then_some(&mut ug);
let flags = match ex.extra_field.as_deref() {
Some(ef) => super::super::process::ef_scan_for_izux_owner(
uz.flavour,
ef,
false,
dos,
Some(&mut z),
owner,
),
None => 0,
};
if flags & EB_UT_FL_MTIME == 0 {
z.mtime = fileio::dos_to_unix_time(uz, dos);
}
if flags & EB_UT_FL_ATIME == 0 {
z.atime = z.mtime;
}
let have = uz.o.x_flag != 0 && flags & super::super::process::EB_UX2_VALID != 0;
(z, have.then_some(ug))
}
fn fits(v: u64) -> bool {
u32::try_from(v).is_ok()
}
fn set_times(f: &std::fs::File, atime: i64, mtime: i64) -> io::Result<()> {
#[cfg(unix)]
{
use std::os::fd::AsRawFd;
let ts = [
libc::timespec {
tv_sec: atime as libc::time_t,
tv_nsec: 0,
},
libc::timespec {
tv_sec: mtime as libc::time_t,
tv_nsec: 0,
},
];
if unsafe { libc::futimens(f.as_raw_fd(), ts.as_ptr()) } == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
#[cfg(not(unix))]
{
let t = |s: i64| std::time::UNIX_EPOCH + std::time::Duration::from_secs(s.max(0) as u64);
f.set_times(
std::fs::FileTimes::new()
.set_accessed(t(atime))
.set_modified(t(mtime)),
)
}
}
fn fchmod(f: &std::fs::File, mode: u32) -> io::Result<()> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
f.set_permissions(std::fs::Permissions::from_mode(mode))
}
#[cfg(not(unix))]
{
let mut p = f.metadata()?.permissions();
p.set_readonly(mode & 0o200 == 0);
f.set_permissions(p)
}
}
fn fchown(f: &std::fs::File, uid: u64, gid: u64) -> io::Result<()> {
#[cfg(unix)]
{
std::os::unix::fs::fchown(f, Some(uid as u32), Some(gid as u32))
}
#[cfg(not(unix))]
{
let _ = (f, uid, gid);
Ok(())
}
}
fn uidgid_failed(uz: &mut Uz, uidgid: [u64; 2], name: &[u8], e: &io::Error) {
let (u, g) = (uidgid[0].to_string(), uidgid[1].to_string());
let err = mapname::strerror(uz, e);
if uz.o.qflag != 0 {
let shown = fileio::fnfilter1(uz, name);
uz.info_fmt(
0x201,
m::CANNOT_SET_ITEM_UID_GID,
&[u.as_bytes(), g.as_bytes(), &shown, &err],
);
} else {
uz.info_fmt(
0x201,
m::CANNOT_SET_UID_GID,
&[u.as_bytes(), g.as_bytes(), &err],
);
}
}
pub(crate) struct SlinkEntry {
pub(crate) target: Vec<u8>,
pub(crate) fname: Vec<u8>,
pub(crate) attr: u32,
pub(crate) uidgid: Option<[u64; 2]>,
}
pub(crate) fn close_outfile(uz: &mut Uz, ex: &mut Ex) -> i32 {
let (zt, uidgid) = get_extattribs(uz, ex);
let Some(out) = ex.outfile.take() else {
return PK_COOL;
};
let fname = c_str(&uz.filename).to_vec();
if ex.symlnk {
let ucsize = uz.lrec.ucsize;
let mut f = out.file;
let mut target = Vec::new();
let read = {
use std::io::{Read, Seek, SeekFrom};
f.seek(SeekFrom::Start(0))
.and_then(|_| (&mut f).take(ucsize).read_to_end(&mut target))
};
if !matches!(read, Ok(n) if n as u64 == ucsize) {
let shown = fileio::fnfilter1(uz, &fname);
uz.info_fmt(0x201, m::SYMLINK_FAILED, &[&shown]);
return PK_WARN;
}
drop(f); let target = c_str(&target).to_vec();
if uz.o.qflag == 0 {
let shown = fileio::fnfilter1(uz, &target);
uz.info_fmt(0, m::SYMLINK_ARROW, &[&shown]);
}
ex.slinks.push(SlinkEntry {
target,
fname,
attr: ex.pinfo.file_attr,
uidgid,
});
return PK_COOL;
}
let f = out.file;
if let Some(ug) = uidgid.filter(|ug| fits(ug[0]) && fits(ug[1]))
&& let Err(e) = fchown(&f, ug[0], ug[1])
{
uidgid_failed(uz, ug, &fname, &e);
}
if let Err(e) = fchmod(&f, filtattr(uz, ex.pinfo.file_attr)) {
perror(uz, m::PERROR_FCHMOD, &e);
}
if uz.o.d_flag <= 1 {
if let Err(e) = set_times(&f, zt.atime, zt.mtime) {
let err = mapname::strerror(uz, &e);
if uz.o.qflag != 0 {
let shown = fileio::fnfilter1(uz, &fname);
uz.info_fmt(0x201, m::CANNOT_SET_ITEM_TIMESTAMPS, &[&shown, &err]);
} else {
uz.info_fmt(0x201, m::CANNOT_SET_TIMESTAMPS, &[&err]);
}
}
}
drop(f);
PK_COOL
}
pub(crate) fn set_symlnk_attribs(uz: &mut Uz, s: &SlinkEntry) -> i32 {
let Ok((root, rel)) = mapname::target(uz, &s.fname) else {
return PK_OK;
};
let Ok((dir, name)) = root.parent(rel) else {
return PK_OK;
};
if let Some(ug) = s.uidgid.filter(|ug| fits(ug[0]) && fits(ug[1]))
&& let Err(e) = dir.lchown(&name, ug[0] as u32, ug[1] as u32)
{
let (u, g) = (ug[0].to_string(), ug[1].to_string());
let shown = fileio::fnfilter1(uz, &s.fname);
let err = mapname::strerror(uz, &e);
uz.info_fmt(
0x201,
m::CANNOT_SET_ITEM_UID_GID,
&[u.as_bytes(), g.as_bytes(), &shown, &err],
);
}
if !flavour::no_lchmod(uz.flavour)
&& let Err(e) = dir.lchmod(&name, filtattr(uz, s.attr))
{
perror(uz, m::PERROR_LCHMOD, &e);
}
PK_OK
}
pub(crate) struct DirEntry {
pub(crate) fname: Vec<u8>,
pub(crate) times: IzTimes,
pub(crate) perms: u32,
pub(crate) uidgid: Option<[u64; 2]>,
}
pub(crate) fn defer_dir_attribs(uz: &Uz, ex: &Ex) -> DirEntry {
let (times, uidgid) = get_extattribs(uz, ex);
DirEntry {
fname: c_str(&uz.filename).to_vec(),
times,
perms: ex.pinfo.file_attr,
uidgid,
}
}
pub(crate) fn set_direc_attribs(uz: &mut Uz, d: &DirEntry) -> i32 {
let mut errval = PK_OK;
let dir = mapname::target(uz, &d.fname)
.and_then(|(root, rel)| root.dir(rel))
.and_then(|dir| dir.attr_file());
if let Some(ug) = d.uidgid.filter(|ug| fits(ug[0]) && fits(ug[1])) {
let r = match &dir {
Ok(f) => fchown(f, ug[0], ug[1]),
Err(e) => Err(copy(e)),
};
if let Err(e) = r {
let (u, g) = (ug[0].to_string(), ug[1].to_string());
let shown = fileio::fnfilter1(uz, &d.fname);
let err = mapname::strerror(uz, &e);
uz.info_fmt(
0x201,
m::CANNOT_SET_ITEM_UID_GID,
&[u.as_bytes(), g.as_bytes(), &shown, &err],
);
if errval == 0 {
errval = PK_WARN;
}
}
}
if uz.o.d_flag <= 0 {
let r = match &dir {
Ok(f) => set_times(f, d.times.atime, d.times.mtime),
Err(e) => Err(copy(e)),
};
if let Err(e) = r {
let shown = fileio::fnfilter1(uz, &d.fname);
let err = mapname::strerror(uz, &e);
uz.info_fmt(0x201, m::CANNOT_SET_ITEM_TIMESTAMPS, &[&shown, &err]);
if errval == 0 {
errval = PK_WARN;
}
}
}
let r = match &dir {
Ok(f) => fchmod(f, d.perms),
Err(e) => Err(copy(e)),
};
if let Err(e) = r {
let shown = fileio::fnfilter1(uz, &d.fname);
let err = mapname::strerror(uz, &e);
uz.info_fmt(0x201, m::DIRLIST_CHMOD_FAILED, &[&shown, &err]);
if errval == 0 {
errval = PK_WARN;
}
}
errval
}
fn copy(e: &io::Error) -> io::Error {
match e.raw_os_error() {
Some(n) => io::Error::from_raw_os_error(n),
None if super::beneath::is_escape(e) => super::beneath::escape(),
None => io::Error::new(e.kind(), e.to_string()),
}
}
pub(crate) fn chmod_dir(uz: &Uz, full: &[u8], mode: u32) -> io::Result<()> {
let (root, rel) = mapname::target(uz, full)?;
fchmod(&root.dir(rel)?.attr_file()?, mode)
}
pub(crate) fn archive_quarantine(host: &Host, f: &std::fs::File) -> Option<Vec<u8>> {
if !matches!(host, Host::Real) {
return None;
}
stuffr::quarantine::read(f)
}
pub(crate) fn quarantine_fd(uz: &Uz, f: &std::fs::File) -> io::Result<()> {
let Some(v) = uz.qf.as_deref() else {
return Ok(());
};
stuffr::quarantine::apply(f, v)
}
pub(crate) fn quarantine_dir(uz: &Uz, d: &super::beneath::Dir) -> io::Result<()> {
if uz.qf.is_none() {
return Ok(());
}
quarantine_fd(uz, &d.attr_file()?)
}
pub(crate) fn quarantine_symlink(
uz: &Uz,
dir: &super::beneath::Dir,
name: &[u8],
) -> io::Result<()> {
if uz.qf.is_none() {
return Ok(());
}
#[cfg(target_os = "macos")]
{
quarantine_fd(uz, &dir.open_symlink(name)?)
}
#[cfg(not(target_os = "macos"))]
{
let _ = (dir, name);
Ok(())
}
}