main.rs 21.8 KB
Newer Older
Anders Blomdell's avatar
Anders Blomdell committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/*

Copyright (C) 2018 Anders Blomdell <anders.blomdell@control.lth.se>

This file is part of hashtoc.

Hashtoc is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program. If not, see <https://www.gnu.org/licenses/>.

*/

Anders Blomdell's avatar
Anders Blomdell committed
21 22 23
extern crate clap;
extern crate libc;
extern crate threadpool;
24
extern crate bytes;
Anders Blomdell's avatar
Anders Blomdell committed
25 26 27

use clap::{Arg, App};
use std::os::unix::fs::{FileTypeExt, MetadataExt};
28 29
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
Anders Blomdell's avatar
Anders Blomdell committed
30 31
use std::fs::{Metadata};
use std::path::{Path, PathBuf};
Anders Blomdell's avatar
Anders Blomdell committed
32
use std::sync::mpsc::{channel, Receiver, Sender};
Anders Blomdell's avatar
Anders Blomdell committed
33 34 35 36 37 38
use std::thread;
use std::fmt;
use std::process::exit;
use threadpool::ThreadPool;
use std::collections::HashMap;
use std::cell::RefCell;
Anders Blomdell's avatar
Anders Blomdell committed
39
use libc::{c_int, time_t};
Anders Blomdell's avatar
Anders Blomdell committed
40 41 42
use std::result::Result;
use std::io::Error as IOError;
use std::io::{self, Write};
43 44
use bytes::BytesMut;
use std::ops::Deref;
Anders Blomdell's avatar
Anders Blomdell committed
45 46

mod libhash;
Anders Blomdell's avatar
Anders Blomdell committed
47 48 49
use libhash::{FLAGS_CLEAR_XATTR, FLAGS_READ_XATTR, FLAGS_WRITE_XATTR,
              FLAGS_MAX_AGE, FLAGS_NO_CALC_HASH,
              FLAGS_VERBOSE0, FLAGS_VERBOSE1, FLAGS_VERBOSE2, FLAGS_VERBOSE3};
Anders Blomdell's avatar
Anders Blomdell committed
50 51 52 53 54
mod hash;
use hash::{md5_file, md5_symlink, sha512_file, sha512_symlink};
mod walk;

macro_rules! reportln {
55 56 57 58 59 60 61 62 63 64 65
    () => (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());
Anders Blomdell's avatar
Anders Blomdell committed
66 67
}

Anders Blomdell's avatar
Anders Blomdell committed
68 69 70 71 72 73 74
// Extract clap optin as an Option<t>
macro_rules! option_t_or_exit {
    ($m:ident, $v:expr, $t:ty) => (
        if let Some(v) = $m.value_of($v) {
            match v.trim().parse::<$t>() {
                Ok(val) => Option::Some(val),
                Err(_)  => {
75
                    reportln!("Argument '--{} {}' cant be parsed as '{}'",
Anders Blomdell's avatar
Anders Blomdell committed
76 77 78 79 80 81 82 83 84 85 86
                             $v, v, stringify!($t));
                    exit(1);
                }
            }
        } else {
            Option::None
        }
    )
}

#[derive(Clone)]
Anders Blomdell's avatar
Anders Blomdell committed
87 88
struct Inode {
    path: PathBuf,
89
    metadata: Option<Metadata>
Anders Blomdell's avatar
Anders Blomdell committed
90 91 92 93 94 95 96 97
}

impl std::fmt::Debug for Inode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Inode {{ path: {:?} }}", self.path)
    }}

enum WalkerMessage {
Anders Blomdell's avatar
Anders Blomdell committed
98 99
    Start(PathBuf),
    Inode(Inode), //path: PathBuf, metadata: Metadata},
Anders Blomdell's avatar
Anders Blomdell committed
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
    Done
}

#[derive(Debug)]
enum WorkerMessageHash {
    MD5(Result<String, IOError>),
    SHA512(Result<String, IOError>)
}

#[derive(Debug)]
struct WorkerMessage {
    index:usize,
    hash: WorkerMessageHash
}

#[derive(Debug)]
enum HashOption<T, E> {
    None,
    Unused,
    Pending,
    Err(E),
    Some(T)
}

#[derive(Debug)]
struct HashEntry {
Anders Blomdell's avatar
Anders Blomdell committed
126 127 128 129
    inode: Inode,
    kind: char,
    md5: RefCell<HashOption<String, std::io::Error>>,
    sha512: RefCell<HashOption<String, std::io::Error>>
Anders Blomdell's avatar
Anders Blomdell committed
130 131 132 133
}

#[derive(Debug)]
struct WorkList {
Anders Blomdell's avatar
Anders Blomdell committed
134 135 136
    first: usize,
    last: usize,
    list: HashMap<usize, HashEntry>
Anders Blomdell's avatar
Anders Blomdell committed
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
}

#[allow(dead_code)]
impl WorkList {
    fn new() -> WorkList {
        WorkList{first: 0,
                 last: 0,
                 list: HashMap::<usize, HashEntry>::new()
        }
    }
    fn used(&self) -> usize {
        self.last - self.first
    }
    fn get_front(&self) -> Option<&HashEntry> {
        if self.first == self.last {
            None
        } else {
            self.list.get(&self.first)
        }
    }
    fn get(&self, index: usize) -> Option<&HashEntry> {
        self.list.get(&index)
    }
    fn push_back(&mut self, entry: HashEntry) -> usize {
        let last = self.last;
        self.last += 1;
        self.list.insert(last, entry);
        last
    }
    fn pop_front(&mut self) -> Option<HashEntry> {
       if self.first == self.last {
            None
       } else {
           let first = self.first;
           self.first += 1;
           self.list.remove(&first)
        }
    }
}

Anders Blomdell's avatar
Anders Blomdell committed
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
struct WorkerPool<T> {
    pool: ThreadPool,
    pending: usize,
    rx: Receiver<T>,
    tx: Sender<T>
}

/*trait ExecuteIf {
    fn execute_if<F, G, H>(&self, guard: bool, func: F)
        where F: FnOnce() -> G + Send + 'static,
              G: FnOnce() -> H + Send + 'static,
              H: std::fmt::Debug;
}

impl ExecuteIf for WorkerPool {
 */
impl<T> WorkerPool<T> {
    fn new(jobs: usize) -> WorkerPool<T> {
        let (tx, rx) = channel::<T>();
        WorkerPool {
            pool: ThreadPool::new(jobs),
            pending: 0,
            rx: rx,
            tx: tx
        }
    }
    fn free(&self) -> bool {
        self.pool.max_count() > self.pending
    }
    fn execute_if<F>(&mut self, guard: bool, func: F)
        where F: FnOnce() -> T + Send + 'static,
              T: Send + std::fmt::Debug + 'static {
        if guard {
            self.pending += 1;
            let tx = self.tx.clone();
            self.pool.execute(move || {
                tx.send(func()).unwrap();
            })
        }
    }
    fn recv(&mut self) -> T {
        self.pending -= 1;
        self.rx.recv().unwrap()
    }
}

fn options_to_flags(options: &clap::ArgMatches) -> (c_int, time_t) {
Anders Blomdell's avatar
Anders Blomdell committed
224 225 226 227
    let xattr = options.is_present("xattr");
    let read_xattr = options.is_present("read_xattr");
    let write_xattr = options.is_present("write_xattr");
    let clear_xattr = options.is_present("clear_xattr");
Anders Blomdell's avatar
Anders Blomdell committed
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
    let maxage = option_t_or_exit!(options, "max_age", libc::time_t);
    let mut flags = 0;
    let mut age = 0;
    match (xattr, read_xattr, write_xattr, clear_xattr, maxage) {
        (false, false, false, false, None) => {
        },
        (true, false, false, false, None) |
        (false, true, true, false, None) => {
            flags = FLAGS_READ_XATTR | FLAGS_WRITE_XATTR;
        },
        (true, false, false, false, Some(maxage)) |
        (false, true, true, false, Some(maxage)) => {
            flags = FLAGS_READ_XATTR | FLAGS_WRITE_XATTR | FLAGS_MAX_AGE;
            age = maxage;
        },
        (false, true, false, false, None) => {
            flags = FLAGS_READ_XATTR;
Anders Blomdell's avatar
Anders Blomdell committed
245
        },
Anders Blomdell's avatar
Anders Blomdell committed
246 247 248
        (false, true, false, false, Some(maxage)) => {
            flags = FLAGS_READ_XATTR | FLAGS_MAX_AGE;
            age = maxage;
Anders Blomdell's avatar
Anders Blomdell committed
249
        },
Anders Blomdell's avatar
Anders Blomdell committed
250 251
        (false, false, false, true, None) => {
            flags = FLAGS_CLEAR_XATTR | FLAGS_NO_CALC_HASH;
Anders Blomdell's avatar
Anders Blomdell committed
252
        },
Anders Blomdell's avatar
Anders Blomdell committed
253 254 255 256
        (_, _, _, _, Some(age)) => {
            panic!("--maxage={} specified without sane xattr flags", age);
        },
        (_, _, _, _, _) => {
Anders Blomdell's avatar
Anders Blomdell committed
257 258 259
            panic!("Invalid flags");
        }
    }
Anders Blomdell's avatar
Anders Blomdell committed
260 261 262 263 264 265 266 267
    match (options.is_present("quiet"), options.occurrences_of("verbose")) {
        (true, 0) => flags |= FLAGS_VERBOSE0,
        (false, 0) => flags |= FLAGS_VERBOSE1,
        (false, 1) => flags |= FLAGS_VERBOSE2,
        (false, 2) => flags |= FLAGS_VERBOSE3,
        (_, _) => panic!("Invalid verbosity")
    }
    (flags, age)
Anders Blomdell's avatar
Anders Blomdell committed
268 269 270 271 272 273 274 275 276 277 278 279 280
}

fn file_type_to_flag(file_type: &std::fs::FileType) -> char {
    if file_type.is_file()              { 'F' }
    else if file_type.is_symlink()      { 'L' }
    else if file_type.is_dir()          { 'D' }
    else if file_type.is_char_device()  { 'C' }
    else if file_type.is_block_device() { 'B' }
    else if file_type.is_socket()       { 'S' }
    else if file_type.is_fifo()         { 'P' }
    else                                { unreachable!() }
}

281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
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
}

Anders Blomdell's avatar
Anders Blomdell committed
309 310
fn dispatcher(options: clap::ArgMatches,
              from_walker: Receiver<WalkerMessage>) {
Anders Blomdell's avatar
Anders Blomdell committed
311 312 313
    let jobs = option_t_or_exit!(options, "jobs", usize).unwrap_or(1);
    let lookahead = option_t_or_exit!(options, "lookahead", usize)
        .unwrap_or(jobs * 10);
Anders Blomdell's avatar
Anders Blomdell committed
314 315
    let calc_md5 = options.is_present("md5");
    let calc_sha512 = options.is_present("sha512");
Anders Blomdell's avatar
Anders Blomdell committed
316
    let (flags, maxage) = options_to_flags(&options);
317 318 319 320 321 322 323 324 325 326
    let zero_terminated = options.is_present("zero-terminated");
    macro_rules! zprintln {
        () => (
            if zero_terminated {
                write!(io::stdout(), "\0").unwrap();
            } else {
                writeln!(io::stdout()).unwrap();
            }
        )
    }
Anders Blomdell's avatar
Anders Blomdell committed
327
    let mut pool = WorkerPool::<WorkerMessage>::new(jobs);
Anders Blomdell's avatar
Anders Blomdell committed
328 329
    let mut worklist = WorkList::new();
    let mut done = false;
330 331 332 333 334
    bprint!("#fields: size:mtime:uid:gid:mode:");
    if calc_md5 { bprint!("md5:"); }
    if calc_sha512 { bprint!("sha512:"); }
    bprint!("kind:name");
    zprintln!();
Anders Blomdell's avatar
Anders Blomdell committed
335 336 337 338
    loop {
        // reportln!("XXX {} {} {}", done, pending, worklist.used());
        if done && worklist.used() == 0 {
            break;
Anders Blomdell's avatar
Anders Blomdell committed
339 340
        } if !done && pool.free() && worklist.used() < lookahead {
            // Get paths from walker and enqueue work
Anders Blomdell's avatar
Anders Blomdell committed
341 342 343 344 345
            let message = match from_walker.recv() {
                Ok(message) => message,
                Err(_) => break
            };
            match message {
Anders Blomdell's avatar
Anders Blomdell committed
346
                WalkerMessage::Start(path) => {
347 348 349 350 351 352 353 354 355 356
                    worklist.push_back(HashEntry {
                        inode: Inode {
                            path:path.clone(),
                            metadata: None
                        },
                        kind: ' ',
                        md5: RefCell::new(HashOption::Unused),
                        sha512: RefCell::new(HashOption::Unused),
                    });
                },
Anders Blomdell's avatar
Anders Blomdell committed
357
                WalkerMessage::Inode(inode) => {
Anders Blomdell's avatar
Anders Blomdell committed
358
                    // Push to worklist
Anders Blomdell's avatar
Anders Blomdell committed
359 360
                    let path = inode.path;
                    let metadata = inode.metadata.unwrap();
Anders Blomdell's avatar
Anders Blomdell committed
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
                    use HashOption::{Pending, Unused, None};
                    let (kind, md5, sha512) = match
                        file_type_to_flag(&metadata.file_type()) {
                            kind @ 'F' | kind @ 'L' =>
                                (kind,
                                 if calc_md5 { Pending } else { Unused },
                                 if calc_sha512 { Pending } else { Unused }
                                ),
                            kind @ _ =>
                                (kind,
                                 if calc_md5 { None } else { Unused },
                                 if calc_sha512 { None } else { Unused }
                                )
                        };
                    let index = worklist.push_back(HashEntry {
                        inode: Inode {
                            path:path.clone(),
378
                            metadata: Some(metadata.clone())
Anders Blomdell's avatar
Anders Blomdell committed
379 380 381 382 383 384 385 386 387
                        },
                        kind: kind,
                        md5: RefCell::new(md5),
                        sha512: RefCell::new(sha512),
                    });

                    match kind {
                        'F' => {
                            use WorkerMessageHash::{MD5, SHA512};
Anders Blomdell's avatar
Anders Blomdell committed
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
                            pool.execute_if(calc_md5, {
                                let path = path.clone();
                                let metadata = metadata.clone();
                                move || {
                                    let h = md5_file(path.as_path(),
                                                     metadata,
                                                     flags, maxage);
                                    WorkerMessage {
                                        index: index,
                                        hash: MD5(h)
                                    }
                                }
                            });
                            pool.execute_if(calc_sha512, {
                                let path = path.clone();
                                move || {
                                    let h = sha512_file(path.as_path(),
                                                        metadata,
                                                        flags, maxage);
                                    WorkerMessage {
                                        index: index,
                                        hash: SHA512(h)
                                    }
                                }
                            });
Anders Blomdell's avatar
Anders Blomdell committed
413 414 415
                        },
                        'L' => {
                            use WorkerMessageHash::{MD5, SHA512};
Anders Blomdell's avatar
Anders Blomdell committed
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
                            pool.execute_if(calc_md5, {
                                let path = path.clone();
                                move || {
                                    let h = md5_symlink(path.as_path());
                                    WorkerMessage {
                                        index: index,
                                        hash: MD5(h)
                                    }
                                }
                            });
                            pool.execute_if(calc_sha512, {
                                let path = path.clone();
                                move || {
                                    let h = sha512_symlink(path.as_path());
                                    WorkerMessage {
                                        index: index,
                                        hash: SHA512(h)
                                    }
                                }
                            });

Anders Blomdell's avatar
Anders Blomdell committed
437 438 439 440 441 442 443
                        },
                        _ => ()
                    };
                },
                WalkerMessage::Done =>
                    done = true,
            }
Anders Blomdell's avatar
Anders Blomdell committed
444 445 446
        } else if pool.pending > 0 {
            // Handle finished results
            let message = pool.recv();
Anders Blomdell's avatar
Anders Blomdell committed
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
            let index = message.index;
            let item = worklist.get(index).unwrap();
            use WorkerMessageHash::{MD5,SHA512};
            match message.hash {
                MD5(hash)  => {
                    *item.md5.borrow_mut() = match hash {
                        Ok(h @ _) => HashOption::Some(h),
                        Err(e @ _) => HashOption::Err(e)
                    }
                },
                SHA512(hash)  => {
                    *item.sha512.borrow_mut() = match hash {
                        Ok(h @ _) => HashOption::Some(h),
                        Err(e @ _) => HashOption::Err(e)
                    }
                }
            }
        }
        
        // Consume finished entries
        while worklist.used() > 0 {
            if { use HashOption::Pending;
                 let front = worklist.get_front().unwrap();
                 let ref md5 = *front.md5.borrow();
                 let ref sha512 = *front.sha512.borrow();
                 match (md5, sha512) {
                     (&Pending, _) | (_, &Pending) => true,
                     (_, _) => false
                 } } {
                // First entry still has pending work
                break;
478
            } else {
Anders Blomdell's avatar
Anders Blomdell committed
479 480 481 482 483 484 485 486
                let front = worklist.pop_front().unwrap();
                {
                    let ref md5 = *front.md5.borrow();
                    let ref sha512 = *front.sha512.borrow();
                    use HashOption::{Err};
                    let error = match ( md5, sha512 ) {
                        (&Err(ref e @ _), _) => Option::Some(e),
                        (_, &Err(ref e @ _)) => Option::Some(e),
Anders Blomdell's avatar
Anders Blomdell committed
487
                        (_, _)                => Option::None
Anders Blomdell's avatar
Anders Blomdell committed
488
                    };
Anders Blomdell's avatar
Anders Blomdell committed
489 490 491
                    if flags & FLAGS_CLEAR_XATTR != 0 {
                        continue;
                    }
Anders Blomdell's avatar
Anders Blomdell committed
492 493 494 495
                    match error {
                        Option::Some(e) => {
                            // Failed to checksum file, report and drop
                            reportln!("Error: {:?} {}", front.inode.path, e);
496
                            continue
Anders Blomdell's avatar
Anders Blomdell committed
497 498 499 500
                        }
                        _ => ()
                    }
                };
501 502 503 504
                if ! (zero_terminated ||
                      check_name(front.inode.path.as_os_str())) {
                    continue
                }
505 506 507 508 509 510 511 512 513 514
                let mut path = front.inode.path.as_path();
                loop {
                    match path.strip_prefix("./") {
                        Ok(p) => path = p,
                        _ => break
                    }
                }
                if path.eq(Path::new("")) && front.kind != ' ' {
                    continue
                }
Anders Blomdell's avatar
Anders Blomdell committed
515
                match front.kind {
516 517
                    ' ' => {
                        // Start of new path
518 519
                        bprint!("#path: {}", front.inode.path.display());
                        zprintln!();
520 521
                        continue
                    },
Anders Blomdell's avatar
Anders Blomdell committed
522
                    'F' | 'L' => {
523
                        let m = &front.inode.metadata.unwrap();
524
                        bprint!("{}:{}:{}:{}:{:o}:",
Anders Blomdell's avatar
Anders Blomdell committed
525 526 527 528
                               m.size(), m.mtime(), m.uid(), m.gid(),
                               m.mode() & 0o7777);
                    },
                    _ => {
529
                        let m = &front.inode.metadata.unwrap();
530
                        bprint!("::{}:{}:{:o}:",
Anders Blomdell's avatar
Anders Blomdell committed
531 532 533 534 535
                               m.uid(), m.gid(), m.mode() & 0o7777);
                    }
                }
                use HashOption::{None,Some,Unused};
                match *front.md5.borrow() {
536 537
                    None => bprint!(":"),
                    Some(ref hash) => bprint!("{}:", hash),
Anders Blomdell's avatar
Anders Blomdell committed
538 539 540 541
                    Unused => (),
                    _ => unreachable!()
                }
                match *front.sha512.borrow() {
542 543
                    None => bprint!(":"),
                    Some(ref hash) => bprint!("{}:", hash),
Anders Blomdell's avatar
Anders Blomdell committed
544 545 546
                    Unused => (),
                    _ => unreachable!()
                }                
Anders Blomdell's avatar
Anders Blomdell committed
547
                bprint!("{}:", front.kind);
548 549
                bwrite!(path.as_os_str().as_bytes());
                zprintln!();
Anders Blomdell's avatar
Anders Blomdell committed
550 551 552
            }
        }
    }
553 554
    bprint!("#endTOC");
    zprintln!();
Anders Blomdell's avatar
Anders Blomdell committed
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600
}

fn main() {
    let matches = App::new("hashtoc")
        .version("1.0")
        .author("Anders Blomdell <anders.blomdell@control.lth.se>")
        .about("Generate a Table Of Contents with HASHes for all files")
        .arg(Arg::with_name("jobs")
             .short("j")
             .long("jobs")
             .takes_value(true)
             .help("number of jobs [threads] to use for HASH calculations"))
        .arg(Arg::with_name("lookahead")
             .short("l")
             .long("lookahead")
             .takes_value(true)
             .help("size of lookahead buffer for HASH calculations [jobs*10]"))
        .arg(Arg::with_name("xattr")
             .short("x")
             .long("xattr")
             .conflicts_with_all(&["read_xattr", "write_xattr", "clear_xattr"])
             .help("read/write HASH extended attribute(s)"))
        .arg(Arg::with_name("read_xattr")
             .short("r")
             .long("read-xattr")
             .conflicts_with_all(&["xattr", "clear_xattr"])
             .help(concat!("read HASH extended attribute(s)\n",
                           "(remove if mtime mismatch detected)")))
        .arg(Arg::with_name("write_xattr")
             .short("w")
             .long("write-xattr")
             .conflicts_with_all(&["xattr", "clear_xattr"])
             .help("write HASH extended attribute(s)"))
        .arg(Arg::with_name("clear_xattr")
             .short("c")
             .long("clear-xattr")
             .conflicts_with_all(&["xattr", "read_xattr", "write_xattr"])
             .help("clear hash extended attribute(s)"))
        .arg(Arg::with_name("md5")
             .long("md5")
             .help("Calculate MD5 hash"))
        .arg(Arg::with_name("sha512")
             .long("sha512")
             .help("Calculate SHA512 hash"))
        .arg(Arg::with_name("verbose")
             .short("v")
Anders Blomdell's avatar
Anders Blomdell committed
601
             .conflicts_with("quiet")
Anders Blomdell's avatar
Anders Blomdell committed
602 603
             .multiple(true)
             .help("Sets the level of verbosity"))
Anders Blomdell's avatar
Anders Blomdell committed
604 605 606 607
        .arg(Arg::with_name("quiet")
             .short("q")
             .conflicts_with("verbose")
             .help("Sets the level of verbosity"))
608 609
        .arg(Arg::with_name("zero-terminated")
             .short("z")
610
             .long("zero-terminated")
611
             .help("End lines with NULL character"))
Anders Blomdell's avatar
Anders Blomdell committed
612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631
        .arg(Arg::with_name("max_age")
             .short("m")
             .long("max-age")
             .takes_value(true)
             .help("max age of HASH extended attribute(s)"))
        .arg(Arg::with_name("PATH")
             .help("path(s) to traverse")
             .required(true)
             .multiple(true)
             .index(1))
        .get_matches();


    let (worker, tx) = {
        let (tx, rx) = channel();
        let args = matches.clone();
        let worker = thread::spawn(move|| { dispatcher(args, rx); });
        (worker, tx)
    };
    let callback = | p:&Path, m:&Metadata | {
Anders Blomdell's avatar
Anders Blomdell committed
632 633
        tx.send( WalkerMessage::Inode(
            Inode{path:p.to_owned(), metadata:Some(m.clone())})).unwrap();
Anders Blomdell's avatar
Anders Blomdell committed
634
    };
635 636 637
    let paths : Vec<_> = matches.values_of_os("PATH").unwrap().collect();
//    let mut paths : Vec<_> = matches.values_of_os("PATH").unwrap().collect();
//    paths.sort();
Anders Blomdell's avatar
Anders Blomdell committed
638
    for p in paths {
639
        let path = std::path::Path::new(p);
Anders Blomdell's avatar
Anders Blomdell committed
640
        let _ = tx.send(WalkerMessage::Start(path.to_owned()));
Anders Blomdell's avatar
Anders Blomdell committed
641 642 643 644 645
        walk::visit(std::path::Path::new(p), &callback).unwrap();
    }
    tx.send(WalkerMessage::Done).unwrap();
    worker.join().unwrap();
}