main.go 3.1 KB

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