Commit 1f0468f6 authored by Anders Blomdell's avatar Anders Blomdell
Browse files

Add zero-termination option and name-checking

parent e9ccfeb4
......@@ -13,4 +13,4 @@ publish = false
clap = "2.20.5"
libc = "0.2.21"
threadpool = "1.3.2"
bytes = "0.4.6"
......@@ -21,9 +21,12 @@ General Public License for more details.
extern crate clap;
extern crate libc;
extern crate threadpool;
extern crate bytes;
use clap::{Arg, App};
use std::os::unix::fs::{FileTypeExt, MetadataExt};
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
use std::fs::{Metadata};
use std::path::{Path, PathBuf};
use std::sync::mpsc::{channel, Receiver, Sender};
......@@ -37,6 +40,8 @@ use libc::{c_int, time_t};
use std::result::Result;
use std::io::Error as IOError;
use std::io::{self, Write};
use bytes::BytesMut;
use std::ops::Deref;
mod libhash;
use libhash::{FLAGS_CLEAR_XATTR, FLAGS_READ_XATTR, FLAGS_WRITE_XATTR,
......@@ -47,8 +52,17 @@ use hash::{md5_file, md5_symlink, sha512_file, sha512_symlink};
mod walk;
macro_rules! reportln {
() => (writeln!(io::stderr()).unwrap_or(()));
($($arg:tt)*) => (writeln!(io::stderr(), $($arg)*).unwrap_or(()));
() => (writeln!(io::stderr()).unwrap());
($($arg:tt)*) => (writeln!(io::stderr(), $($arg)*).unwrap());
}
macro_rules! bprint {
($($arg : tt)*) =>
(io::stdout().write_fmt(format_args!($($arg)*)).unwrap());
}
macro_rules! bwrite {
($($arg : tt)+) => (io::stdout().write_all($($arg)+).unwrap());
}
// Extract clap optin as an Option<t>
......@@ -58,7 +72,7 @@ macro_rules! option_t_or_exit {
match v.trim().parse::<$t>() {
Ok(val) => Option::Some(val),
Err(_) => {
println!("Argument '--{} {}' cant be parsed as '{}'",
reportln!("Argument '--{} {}' cant be parsed as '{}'",
$v, v, stringify!($t));
exit(1);
}
......@@ -264,6 +278,34 @@ fn file_type_to_flag(file_type: &std::fs::FileType) -> char {
else { unreachable!() }
}
fn check_name(name : &OsStr) -> bool {
let mut error = false;
// Check for control characters
for c in name.as_bytes() {
if c < &32u8 {
error = true;
}
}
let mut sane_name = BytesMut::from(name.as_bytes());
if error {
// Replace control chararcters for reporting
for c in sane_name.iter_mut() {
if *c < 32u8 {
*c = '?' as u8;
}
}
}
let p: &OsStr = OsStrExt::from_bytes(sane_name.deref());
error |= match p.to_str() {
Some(_) => false,
None => true,
};
if error {
reportln!("Invalid path {}", p.to_string_lossy());
}
! error
}
fn dispatcher(options: clap::ArgMatches,
from_walker: Receiver<WalkerMessage>) {
let jobs = option_t_or_exit!(options, "jobs", usize).unwrap_or(1);
......@@ -272,13 +314,24 @@ fn dispatcher(options: clap::ArgMatches,
let calc_md5 = options.is_present("md5");
let calc_sha512 = options.is_present("sha512");
let (flags, maxage) = options_to_flags(&options);
let zero_terminated = options.is_present("zero-terminated");
macro_rules! zprintln {
() => (
if zero_terminated {
write!(io::stdout(), "\0").unwrap();
} else {
writeln!(io::stdout()).unwrap();
}
)
}
let mut pool = WorkerPool::<WorkerMessage>::new(jobs);
let mut worklist = WorkList::new();
let mut done = false;
print!("#fields: size:mtime:uid:gid:mode:");
if calc_md5 { print!("md5:"); }
if calc_sha512 { print!("sha512:"); }
println!("kind:name");
bprint!("#fields: size:mtime:uid:gid:mode:");
if calc_md5 { bprint!("md5:"); }
if calc_sha512 { bprint!("sha512:"); }
bprint!("kind:name");
zprintln!();
loop {
// reportln!("XXX {} {} {}", done, pending, worklist.used());
if done && worklist.used() == 0 {
......@@ -445,34 +498,39 @@ fn dispatcher(options: clap::ArgMatches,
_ => ()
}
};
if ! (zero_terminated ||
check_name(front.inode.path.as_os_str())) {
continue
}
match front.kind {
' ' => {
// Start of new path
println!("#path: {}", front.inode.path.display());
bprint!("#path: {}", front.inode.path.display());
zprintln!();
continue
},
'F' | 'L' => {
let m = &front.inode.metadata.unwrap();
print!("{}:{}:{}:{}:{:o}:",
bprint!("{}:{}:{}:{}:{:o}:",
m.size(), m.mtime(), m.uid(), m.gid(),
m.mode() & 0o7777);
},
_ => {
let m = &front.inode.metadata.unwrap();
print!("::{}:{}:{:o}:",
bprint!("::{}:{}:{:o}:",
m.uid(), m.gid(), m.mode() & 0o7777);
}
}
use HashOption::{None,Some,Unused};
match *front.md5.borrow() {
None => print!(":"),
Some(ref hash) => print!("{}:", hash),
None => bprint!(":"),
Some(ref hash) => bprint!("{}:", hash),
Unused => (),
_ => unreachable!()
}
match *front.sha512.borrow() {
None => print!(":"),
Some(ref hash) => print!("{}:", hash),
None => bprint!(":"),
Some(ref hash) => bprint!("{}:", hash),
Unused => (),
_ => unreachable!()
}
......@@ -483,11 +541,14 @@ fn dispatcher(options: clap::ArgMatches,
_ => break
}
}
println!("{}:{}", front.kind, path.display());
bprint!("{}", front.kind);
bwrite!(path.as_os_str().as_bytes());
zprintln!();
}
}
}
println!("#endTOC")
bprint!("#endTOC");
zprintln!();
}
fn main() {
......@@ -541,6 +602,9 @@ fn main() {
.short("q")
.conflicts_with("verbose")
.help("Sets the level of verbosity"))
.arg(Arg::with_name("zero-terminated")
.short("z")
.help("End lines with NULL character"))
.arg(Arg::with_name("max_age")
.short("m")
.long("max-age")
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment