main.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package main
  7. import (
  8. "crypto/sha256"
  9. "errors"
  10. "flag"
  11. "fmt"
  12. "io"
  13. "log"
  14. "os"
  15. "path/filepath"
  16. _ "github.com/syncthing/syncthing/lib/automaxprocs"
  17. )
  18. func main() {
  19. flag.Parse()
  20. log.Println(compareDirectories(flag.Args()...))
  21. }
  22. // Compare a number of directories. Returns nil if the contents are identical,
  23. // otherwise an error describing the first found difference.
  24. func compareDirectories(dirs ...string) error {
  25. chans := make([]chan fileInfo, len(dirs))
  26. for i := range chans {
  27. chans[i] = make(chan fileInfo)
  28. }
  29. errcs := make([]chan error, len(dirs))
  30. abort := make(chan struct{})
  31. for i := range dirs {
  32. errcs[i] = startWalker(dirs[i], chans[i], abort)
  33. }
  34. res := make([]fileInfo, len(dirs))
  35. for {
  36. numDone := 0
  37. for i := range chans {
  38. fi, ok := <-chans[i]
  39. if !ok {
  40. err, hasError := <-errcs[i]
  41. if hasError {
  42. close(abort)
  43. return err
  44. }
  45. numDone++
  46. }
  47. res[i] = fi
  48. }
  49. for i := 1; i < len(res); i++ {
  50. if res[i] != res[0] {
  51. close(abort)
  52. if res[i].name < res[0].name {
  53. return fmt.Errorf("%s missing %v (present in %s)", dirs[0], res[i], dirs[i])
  54. } else if res[i].name > res[0].name {
  55. return fmt.Errorf("%s missing %v (present in %s)", dirs[i], res[0], dirs[0])
  56. }
  57. return fmt.Errorf("mismatch; %v (%s) != %v (%s)", res[i], dirs[i], res[0], dirs[0])
  58. }
  59. }
  60. if numDone == len(dirs) {
  61. return nil
  62. }
  63. }
  64. }
  65. type fileInfo struct {
  66. name string
  67. mode os.FileMode
  68. mod int64
  69. hash [sha256.Size]byte
  70. }
  71. func (f fileInfo) String() string {
  72. return fmt.Sprintf("%s %04o %d %x", f.name, f.mode, f.mod, f.hash)
  73. }
  74. func startWalker(dir string, res chan<- fileInfo, abort <-chan struct{}) chan error {
  75. walker := func(path string, info os.FileInfo, err error) error {
  76. if err != nil {
  77. return err
  78. }
  79. rn, _ := filepath.Rel(dir, path)
  80. if rn == "." {
  81. return nil
  82. }
  83. if rn == ".stversions" || rn == ".stfolder" {
  84. return filepath.SkipDir
  85. }
  86. var f fileInfo
  87. if info.Mode()&os.ModeSymlink != 0 {
  88. f = fileInfo{
  89. name: rn,
  90. mode: os.ModeSymlink,
  91. }
  92. tgt, err := os.Readlink(path)
  93. if err != nil {
  94. return err
  95. }
  96. f.hash = sha256.Sum256([]byte(tgt))
  97. } else if info.IsDir() {
  98. f = fileInfo{
  99. name: rn,
  100. mode: info.Mode(),
  101. // hash and modtime zero for directories
  102. }
  103. } else {
  104. f = fileInfo{
  105. name: rn,
  106. mode: info.Mode(),
  107. mod: info.ModTime().Unix(),
  108. }
  109. sum, err := sha256file(path)
  110. if err != nil {
  111. return err
  112. }
  113. f.hash = sum
  114. }
  115. select {
  116. case res <- f:
  117. return nil
  118. case <-abort:
  119. return errors.New("abort")
  120. }
  121. }
  122. errc := make(chan error)
  123. go func() {
  124. err := filepath.Walk(dir, walker)
  125. close(res)
  126. if err != nil {
  127. errc <- err
  128. }
  129. close(errc)
  130. }()
  131. return errc
  132. }
  133. func sha256file(fname string) (hash [sha256.Size]byte, err error) {
  134. f, err := os.Open(fname)
  135. if err != nil {
  136. return
  137. }
  138. defer f.Close()
  139. h := sha256.New()
  140. io.Copy(h, f)
  141. hb := h.Sum(nil)
  142. copy(hash[:], hb)
  143. return
  144. }