calculate checksums
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.

161 lines
2.8 KiB

  1. package main
  2. import (
  3. "bufio"
  4. "crypto/md5"
  5. "crypto/sha1"
  6. "crypto/sha256"
  7. "crypto/sha512"
  8. "fmt"
  9. "hash"
  10. "io"
  11. "log"
  12. "os"
  13. "strings"
  14. "sync"
  15. )
  16. // input contains a file-ish piece of work to perform
  17. type input struct {
  18. f io.ReadCloser
  19. err error
  20. }
  21. // checksum contains the path to a file, a way to hash it, and the results of
  22. // the hash
  23. type checksum struct {
  24. filename string
  25. hash hash.Hash
  26. checksum string
  27. err error
  28. }
  29. // check is the entry point for -c operation.
  30. func check(args []string, verbose bool) chan error {
  31. jobs := make(chan checksum)
  32. go func() {
  33. for i := range toInput(args) {
  34. if i.err != nil {
  35. jobs <- checksum{err: i.err}
  36. break
  37. }
  38. s := bufio.NewScanner(i.f)
  39. for s.Scan() {
  40. jobs <- parseCS(s.Text())
  41. }
  42. i.f.Close()
  43. if s.Err() != nil {
  44. jobs <- checksum{err: s.Err()}
  45. }
  46. }
  47. close(jobs)
  48. }()
  49. results := []<-chan error{}
  50. for w := 0; w < *ngo; w++ {
  51. results = append(results, verify(jobs, verbose))
  52. }
  53. return merge(results)
  54. }
  55. // toInput converts args to a stream of input
  56. func toInput(args []string) chan input {
  57. r := make(chan input)
  58. go func() {
  59. for _, name := range args {
  60. f, err := os.Open(name)
  61. r <- input{f, err}
  62. }
  63. if len(args) == 0 {
  64. r <- input{f: os.Stdin}
  65. }
  66. close(r)
  67. }()
  68. return r
  69. }
  70. // parseCS picks apart a line from a checksum file and returns everything
  71. // needed to perform a checksum.
  72. func parseCS(line string) checksum {
  73. elems := strings.Fields(line)
  74. if len(elems) < 1 {
  75. return checksum{err: fmt.Errorf("couldn't find checksum in %q", line)}
  76. }
  77. cs := elems[0]
  78. var hsh hash.Hash
  79. switch len(cs) {
  80. case 32:
  81. hsh = md5.New()
  82. case 40:
  83. hsh = sha1.New()
  84. case 64:
  85. hsh = sha256.New()
  86. case 128:
  87. hsh = sha512.New()
  88. default:
  89. return checksum{err: fmt.Errorf("unknown format: %q", line)}
  90. }
  91. return checksum{filename: strings.TrimSpace(line[len(cs):]), hash: hsh, checksum: cs}
  92. }
  93. // verify does grunt work of verifying a stream of jobs (filenames).
  94. func verify(jobs chan checksum, verbose bool) chan error {
  95. r := make(chan error)
  96. go func() {
  97. for job := range jobs {
  98. if job.err != nil {
  99. log.Printf("%+v", job.err)
  100. continue
  101. }
  102. f, err := os.Open(job.filename)
  103. if err != nil {
  104. r <- err
  105. continue
  106. }
  107. if _, err := io.Copy(job.hash, f); err != nil {
  108. r <- err
  109. continue
  110. }
  111. f.Close()
  112. if fmt.Sprintf("%x", job.hash.Sum(nil)) != job.checksum {
  113. r <- fmt.Errorf("%s: bad", job.filename)
  114. } else if verbose {
  115. fmt.Fprintf(os.Stderr, "ok: %v\n", job.filename)
  116. }
  117. }
  118. close(r)
  119. }()
  120. return r
  121. }
  122. // merge is simple error fan-in
  123. func merge(cs []<-chan error) chan error {
  124. out := make(chan error)
  125. var wg sync.WaitGroup
  126. output := func(c <-chan error) {
  127. for n := range c {
  128. out <- n
  129. }
  130. wg.Done()
  131. }
  132. wg.Add(len(cs))
  133. for _, c := range cs {
  134. go output(c)
  135. }
  136. go func() {
  137. wg.Wait()
  138. close(out)
  139. }()
  140. return out
  141. }