author | Alberto Bertogli
<albertito@blitiri.com.ar> 2023-08-23 17:22:57 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2023-08-23 17:22:57 UTC |
parent | 7722f1f489579fed98ba406023cf946f413d5e11 |
summer.go | +55 | -27 |
test/help.t | +25 | -50 |
test/multiroot.t | +59 | -0 |
diff --git a/summer.go b/summer.go index e7f19e1..479e77f 100644 --- a/summer.go +++ b/summer.go @@ -21,16 +21,19 @@ problems). Not intended to detect malicious modification. Checksums are written to/read from each file's extended attributes. +Paths given can be files or directories. If a directory is given, it is +processed recursively. + Usage: - summer [flags] update <dir> - Verify checksums in the given directory, and update them for new or - changed files. - summer [flags] verify <dir> - Verify checksums in the given directory. - summer [flags] generate <dir> - Write checksums for the given directory. Files with pre-existing - checksums are left untouched, and checksums are not verified. + summer [flags] update <paths> + Verify checksums in the given paths, and update them for new or changed + files. + summer [flags] verify <paths> + Verify checksums in the given paths. + summer [flags] generate <paths> + Write checksums for the given paths. Files with pre-existing checksums + are left untouched, and checksums are not verified. Useful when generating checksums for a lot of files for the first time, as is faster to resume work if interrupted. summer [flags] version @@ -93,9 +96,12 @@ func main() { } op := flag.Arg(0) - root := flag.Arg(1) + roots := []string{} + if flag.NArg() > 1 { + roots = flag.Args()[1:] + } - if op != "version" && root == "" { + if op != "version" && len(roots) == 0 { Usage() os.Exit(1) } @@ -105,11 +111,11 @@ func main() { switch op { case "generate": - err = generate(root) + err = generate(roots) case "verify": - err = verify(root) + err = verify(roots) case "update": - err = update(root) + err = update(roots) case "version": PrintVersion() default: @@ -156,7 +162,7 @@ type ChecksumV1 struct { ModTimeUsec int64 } -func openAndInfo(path string, d fs.DirEntry, err error, rootDev uint64) (bool, *os.File, fs.FileInfo, error) { +func openAndInfo(path string, d fs.DirEntry, err error, rootDev deviceID) (bool, *os.File, fs.FileInfo, error) { // Excluded check must come first, because it can be use to skip // directories that would otherwise cause errors. if isExcluded(path) { @@ -195,11 +201,13 @@ func openAndInfo(path string, d fs.DirEntry, err error, rootDev uint64) (bool, * return true, fd, info, nil } -func getDevice(info fs.FileInfo) uint64 { - return info.Sys().(*syscall.Stat_t).Dev +type deviceID uint64 + +func getDevice(info fs.FileInfo) deviceID { + return deviceID(info.Sys().(*syscall.Stat_t).Dev) } -func getDeviceForPath(path string) uint64 { +func getDeviceForPath(path string) deviceID { fi, err := os.Stat(path) if err != nil { // Doesn't matter, because we'll get an error during WalkDir. @@ -208,8 +216,8 @@ func getDeviceForPath(path string) uint64 { return getDevice(fi) } -func generate(root string) error { - rootDev := getDeviceForPath(root) +func generate(roots []string) error { + rootDev := deviceID(0) p := NewProgress(options.isTTY) defer p.Stop() @@ -249,12 +257,18 @@ func generate(root string) error { return nil } - err := filepath.WalkDir(root, fn) - return err + for _, root := range roots { + rootDev = getDeviceForPath(root) + err := filepath.WalkDir(root, fn) + if err != nil { + return err + } + } + return nil } -func verify(root string) error { - rootDev := getDeviceForPath(root) +func verify(roots []string) error { + rootDev := deviceID(0) p := NewProgress(options.isTTY) defer p.Stop() @@ -301,7 +315,14 @@ func verify(root string) error { return nil } - err := filepath.WalkDir(root, fn) + var err error + for _, root := range roots { + rootDev = getDeviceForPath(root) + err = filepath.WalkDir(root, fn) + if err != nil { + break + } + } if p.corrupted > 0 && err == nil { err = fmt.Errorf("detected %d corrupted files", p.corrupted) @@ -309,8 +330,8 @@ func verify(root string) error { return err } -func update(root string) error { - rootDev := getDeviceForPath(root) +func update(roots []string) error { + rootDev := deviceID(0) p := NewProgress(options.isTTY) defer p.Stop() @@ -362,7 +383,14 @@ func update(root string) error { return nil } - err := filepath.WalkDir(root, fn) + var err error + for _, root := range roots { + rootDev = getDeviceForPath(root) + err = filepath.WalkDir(root, fn) + if err != nil { + break + } + } if p.corrupted > 0 && err == nil { err = fmt.Errorf("detected %d corrupted files", p.corrupted) diff --git a/test/help.t b/test/help.t index 4d5cea6..d3b11a1 100644 --- a/test/help.t +++ b/test/help.t @@ -12,16 +12,19 @@ No arguments. Checksums are written to/read from each file's extended attributes. + Paths given can be files or directories. If a directory is given, it is + processed recursively. + Usage: - summer [flags] update <dir> - Verify checksums in the given directory, and update them for new or - changed files. - summer [flags] verify <dir> - Verify checksums in the given directory. - summer [flags] generate <dir> - Write checksums for the given directory. Files with pre-existing - checksums are left untouched, and checksums are not verified. + summer [flags] update <paths> + Verify checksums in the given paths, and update them for new or changed + files. + summer [flags] verify <paths> + Verify checksums in the given paths. + summer [flags] generate <paths> + Write checksums for the given paths. Files with pre-existing checksums + are left untouched, and checksums are not verified. Useful when generating checksums for a lot of files for the first time, as is faster to resume work if interrupted. summer [flags] version @@ -51,16 +54,19 @@ Too few arguments. Checksums are written to/read from each file's extended attributes. + Paths given can be files or directories. If a directory is given, it is + processed recursively. + Usage: - summer [flags] update <dir> - Verify checksums in the given directory, and update them for new or - changed files. - summer [flags] verify <dir> - Verify checksums in the given directory. - summer [flags] generate <dir> - Write checksums for the given directory. Files with pre-existing - checksums are left untouched, and checksums are not verified. + summer [flags] update <paths> + Verify checksums in the given paths, and update them for new or changed + files. + summer [flags] verify <paths> + Verify checksums in the given paths. + summer [flags] generate <paths> + Write checksums for the given paths. Files with pre-existing checksums + are left untouched, and checksums are not verified. Useful when generating checksums for a lot of files for the first time, as is faster to resume work if interrupted. summer [flags] version @@ -82,40 +88,9 @@ Too few arguments. No valid path (the argument is given, but it is empty). - $ summer weifmws "" - # summer 🌞 🏖 - - Utility to detect accidental data corruption (e.g. bitrot, storage media - problems). Not intended to detect malicious modification. - - Checksums are written to/read from each file's extended attributes. - - Usage: - - summer [flags] update <dir> - Verify checksums in the given directory, and update them for new or - changed files. - summer [flags] verify <dir> - Verify checksums in the given directory. - summer [flags] generate <dir> - Write checksums for the given directory. Files with pre-existing - checksums are left untouched, and checksums are not verified. - Useful when generating checksums for a lot of files for the first time, - as is faster to resume work if interrupted. - summer [flags] version - Print software version information. - - Flags: - -exclude value - \texclude these paths (can be repeated) (esc) - -excludere value - \texclude paths matching this regexp (can be repeated) (esc) - -forcetty - \tforce TTY output (esc) - -n\tdry-run mode (do not write anything) (esc) - -q\tquiet mode (esc) - -v\tverbose mode (list each file) (esc) - -x\tdon't cross filesystem boundaries (esc) + $ summer verify "" + 0s: 0 matched, 0 modified, 0 new, 0 corrupted + lstat : no such file or directory [1] diff --git a/test/multiroot.t b/test/multiroot.t new file mode 100644 index 0000000..4253d7a --- /dev/null +++ b/test/multiroot.t @@ -0,0 +1,59 @@ + +Tests for handling multiple roots on the same command invocation. + + $ alias summer="$TESTDIR/../summer" + $ mkdir A B C + $ touch A/a1 A/a2 B/b1 C/c1 + + $ summer generate A B C + 0s: 0 matched, 0 modified, 4 new, 0 corrupted + $ summer update A B C + 0s: 4 matched, 0 modified, 0 new, 0 corrupted + $ summer verify A B C + 0s: 4 matched, 0 modified, 0 new, 0 corrupted + + +Test that individual files work well as roots (common use case). + + $ touch B/b2 + $ summer generate A/* B/* C/c1 + 0s: 0 matched, 0 modified, 1 new, 0 corrupted + $ summer update A/* B/* C/c1 + 0s: 5 matched, 0 modified, 0 new, 0 corrupted + $ summer verify A/* B/* C/c1 + 0s: 5 matched, 0 modified, 0 new, 0 corrupted + + +Check the order is as expected. + + $ summer -v update A B C + "A/a1": match \(checksum:0, mtime:\d+\) (re) + "A/a2": match \(checksum:0, mtime:\d+\) (re) + "B/b1": match \(checksum:0, mtime:\d+\) (re) + "B/b2": match \(checksum:0, mtime:\d+\) (re) + "C/c1": match \(checksum:0, mtime:\d+\) (re) + 0s: 5 matched, 0 modified, 0 new, 0 corrupted + + +Check how we handle getting an error in the middle. + + $ chmod 0000 B/b1 + + $ summer -v verify A B C + "A/a1": match \(checksum:0, mtime:\d+\) (re) + "A/a2": match \(checksum:0, mtime:\d+\) (re) + 0s: 2 matched, 0 modified, 0 new, 0 corrupted + open B/b1: permission denied + [1] + + $ summer -v update A B C + "A/a1": match \(checksum:0, mtime:\d+\) (re) + "A/a2": match \(checksum:0, mtime:\d+\) (re) + 0s: 2 matched, 0 modified, 0 new, 0 corrupted + open B/b1: permission denied + [1] + + $ summer -v generate A B C + 0s: 0 matched, 0 modified, 0 new, 0 corrupted + open B/b1: permission denied + [1]