main.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This program is free software: you can redistribute it and/or modify it
  4. // under the terms of the GNU General Public License as published by the Free
  5. // Software Foundation, either version 3 of the License, or (at your option)
  6. // any later version.
  7. //
  8. // This program is distributed in the hope that it will be useful, but WITHOUT
  9. // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10. // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  11. // more details.
  12. //
  13. // You should have received a copy of the GNU General Public License along
  14. // with this program. If not, see <http://www.gnu.org/licenses/>.
  15. package main
  16. import (
  17. "crypto/md5"
  18. "errors"
  19. "flag"
  20. "fmt"
  21. "io"
  22. "log"
  23. "os"
  24. "path/filepath"
  25. "github.com/syncthing/syncthing/internal/symlinks"
  26. )
  27. func main() {
  28. flag.Parse()
  29. log.Println(compareDirectories(flag.Args()...))
  30. }
  31. // Compare a number of directories. Returns nil if the contents are identical,
  32. // otherwise an error describing the first found difference.
  33. func compareDirectories(dirs ...string) error {
  34. chans := make([]chan fileInfo, len(dirs))
  35. for i := range chans {
  36. chans[i] = make(chan fileInfo)
  37. }
  38. errcs := make([]chan error, len(dirs))
  39. abort := make(chan struct{})
  40. for i := range dirs {
  41. errcs[i] = startWalker(dirs[i], chans[i], abort)
  42. }
  43. res := make([]fileInfo, len(dirs))
  44. for {
  45. numDone := 0
  46. for i := range chans {
  47. fi, ok := <-chans[i]
  48. if !ok {
  49. err, hasError := <-errcs[i]
  50. if hasError {
  51. close(abort)
  52. return err
  53. }
  54. numDone++
  55. }
  56. res[i] = fi
  57. }
  58. for i := 1; i < len(res); i++ {
  59. if res[i] != res[0] {
  60. close(abort)
  61. if res[i].name < res[0].name {
  62. return fmt.Errorf("%s missing %v (present in %s)", dirs[0], res[i], dirs[i])
  63. } else if res[i].name > res[0].name {
  64. return fmt.Errorf("%s missing %v (present in %s)", dirs[i], res[0], dirs[0])
  65. }
  66. return fmt.Errorf("Mismatch; %v (%s) != %v (%s)", res[i], dirs[i], res[0], dirs[0])
  67. }
  68. }
  69. if numDone == len(dirs) {
  70. return nil
  71. }
  72. }
  73. }
  74. type fileInfo struct {
  75. name string
  76. mode os.FileMode
  77. mod int64
  78. hash [16]byte
  79. }
  80. func (f fileInfo) String() string {
  81. return fmt.Sprintf("%s %04o %d %x", f.name, f.mode, f.mod, f.hash)
  82. }
  83. func startWalker(dir string, res chan<- fileInfo, abort <-chan struct{}) chan error {
  84. walker := func(path string, info os.FileInfo, err error) error {
  85. if err != nil {
  86. return err
  87. }
  88. rn, _ := filepath.Rel(dir, path)
  89. if rn == "." || rn == ".stfolder" {
  90. return nil
  91. }
  92. if rn == ".stversions" {
  93. return filepath.SkipDir
  94. }
  95. var f fileInfo
  96. if info.Mode()&os.ModeSymlink != 0 {
  97. f = fileInfo{
  98. name: rn,
  99. mode: os.ModeSymlink,
  100. }
  101. tgt, _, err := symlinks.Read(path)
  102. if err != nil {
  103. return err
  104. }
  105. h := md5.New()
  106. h.Write([]byte(tgt))
  107. hash := h.Sum(nil)
  108. copy(f.hash[:], hash)
  109. } else if info.IsDir() {
  110. f = fileInfo{
  111. name: rn,
  112. mode: info.Mode(),
  113. // hash and modtime zero for directories
  114. }
  115. } else {
  116. f = fileInfo{
  117. name: rn,
  118. mode: info.Mode(),
  119. mod: info.ModTime().Unix(),
  120. }
  121. sum, err := md5file(path)
  122. if err != nil {
  123. return err
  124. }
  125. f.hash = sum
  126. }
  127. select {
  128. case res <- f:
  129. return nil
  130. case <-abort:
  131. return errors.New("abort")
  132. }
  133. }
  134. errc := make(chan error)
  135. go func() {
  136. err := filepath.Walk(dir, walker)
  137. close(res)
  138. if err != nil {
  139. errc <- err
  140. }
  141. close(errc)
  142. }()
  143. return errc
  144. }
  145. func md5file(fname string) (hash [16]byte, err error) {
  146. f, err := os.Open(fname)
  147. if err != nil {
  148. return
  149. }
  150. defer f.Close()
  151. h := md5.New()
  152. io.Copy(h, f)
  153. hb := h.Sum(nil)
  154. copy(hash[:], hb)
  155. return
  156. }