From 8526ada201b6d65a46e9ecebedc342e00d2393d8 Mon Sep 17 00:00:00 2001 From: Anders Blomdell <anders.blomdell@control.lth.se> Date: Wed, 26 Apr 2017 13:06:41 +0200 Subject: [PATCH] Added xattr handling --- Makefile | 3 + src/hash.rs | 38 ++++--- src/libhash.sh | 11 +- src/main.rs | 265 +++++++++++++++++++++++++++++++++--------------- src/md5sum.c | 257 ++++++++++++++++------------------------------ src/sha512sum.c | 101 ++++++++++++++++-- 6 files changed, 398 insertions(+), 277 deletions(-) diff --git a/Makefile b/Makefile index 7cba9ad..d54b73e 100644 --- a/Makefile +++ b/Makefile @@ -23,3 +23,6 @@ $(OUT_DIR)libhash.a(%.o): $(OUT_DIR)%.o $(OUT_DIR)%.o: src/%.c Makefile gcc -fPIC -Wall -Werror -c -o $@ $< + +$(OUT_DIR)md5sum.o: ./src/libhash.h +$(OUT_DIR)sha512sum.o: ./src/libhash.h diff --git a/src/hash.rs b/src/hash.rs index 94a6db2..e7b8318 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -1,22 +1,31 @@ -use libc::{c_char, c_int, size_t}; +use libc::{c_char, c_int, size_t, time_t}; use std::ffi::CString; use std::os::unix::ffi::OsStrExt; use std::path::Path; use std::io::{Error, Result}; +use std::os::unix::fs::{MetadataExt}; +use std::fs::{Metadata}; #[link(name = "hash")] extern { fn hash_md5_file(path: *const c_char, + mtime: time_t, + mtime_nsec: i64, flags: c_int, + maxage: time_t, hash: &mut [u8;16]) -> c_int; } pub fn md5_file(path: &Path, - flags: c_int) -> Result<String> + metadata: Metadata, + flags: c_int, + maxage: time_t) -> Result<String> { let mut hash: [u8;16] = [0;16]; let cstring = CString::new(path.as_os_str().as_bytes()).unwrap(); - if unsafe { hash_md5_file(cstring.as_ptr(), flags, &mut hash) } < 0 { + if unsafe { hash_md5_file(cstring.as_ptr(), + metadata.mtime(), metadata.mtime_nsec(), flags, + maxage, &mut hash) } < 0 { Err(Error::last_os_error()) } else { let s: Vec<String> = hash.iter().map( @@ -29,12 +38,10 @@ pub fn md5_file(path: &Path, extern { fn hash_md5_buf(buf: *const c_char, length: size_t, - flags: c_int, hash: &mut [u8;16]) -> c_int; } -pub fn md5_symlink(path: &Path, - flags: c_int) -> Result<String> +pub fn md5_symlink(path: &Path) -> Result<String> { match path.read_link() { Ok(symlink) => { @@ -42,7 +49,7 @@ pub fn md5_symlink(path: &Path, let length = cstring.to_bytes().len(); let ptr = cstring.as_ptr(); let mut hash: [u8;16] = [0;16]; - if unsafe { hash_md5_buf(ptr, length, flags, &mut hash) } < 0 { + if unsafe { hash_md5_buf(ptr, length, &mut hash) } < 0 { Err(Error::last_os_error()) } else { let s: Vec<String> = hash.iter().map( @@ -57,16 +64,23 @@ pub fn md5_symlink(path: &Path, #[link(name = "hash")] extern { fn hash_sha512_file(path: *const c_char, + mtime: time_t, + mtime_nsec: i64, flags: c_int, + maxage: time_t, hash: &mut [u8;64]) -> c_int; } pub fn sha512_file(path: &Path, - flags: c_int) -> Result<String> + metadata: Metadata, + flags: c_int, + maxage: time_t) -> Result<String> { let mut hash: [u8;64] = [0;64]; let cstring = CString::new(path.as_os_str().as_bytes()).unwrap(); - if unsafe { hash_sha512_file(cstring.as_ptr(), flags, &mut hash) } < 0 { + if unsafe { hash_sha512_file(cstring.as_ptr(), + metadata.mtime(), metadata.mtime_nsec(), flags, + maxage, &mut hash) } < 0 { Err(Error::last_os_error()) } else { let s: Vec<String> = hash.iter().map( @@ -79,12 +93,10 @@ pub fn sha512_file(path: &Path, extern { fn hash_sha512_buf(buf: *const c_char, length: size_t, - flags: c_int, hash: &mut [u8;64]) -> c_int; } -pub fn sha512_symlink(path: &Path, - flags: c_int) -> Result<String> +pub fn sha512_symlink(path: &Path) -> Result<String> { match path.read_link() { Ok(symlink) => { @@ -92,7 +104,7 @@ pub fn sha512_symlink(path: &Path, let length = cstring.to_bytes().len(); let ptr = cstring.as_ptr(); let mut hash: [u8;64] = [0;64]; - if unsafe { hash_sha512_buf(ptr, length, flags, &mut hash) } < 0 { + if unsafe { hash_sha512_buf(ptr, length, &mut hash) } < 0 { Err(Error::last_os_error()) } else { let s: Vec<String> = hash.iter().map( diff --git a/src/libhash.sh b/src/libhash.sh index 07ebf3d..999563a 100755 --- a/src/libhash.sh +++ b/src/libhash.sh @@ -9,12 +9,13 @@ fi ## FLAGS_WRITE_XATTR std::os::raw::c_int 0x0002 ## FLAGS_CLEAR_XATTR std::os::raw::c_int 0x0004 ## FLAGS_MAX_AGE std::os::raw::c_int 0x0008 -## FLAGS_NULL_TERMINATED std::os::raw::c_int 0x0010 -## FLAGS_QUIET std::os::raw::c_int 0x0000 +## FLAGS_NO_CALC_HASH std::os::raw::c_int 0x0010 + +## FLAGS_VERBOSE_MASK std::os::raw::c_int 0xc000 ## FLAGS_VERBOSE0 std::os::raw::c_int 0x0000 -## FLAGS_VERBOSE1 std::os::raw::c_int 0x0020 -## FLAGS_VERBOSE2 std::os::raw::c_int 0x0040 -## FLAGS_VERBOSE3 std::os::raw::c_int 0x0060 +## FLAGS_VERBOSE1 std::os::raw::c_int 0x4000 +## FLAGS_VERBOSE2 std::os::raw::c_int 0x8000 +## FLAGS_VERBOSE3 std::os::raw::c_int 0xc000 gen_h() { echo "enum {" diff --git a/src/main.rs b/src/main.rs index 63fd7d3..f0a17e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,19 +6,22 @@ use clap::{Arg, App}; use std::os::unix::fs::{FileTypeExt, MetadataExt}; use std::fs::{Metadata}; use std::path::{Path, PathBuf}; -use std::sync::mpsc::{channel, Receiver}; +use std::sync::mpsc::{channel, Receiver, Sender}; use std::thread; use std::fmt; use std::process::exit; use threadpool::ThreadPool; use std::collections::HashMap; use std::cell::RefCell; -use libc::c_int; +use libc::{c_int, time_t}; use std::result::Result; use std::io::Error as IOError; use std::io::{self, Write}; mod libhash; +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}; mod hash; use hash::{md5_file, md5_symlink, sha512_file, sha512_symlink}; mod walk; @@ -28,6 +31,25 @@ macro_rules! reportln { ($($arg:tt)*) => (writeln!(io::stderr(), $($arg)*).unwrap_or(())); } +// 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(_) => { + println!("Argument '--{} {}' cant be parsed as '{}'", + $v, v, stringify!($t)); + exit(1); + } + } + } else { + Option::None + } + ) +} + +#[derive(Clone)] struct Inode { path: PathBuf, metadata: Option<Metadata> @@ -39,8 +61,8 @@ impl std::fmt::Debug for Inode { }} enum WalkerMessage { - Start{path: PathBuf}, - Inode{path: PathBuf, metadata: Metadata}, + Start(PathBuf), + Inode(Inode), //path: PathBuf, metadata: Metadata}, Done } @@ -67,17 +89,17 @@ enum HashOption<T, E> { #[derive(Debug)] struct HashEntry { - inode : Inode, - kind : char, - md5 : RefCell<HashOption<String, std::io::Error>>, - sha512 : RefCell<HashOption<String, std::io::Error>> + inode: Inode, + kind: char, + md5: RefCell<HashOption<String, std::io::Error>>, + sha512: RefCell<HashOption<String, std::io::Error>> } #[derive(Debug)] struct WorkList { - first : usize, - last : usize, - list : HashMap<usize, HashEntry> + first: usize, + last: usize, + list: HashMap<usize, HashEntry> } #[allow(dead_code)] @@ -118,28 +140,97 @@ impl WorkList { } } -fn options_to_flags(options: &clap::ArgMatches) -> c_int { +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) { 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"); - let mut flags : c_int = 0; - match (xattr, read_xattr, write_xattr, clear_xattr) { - (false, false, false, false) => { - flags = 0; + 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; }, - (true, false, false, false) | - (false, true, true, false) => { - flags |= libhash::FLAGS_READ_XATTR | libhash::FLAGS_WRITE_XATTR; + (false, true, false, false, Some(maxage)) => { + flags = FLAGS_READ_XATTR | FLAGS_MAX_AGE; + age = maxage; }, - (false, false, false, true) => { - flags |= libhash::FLAGS_CLEAR_XATTR; + (false, false, false, true, None) => { + flags = FLAGS_CLEAR_XATTR | FLAGS_NO_CALC_HASH; }, - (_, _, _, _) => { + (_, _, _, _, Some(age)) => { + panic!("--maxage={} specified without sane xattr flags", age); + }, + (_, _, _, _, _) => { panic!("Invalid flags"); } } - flags + 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) } fn file_type_to_flag(file_type: &std::fs::FileType) -> char { @@ -155,31 +246,14 @@ fn file_type_to_flag(file_type: &std::fs::FileType) -> char { fn dispatcher(options: clap::ArgMatches, from_walker: Receiver<WalkerMessage>) { - macro_rules! parse_opt { - ($n:expr, $t:ty) => ( - if let Some(v) = options.value_of($n) { - match v.parse::<$t>() { - Ok(val) => Option::Some(val), - Err(_) => { - println!("Argument '--{} {}' cant be parsed as '{}'", - $n, v, stringify!($t)); - exit(1); - } - } - } else { - Option::None - } - ) - }; - let jobs = parse_opt!("jobs", usize).unwrap_or(1); - let lookahead = parse_opt!("lookahead", usize).unwrap_or(jobs * 10); + 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); let calc_md5 = options.is_present("md5"); let calc_sha512 = options.is_present("sha512"); - let flags = options_to_flags(&options); - let mut pending = 0 as usize; - let pool = ThreadPool::new(jobs); + let (flags, maxage) = options_to_flags(&options); + let mut pool = WorkerPool::<WorkerMessage>::new(jobs); let mut worklist = WorkList::new(); - let (worker_to_dispatcher, from_pool) = channel::<WorkerMessage>(); let mut done = false; print!("#fields: size:mtime:uid:gid:mode:"); if calc_md5 { print!("md5:"); } @@ -189,14 +263,14 @@ fn dispatcher(options: clap::ArgMatches, // reportln!("XXX {} {} {}", done, pending, worklist.used()); if done && worklist.used() == 0 { break; - } if !done && pending < jobs && worklist.used() < lookahead { - // Get paths to checksum + } if !done && pool.free() && worklist.used() < lookahead { + // Get paths from walker and enqueue work let message = match from_walker.recv() { Ok(message) => message, Err(_) => break }; match message { - WalkerMessage::Start{path} => { + WalkerMessage::Start(path) => { worklist.push_back(HashEntry { inode: Inode { path:path.clone(), @@ -207,8 +281,10 @@ fn dispatcher(options: clap::ArgMatches, sha512: RefCell::new(HashOption::Unused), }); }, - WalkerMessage::Inode{path, metadata} => { + WalkerMessage::Inode(inode) => { // Push to worklist + let path = inode.path; + let metadata = inode.metadata.unwrap(); use HashOption::{Pending, Unused, None}; let (kind, md5, sha512) = match file_type_to_flag(&metadata.file_type()) { @@ -233,34 +309,58 @@ fn dispatcher(options: clap::ArgMatches, sha512: RefCell::new(sha512), }); - // Spawn off needed work - macro_rules! execute { - ($guard:expr, $func:expr, $message:ident) => ( - if $guard { - let path = path.clone(); - let tx = worker_to_dispatcher.clone(); - let flags = flags; - pending += 1; - pool.execute(move || { - let h = $func(path.as_path(), flags); - tx.send( WorkerMessage { - index: index, - hash:$message(h) - }).unwrap(); - }); - } - ) - }; match kind { 'F' => { use WorkerMessageHash::{MD5, SHA512}; - execute!(calc_md5, md5_file, MD5); - execute!(calc_sha512, sha512_file, SHA512); + 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) + } + } + }); }, 'L' => { use WorkerMessageHash::{MD5, SHA512}; - execute!(calc_md5, md5_symlink, MD5); - execute!(calc_sha512, sha512_symlink, SHA512); + 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) + } + } + }); + }, _ => () }; @@ -268,12 +368,11 @@ fn dispatcher(options: clap::ArgMatches, WalkerMessage::Done => done = true, } - } else if pending > 0 { - // Handle paths being processed - let message = from_pool.recv().unwrap(); + } else if pool.pending > 0 { + // Handle finished results + let message = pool.recv(); let index = message.index; let item = worklist.get(index).unwrap(); - pending -= 1; use WorkerMessageHash::{MD5,SHA512}; match message.hash { MD5(hash) => { @@ -312,8 +411,11 @@ fn dispatcher(options: clap::ArgMatches, let error = match ( md5, sha512 ) { (&Err(ref e @ _), _) => Option::Some(e), (_, &Err(ref e @ _)) => Option::Some(e), - (_,_) => Option::None + (_, _) => Option::None }; + if flags & FLAGS_CLEAR_XATTR != 0 { + continue; + } match error { Option::Some(e) => { // Failed to checksum file, report and drop @@ -412,8 +514,13 @@ fn main() { .help("Calculate SHA512 hash")) .arg(Arg::with_name("verbose") .short("v") + .conflicts_with("quiet") .multiple(true) .help("Sets the level of verbosity")) + .arg(Arg::with_name("quiet") + .short("q") + .conflicts_with("verbose") + .help("Sets the level of verbosity")) .arg(Arg::with_name("max_age") .short("m") .long("max-age") @@ -434,15 +541,15 @@ fn main() { (worker, tx) }; let callback = | p:&Path, m:&Metadata | { - tx.send( WalkerMessage::Inode{ - path:p.to_owned(), metadata:m.clone()}).unwrap(); + tx.send( WalkerMessage::Inode( + Inode{path:p.to_owned(), metadata:Some(m.clone())})).unwrap(); }; let paths : Vec<_> = matches.values_of_os("PATH").unwrap().collect(); // let mut paths : Vec<_> = matches.values_of_os("PATH").unwrap().collect(); // paths.sort(); for p in paths { let path = std::path::Path::new(p); - let _ = tx.send(WalkerMessage::Start{path: path.to_owned()}); + let _ = tx.send(WalkerMessage::Start(path.to_owned())); walk::visit(std::path::Path::new(p), &callback).unwrap(); } tx.send(WalkerMessage::Done).unwrap(); diff --git a/src/md5sum.c b/src/md5sum.c index 1a85d6a..785f61e 100644 --- a/src/md5sum.c +++ b/src/md5sum.c @@ -2,21 +2,31 @@ #include <string.h> #include <sys/types.h> #include <sys/stat.h> +#include <sys/xattr.h> #include <fcntl.h> #include <time.h> #include <unistd.h> #include <openssl/md5.h> #include <errno.h> #include <limits.h> +#include <stdint.h> +#include "libhash.h" #define BLOCKSIZE 4096 +#define XATTR_MD5HASH "trusted.md5.control.lth.se" + + +struct __attribute__((__packed__)) hash_attr { + time_t hash_time; + time_t mtime; + uint32_t mtime_nsec; + uint8_t hash[MD5_DIGEST_LENGTH]; +}; int hash_md5_buf(char *buf, size_t length, - int flags, unsigned char *hash) { - MD5_CTX ctx; MD5_Init(&ctx); @@ -27,14 +37,66 @@ int hash_md5_buf(char *buf, } int hash_md5_file(char *filename, + time_t mtime, + int64_t mtime_nsec, int flags, + time_t maxage, unsigned char *hash) { int fd; + int result = 0, recalc = 1; + struct hash_attr hash_attr; + + if (flags & FLAGS_CLEAR_XATTR) { + if (removexattr(filename, XATTR_MD5HASH) != 0) { + if (errno != ENODATA) { + result = -1; + fprintf(stderr, "Failed to remove xattr '%s'\n", filename); + goto out; + } else if ((flags & FLAGS_VERBOSE_MASK) > FLAGS_VERBOSE2) { + fprintf(stderr, "No xattr for '%s'\n", filename); + } + } else { + if ((flags & FLAGS_VERBOSE_MASK) > FLAGS_VERBOSE2) { + fprintf(stderr, "Removed xattr for '%s'\n", filename); + } + } + } + if (flags & FLAGS_NO_CALC_HASH) { + // We should not calculate hash + goto out; + } + + if (flags & FLAGS_READ_XATTR) { + int err; + + err = getxattr(filename, XATTR_MD5HASH, &hash_attr, sizeof(hash_attr)); + if (err < 0 && errno != ENODATA) { + result = -1; + goto out; + } + if (err != sizeof(hash_attr)) { + recalc = 1; + } else if (le64toh(hash_attr.mtime) != mtime) { + recalc = 1; + } else if (le32toh(hash_attr.mtime_nsec) != mtime_nsec) { + recalc = 1; + } else if (flags & FLAGS_MAX_AGE && le64toh(hash_attr.hash_time) < maxage) { + recalc = 1; + } else { + recalc = 0; + memcpy(hash, hash_attr.hash, MD5_DIGEST_LENGTH); + } + } + + if (recalc == 0) { + goto out; + } fd = open (filename, O_RDONLY); if (fd < 0) { - return -1; + result = -1; + goto out; } else { MD5_CTX ctx; unsigned char buffer[BLOCKSIZE]; @@ -48,180 +110,31 @@ int hash_md5_file(char *filename, } MD5_Final(hash, &ctx); - goto out_close; - } -out_close: - close(fd); -#if 0 - char digest[128]; - int length = 0, recalc = 1; - char md5[33]; - - if (options->flags & FLAGS_CLEAR_XATTR) { - if (removexattr(filename, XATTR_MD5SUM) != 0) { - if (errno != ENOATTR) { - fprintf(stderr, "Failed to remove xattr '%s'\n", filename); - } - } else if (options->flags & FLAGS_VERBOSE) { - fprintf(stderr, "Removing xattr for '%s'\n", filename); + if (count < 0) { + result = -1; + fremovexattr(fd, XATTR_MD5HASH); + goto out_close; } - goto out; - } - if (options->flags & FLAGS_READ_XATTR) { - length = getxattr(filename, XATTR_MD5SUM, digest, 128); - } - if (length > 0) { -#define FORMAT "t=%" PRIu64 ",mtime=%" PRIu64 ".%" PRIu32 ",md5=%32s" - uint64_t t; - struct { - uint64_t tv_sec; - uint32_t tv_nsec; - } mtime; - int items = sscanf(digest, FORMAT, &t, &mtime.tv_sec, &mtime.tv_nsec, md5); - if (items == 4 && - ((options->flags & FLAGS_MAX_AGE) == 0 || t >= options->max_age) && - mtime.tv_sec == buf.st_mtim.tv_sec && - mtime.tv_nsec == buf.st_mtim.tv_nsec) { - recalc = 0; - } else { - if (removexattr(filename, XATTR_MD5SUM) != 0) { - if (errno != ENOATTR) { - fprintf(stderr, "Failed to remove xattr '%s' (mtime)\n", filename); - } - } else if (options->flags & FLAGS_VERBOSE) { - fprintf(stderr, "Removing xattr for '%s'\n", filename); - } - } - } - if (recalc) { - FILE *f; - unsigned char sum[16]; - f = fopen (filename, "r"); - if (!f) { - sprintf(md5, "_unreadable_"); - if (options->flags & (FLAGS_READ_XATTR|FLAGS_WRITE_XATTR)) { - /* Only remove md5sum for unreadble file if we are root */ - if (removexattr(filename, XATTR_MD5SUM) != 0) { - if (errno != ENOATTR) { - fprintf(stderr, "Failed to remove xattr '%s' (unreadable)\n", - filename); - } - } else if (options->flags & FLAGS_VERBOSE) { - fprintf(stderr, "Removing xattr for '%s'\n", filename); - } - } - } else { - int i; - time_t now; + if (flags & FLAGS_WRITE_XATTR) { + int err; - time(&now); - - md5_stream(f, &sum); - for (i = 0 ; i < 16 ; i++) { - sprintf(&md5[i*2], "%2.2x", sum[i]); - } - fclose(f); - if (options->flags & FLAGS_WRITE_XATTR) { - int len = snprintf(digest, sizeof(digest), - "t=%" PRIu64 ",mtime=%" PRIu64 ".%9.9u,md5=%32s", - (uint64_t)now, - (uint64_t)buf.st_mtim.tv_sec, - (uint32_t) buf.st_mtim.tv_nsec, - md5); - if (setxattr(filename, XATTR_MD5SUM, digest, len, 0) != 0) { - fprintf(stderr, "Failed to set xattr '%s'\n", filename); - } else if (options->flags & FLAGS_VERBOSE) { - fprintf(stderr, "Setting xattr for '%s'\n", filename); - } - } - } - } - printentry(buf.st_size, buf.st_mtime, - buf.st_uid, buf.st_gid, buf.st_mode, - md5, 'F', filename); -out: - ; -} - -void dofile(char *filename, dev_t *dev, struct options *options) -{ - struct stat64 buf; - int err; - - if (getuid() != 0 && - faccessat(AT_FDCWD, filename, R_OK, AT_SYMLINK_NOFOLLOW) != 0) { - /* Bail out if no access */ - fprintf(stderr, "Failed to access '%s'\n", filename); - goto out; - } - err = lstat64(filename, &buf); - if (err == 0 && dev == 0) { dev = &buf.st_dev; } - if (err != 0) { - fprintf(stderr, "Failed to lstat '%s'\n", filename); - } else if (*dev != buf.st_dev) { - fprintf(stderr, "skipping mountpoint '%s'\n", filename); - } else if (filenameislegal(filename)) { - if (S_ISREG(buf.st_mode)) { - md5sum_file(filename, buf, options); - } else if (S_ISDIR(buf.st_mode)) { - if (! (options->flags & FLAGS_CLEAR_XATTR)) { - printentry(0, 0, buf.st_uid, buf.st_gid, buf.st_mode, - "", 'D', filename); - } - if (filename[0] == 0) { filename = "."; } - dodir(filename, dev, options); - } else if (options->flags & FLAGS_CLEAR_XATTR) { - goto out; - } else if (S_ISLNK(buf.st_mode)) { - char *link = malloc(buf.st_size + 1); - err = readlink(filename, link, buf.st_size); - if (err != buf.st_size) { - fprintf(stderr, "failed to readlink: %s\n", filename); - } else { - char md5[33]; - unsigned char sum[16]; - int i; - - md5_buffer(link, buf.st_size, &sum); - for (i = 0 ; i < 16 ; i++) { - sprintf(&md5[i*2], "%2.2x", sum[i]); - } - printentry(buf.st_size, buf.st_mtime, - buf.st_uid, buf.st_gid, buf.st_mode, - md5, 'L', filename); + hash_attr.hash_time = htole64(now); + hash_attr.mtime = htole64(mtime); + hash_attr.mtime_nsec = htole32(mtime_nsec); + memcpy(hash_attr.hash, hash, MD5_DIGEST_LENGTH); + err = fsetxattr(fd, XATTR_MD5HASH, &hash_attr, sizeof(hash_attr), 0); + if (err < 0) { + result = -1; + fremovexattr(fd, XATTR_MD5HASH); + goto out_close; } - free(link); - } else { - char kind; - - switch(buf.st_mode & S_IFMT) { - case S_IFSOCK: { kind = 'S'; } break; - case S_IFBLK: { kind = 'B'; } break; - case S_IFCHR: { kind = 'C'; } break; - case S_IFIFO: { kind = 'F'; } break; - default: { kind = 'U'; } - } - printentry(buf.st_size, buf.st_mtime, - buf.st_uid, buf.st_gid, buf.st_mode, - "", kind, filename); + } else if (flags & FLAGS_READ_XATTR) { + fremovexattr(fd, XATTR_MD5HASH); } } +out_close: + close(fd); out: - ; -#endif - -/* - if (options->flags & FLAGS_READ_XATTR) { - length = getxattr(filename, XATTR_MD5SUM, digest, 128); - } -int MD5_Init(MD5_CTX *c); - int MD5_Update(MD5_CTX *c, const void *data, - unsigned long len); - int MD5_Final(unsigned char *md, MD5_CTX *c); -char data[] = "data to hash"; -char hash[SHA512_DIGEST_LENGTH]; -SHA512(data, sizeof(data) - 1, hash); -*/ - return 0; + return result; } diff --git a/src/sha512sum.c b/src/sha512sum.c index 0ddb355..c7d35e3 100644 --- a/src/sha512sum.c +++ b/src/sha512sum.c @@ -2,19 +2,30 @@ #include <string.h> #include <sys/types.h> #include <sys/stat.h> +#include <sys/xattr.h> #include <fcntl.h> #include <time.h> #include <unistd.h> #include <openssl/sha.h> #include <errno.h> #include <limits.h> +#include <stdint.h> +#include "libhash.h" #define BLOCKSIZE 4096 +#define XATTR_SHA512HASH "trusted.sha512.control.lth.se" + + +struct __attribute__((__packed__)) hash_attr { + time_t hash_time; + time_t mtime; + uint32_t mtime_nsec; + uint8_t hash[SHA512_DIGEST_LENGTH]; +}; int hash_sha512_buf(char *buf, - size_t length, - int flags, - unsigned char *hash) + size_t length, + unsigned char *hash) { SHA512_CTX ctx; @@ -26,14 +37,66 @@ int hash_sha512_buf(char *buf, } int hash_sha512_file(char *filename, - int flags, - unsigned char *hash) + time_t mtime, + int64_t mtime_nsec, + int flags, + time_t maxage, + unsigned char *hash) { int fd; + int result = 0, recalc = 1; + struct hash_attr hash_attr; + + if (flags & FLAGS_CLEAR_XATTR) { + if (removexattr(filename, XATTR_SHA512HASH) != 0) { + if (errno != ENODATA) { + result = -1; + fprintf(stderr, "Failed to remove xattr '%s'\n", filename); + goto out; + } else if ((flags & FLAGS_VERBOSE_MASK) > FLAGS_VERBOSE2) { + fprintf(stderr, "No xattr for '%s'\n", filename); + } + } else { + if ((flags & FLAGS_VERBOSE_MASK) > FLAGS_VERBOSE2) { + fprintf(stderr, "Removed xattr for '%s'\n", filename); + } + } + } + if (flags & FLAGS_NO_CALC_HASH) { + // We should not calculate hash + goto out; + } + + if (flags & FLAGS_READ_XATTR) { + int err; + + err = getxattr(filename, XATTR_SHA512HASH, &hash_attr, sizeof(hash_attr)); + if (err < 0 && errno != ENODATA) { + result = -1; + goto out; + } + if (err != sizeof(hash_attr)) { + recalc = 1; + } else if (le64toh(hash_attr.mtime) != mtime) { + recalc = 1; + } else if (le32toh(hash_attr.mtime_nsec) != mtime_nsec) { + recalc = 1; + } else if (flags & FLAGS_MAX_AGE && le64toh(hash_attr.hash_time) < maxage) { + recalc = 1; + } else { + recalc = 0; + memcpy(hash, hash_attr.hash, SHA512_DIGEST_LENGTH); + } + } + + if (recalc == 0) { + goto out; + } fd = open (filename, O_RDONLY); if (fd < 0) { - return -1; + result = -1; + goto out; } else { SHA512_CTX ctx; unsigned char buffer[BLOCKSIZE]; @@ -47,9 +110,31 @@ int hash_sha512_file(char *filename, } SHA512_Final(hash, &ctx); - goto out_close; + if (count < 0) { + result = -1; + fremovexattr(fd, XATTR_SHA512HASH); + goto out_close; + } + + if (flags & FLAGS_WRITE_XATTR) { + int err; + + hash_attr.hash_time = htole64(now); + hash_attr.mtime = htole64(mtime); + hash_attr.mtime_nsec = htole32(mtime_nsec); + memcpy(hash_attr.hash, hash, SHA512_DIGEST_LENGTH); + err = fsetxattr(fd, XATTR_SHA512HASH, &hash_attr, sizeof(hash_attr), 0); + if (err < 0) { + result = -1; + fremovexattr(fd, XATTR_SHA512HASH); + goto out_close; + } + } else if (flags & FLAGS_READ_XATTR) { + fremovexattr(fd, XATTR_SHA512HASH); + } } out_close: close(fd); - return 0; +out: + return result; } -- GitLab