use std::{
cell::RefCell,
fmt::Display,
fs::OpenOptions,
io::{stderr, BufRead, BufReader, BufWriter, Write},
path::PathBuf,
rc::Rc,
str::FromStr,
time::SystemTime,
};
use clap::{crate_version, value_parser, Arg, ArgAction, ArgMatches, Command};
use uucore::error::{strip_errno, UResult, USimpleError};
use crate::find::{find_main, Dependencies};
pub struct Config {
find_options: String,
local_paths: Vec<PathBuf>,
net_paths: Vec<String>,
prune_paths: Vec<PathBuf>,
prune_fs: Vec<String>,
output: PathBuf,
db_format: DbFormat,
}
impl From<ArgMatches> for Config {
fn from(value: ArgMatches) -> Self {
Self {
find_options: value
.get_one::<String>("findoptions")
.cloned()
.unwrap_or_else(String::new),
local_paths: value.get_one::<String>("localpaths").map_or_else(
|| vec![PathBuf::from("/")],
|s| {
s.split_whitespace()
.filter_map(|s| PathBuf::from_str(s).ok())
.collect()
},
),
net_paths: value
.get_one::<String>("netpaths")
.map(|s| {
s.split_whitespace()
.map(std::borrow::ToOwned::to_owned)
.collect()
})
.unwrap_or_default(),
prune_paths: value.get_one::<String>("prunepaths").map_or_else(
|| {
["/tmp", "/usr/tmp", "/var/tmp", "/afs"]
.into_iter()
.map(PathBuf::from)
.collect()
},
|s| s.split_whitespace().map(PathBuf::from).collect(),
),
prune_fs: value.get_one::<String>("prunefs").map_or_else(
|| {
["nfs", "NFS", "proc"]
.into_iter()
.map(str::to_string)
.collect()
},
|s| {
s.split_whitespace()
.map(std::borrow::ToOwned::to_owned)
.collect()
},
),
db_format: value
.get_one::<DbFormat>("dbformat")
.copied()
.unwrap_or_default(),
output: value
.get_one::<PathBuf>("output")
.cloned()
.unwrap_or_else(|| PathBuf::from("/usr/local/var/locatedb")),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub enum DbFormat {
#[default]
Locate02,
}
impl Display for DbFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Locate02 => f.write_str("GNU LOCATE02"),
}
}
}
fn uu_app() -> Command {
Command::new("updatedb")
.version(crate_version!())
.arg(
Arg::new("findoptions")
.long("findoptions")
.require_equals(true)
.env("FINDOPTIONS")
.action(ArgAction::Append),
)
.arg(
Arg::new("localpaths")
.long("localpaths")
.require_equals(true)
.action(ArgAction::Set),
)
.arg(
Arg::new("netpaths")
.long("netpaths")
.require_equals(true)
.env("NETPATHS")
.action(ArgAction::Set),
)
.arg(
Arg::new("prunepaths")
.long("prunepaths")
.require_equals(true)
.env("PRUNEPATHS")
.action(ArgAction::Set),
)
.arg(
Arg::new("prunefs")
.long("prunefs")
.require_equals(true)
.env("PRUNEFS")
.action(ArgAction::Set),
)
.arg(
Arg::new("output")
.long("output")
.require_equals(true)
.value_parser(value_parser!(PathBuf))
.action(ArgAction::Set),
)
.arg(
Arg::new("localuser")
.long("localuser")
.require_equals(true)
.env("LOCALUSER")
.action(ArgAction::Set),
)
.arg(
Arg::new("netuser")
.long("netuser")
.require_equals(true)
.env("NETUSER")
.action(ArgAction::Set),
)
.arg(
Arg::new("dbformat")
.long("dbformat")
.require_equals(true)
.value_parser(["LOCATE02"])
.action(ArgAction::Set),
)
}
struct Frcoder<'a> {
reader: BufReader<&'a [u8]>,
prev: Option<Vec<u8>>,
prefix: usize,
ty: DbFormat,
}
impl<'a> Frcoder<'a> {
fn new(v: &'a [u8], ty: DbFormat) -> Self {
Self {
reader: BufReader::new(v),
prev: None,
prefix: 0,
ty,
}
}
fn generate_header(&self) -> Vec<u8> {
match self.ty {
DbFormat::Locate02 => "\0LOCATE02\0".as_bytes().to_vec(),
}
}
}
impl Iterator for Frcoder<'_> {
type Item = Vec<u8>;
fn next(&mut self) -> Option<Self::Item> {
let mut path = Vec::new();
if self.reader.read_until(b'\0', &mut path).ok()? == 0 {
return None;
}
let prefix = path
.iter()
.zip(self.prev.as_deref().unwrap_or_default())
.take_while(|(a, b)| a == b)
.count();
let diff = prefix as i32 - self.prefix as i32;
let mut out = Vec::new();
if diff.abs() > 0x7f {
out.push(0x80);
out.extend((diff as i16).to_be_bytes());
} else {
out.push(diff as u8);
}
out.extend(path.iter().skip(prefix));
self.prefix = prefix;
self.prev = Some(path);
Some(out)
}
}
struct CapturedDependencies {
output: Rc<RefCell<dyn Write>>,
now: SystemTime,
}
impl CapturedDependencies {
fn new(output: Rc<RefCell<dyn Write>>) -> Self {
Self {
output,
now: SystemTime::now(),
}
}
}
impl Dependencies for CapturedDependencies {
fn get_output(&self) -> &RefCell<dyn Write> {
self.output.as_ref()
}
fn now(&self) -> SystemTime {
self.now
}
fn confirm(&self, _prompt: &str) -> bool {
false
}
}
fn do_updatedb(args: &[&str]) -> UResult<()> {
let matches = uu_app().try_get_matches_from(args)?;
let config = Config::from(matches);
let mut tokens: Vec<String> = Vec::new();
tokens.extend(
config
.local_paths
.iter()
.filter_map(|p| p.to_str())
.map(String::from),
);
tokens.extend(config.net_paths.iter().cloned());
tokens.extend(config.find_options.split_whitespace().map(String::from));
let mut clauses: Vec<Vec<String>> = Vec::new();
if !config.prune_fs.is_empty() {
let mut clause = vec![String::from("(")];
for (i, fs) in config.prune_fs.iter().enumerate() {
if i > 0 {
clause.push(String::from("-or"));
}
clause.push(String::from("-fstype"));
clause.push(fs.clone());
}
clause.push(String::from(")"));
clause.push(String::from("-prune"));
clauses.push(clause);
}
for path in config.prune_paths.iter().filter_map(|p| p.to_str()) {
clauses.push(vec![
String::from("-regex"),
path.to_string(),
String::from("-prune"),
]);
}
clauses.push(vec![String::from("-print0")]);
for (i, clause) in clauses.into_iter().enumerate() {
if i > 0 {
tokens.push(String::from("-or"));
}
tokens.extend(clause);
}
tokens.push(String::from("-sorted"));
let mut find_args = vec!["find"];
find_args.extend(tokens.iter().map(String::as_str));
let output = Rc::new(RefCell::new(Vec::new()));
let deps = CapturedDependencies::new(output.clone());
find_main(find_args.as_slice(), &deps);
let output_path = config.output;
let file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(&output_path)
.map_err(|e| {
USimpleError::new(
1,
format!(
"cannot create '{}': {}",
output_path.display(),
strip_errno(&e)
),
)
})?;
let mut writer = BufWriter::new(file);
let write_err = |e: std::io::Error| {
USimpleError::new(
1,
format!(
"error writing '{}': {}",
output_path.display(),
strip_errno(&e)
),
)
};
let output = output.borrow();
let frcoder = Frcoder::new(output.as_slice(), config.db_format);
writer
.write_all(&frcoder.generate_header())
.map_err(&write_err)?;
for v in frcoder {
writer.write_all(v.as_slice()).map_err(&write_err)?;
}
writer.flush().map_err(&write_err)?;
Ok(())
}
pub fn updatedb_main(args: &[&str]) -> i32 {
match do_updatedb(args) {
Ok(()) => 0,
Err(e) => {
let _ = writeln!(&mut stderr(), "Error: {e}");
1
}
}
}