main.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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 http://mozilla.org/MPL/2.0/.
  6. package main
  7. import (
  8. "crypto/md5"
  9. "errors"
  10. "flag"
  11. "fmt"
  12. "io"
  13. "log"
  14. "os"
  15. "path/filepath"
  16. "github.com/syncthing/syncthing/lib/symlinks"
  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 [16]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 == "." || rn == ".stfolder" {
  81. return nil
  82. }
  83. if rn == ".stversions" {
  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 := symlinks.Read(path)
  93. if err != nil {
  94. return err
  95. }
  96. h := md5.New()
  97. h.Write([]byte(tgt))
  98. hash := h.Sum(nil)
  99. copy(f.hash[:], hash)
  100. } else if info.IsDir() {
  101. f = fileInfo{
  102. name: rn,
  103. mode: info.Mode(),
  104. // hash and modtime zero for directories
  105. }
  106. } else {
  107. f = fileInfo{
  108. name: rn,
  109. mode: info.Mode(),
  110. mod: info.ModTime().Unix(),
  111. }
  112. sum, err := md5file(path)
  113. if err != nil {
  114. return err
  115. }
  116. f.hash = sum
  117. }
  118. select {
  119. case res <- f:
  120. return nil
  121. case <-abort:
  122. return errors.New("abort")
  123. }
  124. }
  125. errc := make(chan error)
  126. go func() {
  127. err := filepath.Walk(dir, walker)
  128. close(res)
  129. if err != nil {
  130. errc <- err
  131. }
  132. close(errc)
  133. }()
  134. return errc
  135. }
  136. func md5file(fname string) (hash [16]byte, err error) {
  137. f, err := os.Open(fname)
  138. if err != nil {
  139. return
  140. }
  141. defer f.Close()
  142. h := md5.New()
  143. io.Copy(h, f)
  144. hb := h.Sum(nil)
  145. copy(hash[:], hb)
  146. return
  147. }