mod encoding;
mod scan;
mod write;
use crate::Result;
use crate::block::BlockDevice;
use crate::fs::archive::ArchiveFs;
pub const SIG_LOCAL: u32 = 0x0403_4b50;
pub const SIG_CENTRAL: u32 = 0x0201_4b50;
pub const SIG_EOCD: u32 = 0x0605_4b50;
pub const SIG_ZIP64_EOCD: u32 = 0x0606_4b50;
pub const SIG_ZIP64_LOCATOR: u32 = 0x0706_4b50;
pub const METHOD_STORE: u16 = 0;
pub const METHOD_DEFLATE: u16 = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Compression {
Stored,
Deflate,
}
#[derive(Debug, Clone)]
pub struct ZipFormatOpts {
pub method: Compression,
pub level: u32,
}
impl Default for ZipFormatOpts {
fn default() -> Self {
Self {
method: Compression::Deflate,
level: 6,
}
}
}
impl ZipFormatOpts {
pub fn apply_options(&mut self, bag: &mut crate::format_opts::OptionMap) -> Result<()> {
if let Some(c) = bag.take_str("compression") {
self.method = match c.to_ascii_lowercase().as_str() {
"store" | "stored" | "none" => Compression::Stored,
"deflate" | "deflated" => Compression::Deflate,
other => {
return Err(crate::Error::InvalidArgument(format!(
"zip: unknown compression {other:?} (use `stored` or `deflate`)"
)));
}
};
}
if let Some(l) = bag.take_u32("level")? {
if l > 9 {
return Err(crate::Error::InvalidArgument(format!(
"zip: compression level {l} out of range (0..=9)"
)));
}
self.level = l;
}
Ok(())
}
}
pub struct ZipFs(pub ArchiveFs);
impl ZipFs {
pub fn open(dev: &mut dyn BlockDevice) -> Result<Self> {
Ok(Self(ArchiveFs::from_index(scan::scan(dev)?)))
}
pub fn format(dev: &mut dyn BlockDevice, opts: &ZipFormatOpts) -> Result<Self> {
Ok(Self(ArchiveFs::writer(
"zip",
Box::new(write::ZipWriter::new(dev, opts.clone())),
)))
}
}
impl crate::fs::FilesystemFactory for ZipFs {
type FormatOpts = ZipFormatOpts;
fn format(dev: &mut dyn BlockDevice, opts: &Self::FormatOpts) -> Result<Self> {
Self::format(dev, opts)
}
fn open(dev: &mut dyn BlockDevice) -> Result<Self> {
Self::open(dev)
}
}
crate::impl_archive_fs_filesystem!(ZipFs);
pub(crate) fn dos_to_unix(dos_date: u16, dos_time: u16) -> u64 {
let sec = ((dos_time & 0x1f) as u64) * 2;
let min = ((dos_time >> 5) & 0x3f) as u64;
let hour = ((dos_time >> 11) & 0x1f) as u64;
let day = (dos_date & 0x1f) as u64;
let month = ((dos_date >> 5) & 0x0f) as u64;
let year = 1980 + ((dos_date >> 9) & 0x7f) as u64;
days_from_civil(year, month.max(1), day.max(1)) * 86400 + hour * 3600 + min * 60 + sec
}
pub(crate) fn unix_to_dos(secs: u64) -> (u16, u16) {
let days = (secs / 86400) as i64;
let rem = secs % 86400;
let (year, month, day) = civil_from_days(days);
if year < 1980 {
return (0x0021, 0);
}
let hour = (rem / 3600) as u16;
let min = ((rem % 3600) / 60) as u16;
let sec2 = ((rem % 60) / 2) as u16;
let date = (((year - 1980) as u16) << 9) | ((month as u16) << 5) | (day as u16);
let time = (hour << 11) | (min << 5) | sec2;
(date, time)
}
fn days_from_civil(y: u64, m: u64, d: u64) -> u64 {
let y = if m <= 2 { y - 1 } else { y } as i64;
let era = if y >= 0 { y } else { y - 399 } / 400;
let yoe = y - era * 400;
let mp = ((m + 9) % 12) as i64;
let doy = (153 * mp + 2) / 5 + d as i64 - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
(era * 146097 + doe - 719468) as u64
}
fn civil_from_days(z: i64) -> (i64, u64, u64) {
let z = z + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = z - era * 146097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = (doy - (153 * mp + 2) / 5 + 1) as u64;
let m = (if mp < 10 { mp + 3 } else { mp - 9 }) as u64;
(if m <= 2 { y + 1 } else { y }, m, d)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dos_time_round_trips() {
let secs = days_from_civil(2021, 6, 15) * 86400 + 12 * 3600 + 30 * 60 + 44;
let (date, time) = unix_to_dos(secs);
assert_eq!(dos_to_unix(date, time), secs);
}
#[test]
fn pre_1980_clamps() {
let (date, time) = unix_to_dos(0); assert_eq!((date, time), (0x0021, 0));
}
}