diff --git a/Makefile b/Makefile
index 7cba9ad43ed9f908da0db5ecc6bb25c06da123cd..d54b73e53c96492587db1b2a7fd1c898a0e4b3d4 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 94a6db2ec91a96e31f983c3b3454bf22e8db32b6..e7b8318c40f7d1c4f153dbc0e58f96d017982b42 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 07ebf3d8db83eef926202d5265364956dbd2fddb..999563accd62dd37b97864abf9eaceeac31a0dad 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 63fd7d367ae437616976b7003617ce0ab9c8e7e5..f0a17e700e23119fb97feef8fc95f08afeed87fb 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 1a85d6a88362245847da2cdd17ca266a376b4b2d..785f61e327991df9d1be8bf636e904c085b05263 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 0ddb35552a6e7f084bc1846758d4f1feea12af9e..c7d35e351a6cfd33e6d513160628298ee7093ae1 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;
 }