seems to work now! needs more features

This commit is contained in:
Jeffrey Paul 2019-10-03 05:43:02 -07:00
parent d4b3203f03
commit a80397fdf6
3 changed files with 78 additions and 31 deletions

View File

@ -13,7 +13,7 @@ GOFLAGS = -ldflags "$(GOLDFLAGS)"
default: rundebug default: rundebug
rundebug: build rundebug: build
./xsum -d ./xsum -v
run: build run: build
./xsum ./xsum

View File

@ -1,9 +1,15 @@
# xsum # xsum
this stores a file mtime and cryptographically secure content checksum This stores a file mtime, a size, and a cryptographically secure content checksum
in an xattr, so that you can verify that your files aren't corrupted in an xattr, so that you can verify that your files aren't corrupted
on filesystems that are dumb and don't include data checksums (e.g. apfs) on filesystems that are dumb and don't include data checksums (e.g. apfs)
# background
You can dd a few random bytes into the middle of an hfs+ or apfs filesystem,
and, if they land in file data, an fsck/Disk First Aid on the filesystem
will pass with flying colors. There is no file content checksum.
# author # author
sneak <sneak@sneak.berlin> Jeffrey Paul [sneak@sneak.berlin](mailto:sneak@sneak.berlin)

97
main.go
View File

@ -4,12 +4,12 @@ package main
import "crypto/sha256" import "crypto/sha256"
import "fmt" import "fmt"
import "github.com/sirupsen/logrus" import "github.com/jessevdk/go-flags"
import "github.com/multiformats/go-multihash"
import "github.com/mr-tron/base58" import "github.com/mr-tron/base58"
import "github.com/multiformats/go-multihash"
import "github.com/pkg/xattr" import "github.com/pkg/xattr"
import "github.com/sirupsen/logrus"
//import "errors"
import "os" import "os"
import "io" import "io"
import "flag" import "flag"
@ -40,9 +40,21 @@ func xsum() int {
log = logrus.New() log = logrus.New()
log.SetLevel(logrus.ErrorLevel) log.SetLevel(logrus.ErrorLevel)
log.SetReportCaller(false) log.SetReportCaller(false)
debugPtr := flag.Bool("d", false, "debug mode")
flag.Parse() var opts struct {
if *debugPtr == true { // Slice of bool will append 'true' each time the option
// is encountered (can be set multiple times, like -vvv)
Verbose bool `short:"v" long:"verbose" description:"Show verbose debug information"`
}
args, err := flags.Parse(&opts)
if err != nil {
usage()
return -1
}
if opts.Verbose == true {
log.SetReportCaller(true) log.SetReportCaller(true)
log.SetLevel(logrus.DebugLevel) log.SetLevel(logrus.DebugLevel)
} }
@ -55,15 +67,20 @@ func xsum() int {
Builduser, Builduser,
) )
paths := flag.Args() if len(args) < 2 {
if len(paths) > 1 { usage()
paths = paths[1:] return -1
} }
switch flag.Arg(0) {
mode := args[0]
paths := args[1:]
switch mode {
case "cron": case "cron":
x := xsfCheckAndUpdate(paths) x := xsfCheckAndUpdate(paths)
if x != nil { if x != nil {
log.Error(x) log.Debug(x)
return -1 return -1
} else { } else {
return 0 return 0
@ -71,7 +88,7 @@ func xsum() int {
case "check-and-update": case "check-and-update":
x := xsfCheckAndUpdate(paths) x := xsfCheckAndUpdate(paths)
if x != nil { if x != nil {
log.Error(x) log.Debug(x)
return -1 return -1
} else { } else {
return 0 return 0
@ -79,7 +96,7 @@ func xsum() int {
case "check": case "check":
x := xsfCheck(paths) x := xsfCheck(paths)
if x != nil { if x != nil {
log.Error(x) log.Debug(x)
return -1 return -1
} else { } else {
return 0 return 0
@ -87,7 +104,7 @@ func xsum() int {
case "update": case "update":
x := xsfUpdate(paths) x := xsfUpdate(paths)
if x != nil { if x != nil {
log.Error(x) log.Debug(x)
return -1 return -1
} else { } else {
return 0 return 0
@ -99,7 +116,7 @@ func xsum() int {
} }
func usage() { func usage() {
fmt.Fprintf(os.Stderr, "usage: %s [-d] <update|check|check-and-update|cron> <path> [path2] [...]\n", os.Args[0]) fmt.Fprintf(os.Stderr, "usage: %s [-v] <update|check|check-and-update|cron> <path> [path2] [...]\n", os.Args[0])
flag.PrintDefaults() flag.PrintDefaults()
} }
@ -109,7 +126,10 @@ func xsfCheck(paths []string) error {
x := newXsf(path) x := newXsf(path)
err := x.Check() err := x.Check()
if err != nil { if err != nil {
fmt.Printf("%s\tERROR (expected=%s actual=%s)\n", x.path, x.xmultihash, x.multihash)
return err return err
} else {
fmt.Printf("%s\tOK (hash=%s)\n", x.path, x.multihash)
} }
} }
return nil return nil
@ -125,8 +145,9 @@ func xsfUpdate(paths []string) error {
x := newXsf(path) x := newXsf(path)
err := x.Update() err := x.Update()
if err != nil { if err != nil {
showError(err) failure := fmt.Errorf("%s\tERROR (error=%s)\n", x.path, err)
return err log.Error(failure)
return failure
} }
} }
return nil return nil
@ -134,9 +155,12 @@ func xsfUpdate(paths []string) error {
func xsfCheckAndUpdate(paths []string) error { func xsfCheckAndUpdate(paths []string) error {
log.Debugf("check-and-update") log.Debugf("check-and-update")
r := xsfCheck(paths) err := xsfCheck(paths)
if r != nil { if err != nil {
return r //xsfCheck() does the printing of errors itself, we just need to
//bubble it up and not update
log.Error(err)
return err
} }
return xsfUpdate(paths) return xsfUpdate(paths)
} }
@ -252,12 +276,10 @@ func (x *xsf) readSizeXattr() error {
if err != nil { if err != nil {
return err return err
} }
a, b := strconv.ParseInt(string(v), 10, 64) a, b := strconv.ParseInt(string(v), 10, 64)
if b != nil { if b != nil {
return b return b
} }
x.xsize = uint64(a) x.xsize = uint64(a)
return nil return nil
} }
@ -358,8 +380,8 @@ func (x *xsf) Check() error {
actualHash := x.multihash actualHash := x.multihash
if predictedHash != actualHash { if predictedHash != actualHash {
log.Errorf("file corruption detected: expected=%s actual=%s", predictedHash, actualHash) failure := fmt.Errorf("file corruption detected: expected=%s actual=%s", predictedHash, actualHash)
return err return failure
} else { } else {
log.Infof("file OK hash=%s", actualHash) log.Infof("file OK hash=%s", actualHash)
return nil return nil
@ -386,22 +408,37 @@ func (x *xsf) missingXattrs() bool {
} }
func (x *xsf) needsUpdate() bool { func (x *xsf) needsUpdate() bool {
log.Debugf("checking if file needs update")
// this expects stat() to have been called on the xsf // this expects stat() to have been called on the xsf
// by Update already, so we have x.mtime et al populated from the // by Update already, so we have x.mtime et al populated from the
// filesystem // filesystem
// if the file doesn't have all 3 xattrs, it needs an update. // if the file doesn't have all 3 xattrs, it needs an update.
if x.missingXattrs() == false { if x.missingXattrs() == true {
log.Debugf("file is missing xattrs")
return true return true
} }
// if the size doesn't match, it needs an update // if the size doesn't match, it needs an update
e := x.readSizeXattr()
if e != nil {
log.Debugf("unable to read file size attribute")
return true
}
if x.size != x.xsize { if x.size != x.xsize {
log.Debugf("file needs update, size is %s, xattr size is %s", x.size, x.xsize) log.Debugf("file needs update, size is %s, xattr size is %s", x.size, x.xsize)
return true return true
} }
// if the mtime is not the same, it needs an update // if the mtime is not the same, it needs an update
e2 := x.readMtimeXattr()
if e2 != nil {
log.Debugf("unable to read file mtime attribute")
return true
}
if x.mtime != x.xmtime { if x.mtime != x.xmtime {
log.Debugf("file needs update, mtime is %s, xattr mtime is %s", x.mtime, x.xmtime) log.Debugf("file needs update, mtime is %s, xattr mtime is %s", x.mtime, x.xmtime)
return true return true
@ -419,13 +456,17 @@ func (x *xsf) Update() error {
return err return err
} }
//FIXME check if size/mtime are defined first
//and skip update if match (and key 'multihash' exists)
if err = x.stat(); err != nil { if err = x.stat(); err != nil {
return err return err
} }
// reminder: needsUpdate() must be called after stat() so that the
// struct is populated
if x.needsUpdate() == false {
log.Debugf("skipping update on already hashed file %s", x.path)
return nil
}
if err = x.hash(); err != nil { if err = x.hash(); err != nil {
return err return err
} }