c4ba580cbb
After this change, - Symlinks on Windows are always unsupported. Sorry. - Symlinks are always enabled on other platforms. They are just a small file like anything else. There is no need to special case them. If you don't want to sync some symlinks, ignore them. - The protocol doesn't differentiate between different "types" of symlinks. If that distinction ever does become relevant the individual devices can figure it out by looking at the destination when they create the link. It's backwards compatible in that all the old symlink types are still understood to be symlinks, and the new SYMLINK type is equivalent to the old SYMLINK_UNKNOWN which was always a valid way to do it. GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3962 LGTM: AudriusButkevicius
167 lines
3.1 KiB
Go
167 lines
3.1 KiB
Go
// Copyright (C) 2014 The Syncthing Authors.
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package main
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
log.Println(compareDirectories(flag.Args()...))
|
|
}
|
|
|
|
// Compare a number of directories. Returns nil if the contents are identical,
|
|
// otherwise an error describing the first found difference.
|
|
func compareDirectories(dirs ...string) error {
|
|
chans := make([]chan fileInfo, len(dirs))
|
|
for i := range chans {
|
|
chans[i] = make(chan fileInfo)
|
|
}
|
|
errcs := make([]chan error, len(dirs))
|
|
abort := make(chan struct{})
|
|
|
|
for i := range dirs {
|
|
errcs[i] = startWalker(dirs[i], chans[i], abort)
|
|
}
|
|
|
|
res := make([]fileInfo, len(dirs))
|
|
for {
|
|
numDone := 0
|
|
for i := range chans {
|
|
fi, ok := <-chans[i]
|
|
if !ok {
|
|
err, hasError := <-errcs[i]
|
|
if hasError {
|
|
close(abort)
|
|
return err
|
|
}
|
|
numDone++
|
|
}
|
|
res[i] = fi
|
|
}
|
|
|
|
for i := 1; i < len(res); i++ {
|
|
if res[i] != res[0] {
|
|
close(abort)
|
|
if res[i].name < res[0].name {
|
|
return fmt.Errorf("%s missing %v (present in %s)", dirs[0], res[i], dirs[i])
|
|
} else if res[i].name > res[0].name {
|
|
return fmt.Errorf("%s missing %v (present in %s)", dirs[i], res[0], dirs[0])
|
|
}
|
|
return fmt.Errorf("Mismatch; %v (%s) != %v (%s)", res[i], dirs[i], res[0], dirs[0])
|
|
}
|
|
}
|
|
|
|
if numDone == len(dirs) {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
type fileInfo struct {
|
|
name string
|
|
mode os.FileMode
|
|
mod int64
|
|
hash [16]byte
|
|
}
|
|
|
|
func (f fileInfo) String() string {
|
|
return fmt.Sprintf("%s %04o %d %x", f.name, f.mode, f.mod, f.hash)
|
|
}
|
|
|
|
func startWalker(dir string, res chan<- fileInfo, abort <-chan struct{}) chan error {
|
|
walker := func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rn, _ := filepath.Rel(dir, path)
|
|
if rn == "." || rn == ".stfolder" {
|
|
return nil
|
|
}
|
|
if rn == ".stversions" {
|
|
return filepath.SkipDir
|
|
}
|
|
|
|
var f fileInfo
|
|
if info.Mode()&os.ModeSymlink != 0 {
|
|
f = fileInfo{
|
|
name: rn,
|
|
mode: os.ModeSymlink,
|
|
}
|
|
|
|
tgt, err := os.Readlink(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
h := md5.New()
|
|
h.Write([]byte(tgt))
|
|
hash := h.Sum(nil)
|
|
|
|
copy(f.hash[:], hash)
|
|
} else if info.IsDir() {
|
|
f = fileInfo{
|
|
name: rn,
|
|
mode: info.Mode(),
|
|
// hash and modtime zero for directories
|
|
}
|
|
} else {
|
|
f = fileInfo{
|
|
name: rn,
|
|
mode: info.Mode(),
|
|
mod: info.ModTime().Unix(),
|
|
}
|
|
sum, err := md5file(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f.hash = sum
|
|
}
|
|
|
|
select {
|
|
case res <- f:
|
|
return nil
|
|
case <-abort:
|
|
return errors.New("abort")
|
|
}
|
|
}
|
|
|
|
errc := make(chan error)
|
|
go func() {
|
|
err := filepath.Walk(dir, walker)
|
|
close(res)
|
|
if err != nil {
|
|
errc <- err
|
|
}
|
|
close(errc)
|
|
}()
|
|
return errc
|
|
}
|
|
|
|
func md5file(fname string) (hash [16]byte, err error) {
|
|
f, err := os.Open(fname)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer f.Close()
|
|
|
|
h := md5.New()
|
|
io.Copy(h, f)
|
|
hb := h.Sum(nil)
|
|
copy(hash[:], hb)
|
|
|
|
return
|
|
}
|