seems to work now! needs more features
This commit is contained in:
parent
d4b3203f03
commit
a80397fdf6
2
Makefile
2
Makefile
@ -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
|
||||||
|
10
README.md
10
README.md
@ -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
97
main.go
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user