You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
2.8 KiB
160 lines
2.8 KiB
package main |
|
|
|
import ( |
|
"bufio" |
|
"crypto/md5" |
|
"crypto/sha1" |
|
"crypto/sha256" |
|
"crypto/sha512" |
|
"fmt" |
|
"hash" |
|
"io" |
|
"log" |
|
"os" |
|
"strings" |
|
"sync" |
|
) |
|
|
|
// input contains a file-ish piece of work to perform |
|
type input struct { |
|
f io.ReadCloser |
|
err error |
|
} |
|
|
|
// checksum contains the path to a file, a way to hash it, and the results of |
|
// the hash |
|
type checksum struct { |
|
filename string |
|
hash hash.Hash |
|
checksum string |
|
err error |
|
} |
|
|
|
// check is the entry point for -c operation. |
|
func check(args []string, verbose bool) chan error { |
|
jobs := make(chan checksum) |
|
|
|
go func() { |
|
for i := range toInput(args) { |
|
if i.err != nil { |
|
jobs <- checksum{err: i.err} |
|
break |
|
} |
|
s := bufio.NewScanner(i.f) |
|
for s.Scan() { |
|
jobs <- parseCS(s.Text()) |
|
} |
|
i.f.Close() |
|
if s.Err() != nil { |
|
jobs <- checksum{err: s.Err()} |
|
} |
|
} |
|
close(jobs) |
|
}() |
|
|
|
results := []<-chan error{} |
|
|
|
for w := 0; w < *ngo; w++ { |
|
results = append(results, verify(jobs, verbose)) |
|
} |
|
|
|
return merge(results) |
|
} |
|
|
|
// toInput converts args to a stream of input |
|
func toInput(args []string) chan input { |
|
r := make(chan input) |
|
|
|
go func() { |
|
for _, name := range args { |
|
f, err := os.Open(name) |
|
r <- input{f, err} |
|
} |
|
if len(args) == 0 { |
|
r <- input{f: os.Stdin} |
|
} |
|
close(r) |
|
}() |
|
|
|
return r |
|
} |
|
|
|
// parseCS picks apart a line from a checksum file and returns everything |
|
// needed to perform a checksum. |
|
func parseCS(line string) checksum { |
|
elems := strings.Fields(line) |
|
if len(elems) < 1 { |
|
return checksum{err: fmt.Errorf("couldn't find checksum in %q", line)} |
|
} |
|
cs := elems[0] |
|
var hsh hash.Hash |
|
switch len(cs) { |
|
case 32: |
|
hsh = md5.New() |
|
case 40: |
|
hsh = sha1.New() |
|
case 64: |
|
hsh = sha256.New() |
|
case 128: |
|
hsh = sha512.New() |
|
default: |
|
return checksum{err: fmt.Errorf("unknown format: %q", line)} |
|
} |
|
|
|
return checksum{filename: strings.TrimSpace(line[len(cs):]), hash: hsh, checksum: cs} |
|
} |
|
|
|
// verify does grunt work of verifying a stream of jobs (filenames). |
|
func verify(jobs chan checksum, verbose bool) chan error { |
|
r := make(chan error) |
|
go func() { |
|
for job := range jobs { |
|
if job.err != nil { |
|
log.Printf("%+v", job.err) |
|
continue |
|
} |
|
f, err := os.Open(job.filename) |
|
if err != nil { |
|
r <- err |
|
continue |
|
} |
|
if _, err := io.Copy(job.hash, f); err != nil { |
|
r <- err |
|
continue |
|
} |
|
f.Close() |
|
if fmt.Sprintf("%x", job.hash.Sum(nil)) != job.checksum { |
|
r <- fmt.Errorf("%s: bad", job.filename) |
|
} else if verbose { |
|
fmt.Fprintf(os.Stderr, "ok: %v\n", job.filename) |
|
} |
|
} |
|
close(r) |
|
}() |
|
return r |
|
} |
|
|
|
// merge is simple error fan-in |
|
func merge(cs []<-chan error) chan error { |
|
out := make(chan error) |
|
|
|
var wg sync.WaitGroup |
|
|
|
output := func(c <-chan error) { |
|
for n := range c { |
|
out <- n |
|
} |
|
wg.Done() |
|
} |
|
|
|
wg.Add(len(cs)) |
|
for _, c := range cs { |
|
go output(c) |
|
} |
|
|
|
go func() { |
|
wg.Wait() |
|
close(out) |
|
}() |
|
return out |
|
}
|
|
|