main.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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. )
  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 [16]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 == "." || rn == ".stfolder" {
  80. return nil
  81. }
  82. if rn == ".stversions" {
  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. h := md5.New()
  96. h.Write([]byte(tgt))
  97. hash := h.Sum(nil)
  98. copy(f.hash[:], hash)
  99. } else if info.IsDir() {
  100. f = fileInfo{
  101. name: rn,
  102. mode: info.Mode(),
  103. // hash and modtime zero for directories
  104. }
  105. } else {
  106. f = fileInfo{
  107. name: rn,
  108. mode: info.Mode(),
  109. mod: info.ModTime().Unix(),
  110. }
  111. sum, err := md5file(path)
  112. if err != nil {
  113. return err
  114. }
  115. f.hash = sum
  116. }
  117. select {
  118. case res <- f:
  119. return nil
  120. case <-abort:
  121. return errors.New("abort")
  122. }
  123. }
  124. errc := make(chan error)
  125. go func() {
  126. err := filepath.Walk(dir, walker)
  127. close(res)
  128. if err != nil {
  129. errc <- err
  130. }
  131. close(errc)
  132. }()
  133. return errc
  134. }
  135. func md5file(fname string) (hash [16]byte, err error) {
  136. f, err := os.Open(fname)
  137. if err != nil {
  138. return
  139. }
  140. defer f.Close()
  141. h := md5.New()
  142. io.Copy(h, f)
  143. hb := h.Sum(nil)
  144. copy(hash[:], hb)
  145. return
  146. }