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
 | 
			
		||||
 | 
			
		||||
rundebug: build
 | 
			
		||||
	./xsum -d
 | 
			
		||||
	./xsum -v
 | 
			
		||||
 | 
			
		||||
run: build
 | 
			
		||||
	./xsum
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							@ -1,9 +1,15 @@
 | 
			
		||||
# 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
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
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 "fmt"
 | 
			
		||||
import "github.com/sirupsen/logrus"
 | 
			
		||||
import "github.com/multiformats/go-multihash"
 | 
			
		||||
import "github.com/jessevdk/go-flags"
 | 
			
		||||
import "github.com/mr-tron/base58"
 | 
			
		||||
import "github.com/multiformats/go-multihash"
 | 
			
		||||
import "github.com/pkg/xattr"
 | 
			
		||||
import "github.com/sirupsen/logrus"
 | 
			
		||||
 | 
			
		||||
//import "errors"
 | 
			
		||||
import "os"
 | 
			
		||||
import "io"
 | 
			
		||||
import "flag"
 | 
			
		||||
@ -40,9 +40,21 @@ func xsum() int {
 | 
			
		||||
	log = logrus.New()
 | 
			
		||||
	log.SetLevel(logrus.ErrorLevel)
 | 
			
		||||
	log.SetReportCaller(false)
 | 
			
		||||
	debugPtr := flag.Bool("d", false, "debug mode")
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
	if *debugPtr == true {
 | 
			
		||||
 | 
			
		||||
	var opts struct {
 | 
			
		||||
		// 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.SetLevel(logrus.DebugLevel)
 | 
			
		||||
	}
 | 
			
		||||
@ -55,15 +67,20 @@ func xsum() int {
 | 
			
		||||
		Builduser,
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	paths := flag.Args()
 | 
			
		||||
	if len(paths) > 1 {
 | 
			
		||||
		paths = paths[1:]
 | 
			
		||||
	if len(args) < 2 {
 | 
			
		||||
		usage()
 | 
			
		||||
		return -1
 | 
			
		||||
	}
 | 
			
		||||
	switch flag.Arg(0) {
 | 
			
		||||
 | 
			
		||||
	mode := args[0]
 | 
			
		||||
 | 
			
		||||
	paths := args[1:]
 | 
			
		||||
 | 
			
		||||
	switch mode {
 | 
			
		||||
	case "cron":
 | 
			
		||||
		x := xsfCheckAndUpdate(paths)
 | 
			
		||||
		if x != nil {
 | 
			
		||||
			log.Error(x)
 | 
			
		||||
			log.Debug(x)
 | 
			
		||||
			return -1
 | 
			
		||||
		} else {
 | 
			
		||||
			return 0
 | 
			
		||||
@ -71,7 +88,7 @@ func xsum() int {
 | 
			
		||||
	case "check-and-update":
 | 
			
		||||
		x := xsfCheckAndUpdate(paths)
 | 
			
		||||
		if x != nil {
 | 
			
		||||
			log.Error(x)
 | 
			
		||||
			log.Debug(x)
 | 
			
		||||
			return -1
 | 
			
		||||
		} else {
 | 
			
		||||
			return 0
 | 
			
		||||
@ -79,7 +96,7 @@ func xsum() int {
 | 
			
		||||
	case "check":
 | 
			
		||||
		x := xsfCheck(paths)
 | 
			
		||||
		if x != nil {
 | 
			
		||||
			log.Error(x)
 | 
			
		||||
			log.Debug(x)
 | 
			
		||||
			return -1
 | 
			
		||||
		} else {
 | 
			
		||||
			return 0
 | 
			
		||||
@ -87,7 +104,7 @@ func xsum() int {
 | 
			
		||||
	case "update":
 | 
			
		||||
		x := xsfUpdate(paths)
 | 
			
		||||
		if x != nil {
 | 
			
		||||
			log.Error(x)
 | 
			
		||||
			log.Debug(x)
 | 
			
		||||
			return -1
 | 
			
		||||
		} else {
 | 
			
		||||
			return 0
 | 
			
		||||
@ -99,7 +116,7 @@ func xsum() int {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -109,7 +126,10 @@ func xsfCheck(paths []string) error {
 | 
			
		||||
		x := newXsf(path)
 | 
			
		||||
		err := x.Check()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Printf("%s\tERROR (expected=%s actual=%s)\n", x.path, x.xmultihash, x.multihash)
 | 
			
		||||
			return err
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Printf("%s\tOK (hash=%s)\n", x.path, x.multihash)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
@ -125,8 +145,9 @@ func xsfUpdate(paths []string) error {
 | 
			
		||||
		x := newXsf(path)
 | 
			
		||||
		err := x.Update()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			showError(err)
 | 
			
		||||
			return err
 | 
			
		||||
			failure := fmt.Errorf("%s\tERROR (error=%s)\n", x.path, err)
 | 
			
		||||
			log.Error(failure)
 | 
			
		||||
			return failure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
@ -134,9 +155,12 @@ func xsfUpdate(paths []string) error {
 | 
			
		||||
 | 
			
		||||
func xsfCheckAndUpdate(paths []string) error {
 | 
			
		||||
	log.Debugf("check-and-update")
 | 
			
		||||
	r := xsfCheck(paths)
 | 
			
		||||
	if r != nil {
 | 
			
		||||
		return r
 | 
			
		||||
	err := xsfCheck(paths)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		//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)
 | 
			
		||||
}
 | 
			
		||||
@ -252,12 +276,10 @@ func (x *xsf) readSizeXattr() error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a, b := strconv.ParseInt(string(v), 10, 64)
 | 
			
		||||
	if b != nil {
 | 
			
		||||
		return b
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	x.xsize = uint64(a)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@ -358,8 +380,8 @@ func (x *xsf) Check() error {
 | 
			
		||||
	actualHash := x.multihash
 | 
			
		||||
 | 
			
		||||
	if predictedHash != actualHash {
 | 
			
		||||
		log.Errorf("file corruption detected: expected=%s actual=%s", predictedHash, actualHash)
 | 
			
		||||
		return err
 | 
			
		||||
		failure := fmt.Errorf("file corruption detected: expected=%s actual=%s", predictedHash, actualHash)
 | 
			
		||||
		return failure
 | 
			
		||||
	} else {
 | 
			
		||||
		log.Infof("file OK hash=%s", actualHash)
 | 
			
		||||
		return nil
 | 
			
		||||
@ -386,22 +408,37 @@ func (x *xsf) missingXattrs() bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (x *xsf) needsUpdate() bool {
 | 
			
		||||
	log.Debugf("checking if file needs update")
 | 
			
		||||
	// this expects stat() to have been called on the xsf
 | 
			
		||||
	// by Update already, so we have x.mtime et al populated from the
 | 
			
		||||
	// filesystem
 | 
			
		||||
 | 
			
		||||
	// 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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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 {
 | 
			
		||||
		log.Debugf("file needs update, size is %s, xattr size is %s", x.size, x.xsize)
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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 {
 | 
			
		||||
		log.Debugf("file needs update, mtime is %s, xattr mtime is %s", x.mtime, x.xmtime)
 | 
			
		||||
		return true
 | 
			
		||||
@ -419,13 +456,17 @@ func (x *xsf) Update() error {
 | 
			
		||||
		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 {
 | 
			
		||||
		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 {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user