-
Notifications
You must be signed in to change notification settings - Fork 0
/
check.go
160 lines (141 loc) · 2.84 KB
/
check.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
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
}