use super::matching::{iswild, matches};
use super::{Uz, flavour};
pub(crate) mod attrs;
pub(crate) mod beneath;
pub(crate) mod charset;
pub(crate) mod mapname;
pub(crate) struct Wild {
notfirstcall: bool,
wild_dir: Option<Dir>,
wildname: Vec<u8>,
dirname: Vec<u8>,
have_dirname: bool,
}
impl Wild {
pub(crate) fn new() -> Wild {
Wild {
notfirstcall: false,
wild_dir: None,
wildname: Vec::new(),
dirname: Vec::new(),
have_dirname: false,
}
}
fn raw(uz: &Uz, wildspec: &[u8]) -> Vec<u8> {
let n = uz.filename.len() - 1;
wildspec[..wildspec.len().min(n)].to_vec()
}
fn joined(&self, d_name: &[u8]) -> Vec<u8> {
if self.have_dirname {
[&self.dirname[..], d_name].concat()
} else {
d_name.to_vec()
}
}
pub(crate) fn do_wild(&mut self, uz: &Uz, wildspec: &[u8]) -> Option<Vec<u8>> {
let stop = flavour::wild_stop_at_dir(uz.flavour) && uz.o.w_flag;
if !self.notfirstcall {
self.notfirstcall = true;
if !iswild(wildspec) {
self.have_dirname = false;
self.wild_dir = None;
return Some(Self::raw(uz, wildspec));
}
match wildspec.iter().rposition(|&b| b == b'/') {
None => {
self.dirname = b".".to_vec();
self.have_dirname = false;
self.wildname = wildspec.to_vec();
}
Some(i) => {
self.dirname = wildspec[..=i].to_vec();
self.wildname = wildspec[i + 1..].to_vec();
self.have_dirname = true;
}
}
self.wild_dir = Dir::open(&uz.host.path(&self.dirname));
if let Some(dir) = self.wild_dir.as_mut() {
while let Some(name) = dir.next() {
if name.first() == Some(&b'.') && self.wildname.first() != Some(&b'.') {
continue; }
if matches(&self.wildname, &name, false, stop) && name != b"." && name != b".."
{
return Some(self.joined(&name));
}
}
self.wild_dir = None;
}
return Some(Self::raw(uz, wildspec));
}
let Some(dir) = self.wild_dir.as_mut() else {
self.notfirstcall = false; return None;
};
while let Some(name) = dir.next() {
if name.first() == Some(&b'.') && self.wildname.first() != Some(&b'.') {
continue; }
if matches(&self.wildname, &name, false, stop) {
return Some(self.joined(&name));
}
}
self.wild_dir = None;
self.notfirstcall = false; None
}
}
#[cfg(unix)]
pub(crate) struct Dir(std::ptr::NonNull<libc::DIR>);
#[cfg(unix)]
impl Dir {
pub(crate) fn open(path: &std::path::Path) -> Option<Dir> {
use std::os::unix::ffi::OsStrExt;
let c = std::ffi::CString::new(path.as_os_str().as_bytes()).ok()?;
let d = unsafe { libc::opendir(c.as_ptr()) };
std::ptr::NonNull::new(d).map(Dir)
}
pub(crate) fn next(&mut self) -> Option<Vec<u8>> {
unsafe {
let ent = libc::readdir(self.0.as_ptr());
if ent.is_null() {
return None;
}
let name = std::ffi::CStr::from_ptr((*ent).d_name.as_ptr());
Some(name.to_bytes().to_vec())
}
}
}
#[cfg(unix)]
impl Drop for Dir {
fn drop(&mut self) {
unsafe {
libc::closedir(self.0.as_ptr());
}
}
}
#[cfg(not(unix))]
pub(crate) struct Dir(std::vec::IntoIter<Vec<u8>>);
#[cfg(not(unix))]
impl Dir {
pub(crate) fn open(path: &std::path::Path) -> Option<Dir> {
let names: Vec<Vec<u8>> = std::fs::read_dir(path)
.ok()?
.filter_map(|e| e.ok())
.map(|e| e.file_name().as_encoded_bytes().to_vec())
.collect();
Some(Dir(names.into_iter()))
}
pub(crate) fn next(&mut self) -> Option<Vec<u8>> {
self.0.next()
}
}
pub(crate) fn stamp_file(uz: &Uz, fname: &[u8], modtime: i64) -> std::io::Result<()> {
let path = uz.host.path(fname);
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
let c = std::ffi::CString::new(path.as_os_str().as_bytes())?;
let tp = libc::utimbuf {
actime: modtime as libc::time_t,
modtime: modtime as libc::time_t,
};
let rc = unsafe { libc::utime(c.as_ptr(), &tp) };
if rc == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}
#[cfg(not(unix))]
{
let t = if modtime >= 0 {
std::time::UNIX_EPOCH + std::time::Duration::from_secs(modtime as u64)
} else {
std::time::UNIX_EPOCH
};
let f = std::fs::OpenOptions::new().write(true).open(&path)?;
f.set_times(std::fs::FileTimes::new().set_accessed(t).set_modified(t))
}
}