mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-17 10:18:15 -05:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9da422f1c5 | ||
|
|
ab1739ba34 | ||
|
|
fc1430aa92 | ||
|
|
3cde608eda | ||
|
|
2898552f4b | ||
|
|
911c148c71 | ||
|
|
91568a173a | ||
|
|
e57f5499a1 | ||
|
|
9abb7b71a9 | ||
|
|
724c354d62 | ||
|
|
c44779094d | ||
|
|
eeedab4091 | ||
|
|
8559e20237 | ||
|
|
26730eb083 | ||
|
|
4160ce674d | ||
|
|
2dbeea21c4 | ||
|
|
a2b8485a89 |
26
build.go
26
build.go
@@ -298,6 +298,7 @@ func runCommand(cmd string, target target) {
|
||||
ok := gometalinter("deadcode", dirs, "test/util.go")
|
||||
ok = gometalinter("structcheck", dirs) && ok
|
||||
ok = gometalinter("varcheck", dirs) && ok
|
||||
ok = gometalinter("ineffassign", dirs) && ok
|
||||
if !ok {
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -356,14 +357,23 @@ func checkRequiredGoVersion() (float64, bool) {
|
||||
}
|
||||
|
||||
func setup() {
|
||||
runPrint("go", "get", "-v", "github.com/golang/lint/golint")
|
||||
runPrint("go", "get", "-v", "golang.org/x/tools/cmd/cover")
|
||||
runPrint("go", "get", "-v", "golang.org/x/net/html")
|
||||
runPrint("go", "get", "-v", "github.com/FiloSottile/gvt")
|
||||
runPrint("go", "get", "-v", "github.com/axw/gocov/gocov")
|
||||
runPrint("go", "get", "-v", "github.com/AlekSi/gocov-xml")
|
||||
runPrint("go", "get", "-v", "github.com/alecthomas/gometalinter")
|
||||
runPrint("go", "get", "-v", "github.com/mitchellh/go-wordwrap")
|
||||
packages := []string{
|
||||
"github.com/alecthomas/gometalinter",
|
||||
"github.com/AlekSi/gocov-xml",
|
||||
"github.com/axw/gocov/gocov",
|
||||
"github.com/FiloSottile/gvt",
|
||||
"github.com/golang/lint/golint",
|
||||
"github.com/gordonklaus/ineffassign",
|
||||
"github.com/mitchellh/go-wordwrap",
|
||||
"github.com/opennota/check/cmd/...",
|
||||
"github.com/tsenart/deadcode",
|
||||
"golang.org/x/net/html",
|
||||
"golang.org/x/tools/cmd/cover",
|
||||
}
|
||||
for _, pkg := range packages {
|
||||
fmt.Println(pkg)
|
||||
runPrint("go", "get", "-u", pkg)
|
||||
}
|
||||
}
|
||||
|
||||
func test(pkgs ...string) {
|
||||
|
||||
@@ -62,6 +62,10 @@ func (i requestID) String() string {
|
||||
return fmt.Sprintf("%016x", int64(i))
|
||||
}
|
||||
|
||||
type contextKey int
|
||||
|
||||
const idKey contextKey = iota
|
||||
|
||||
func negCacheFor(lastSeen time.Time) int {
|
||||
since := time.Since(lastSeen).Seconds()
|
||||
if since >= maxDeviceAge {
|
||||
@@ -132,7 +136,7 @@ var topCtx = context.Background()
|
||||
|
||||
func (s *querysrv) handler(w http.ResponseWriter, req *http.Request) {
|
||||
reqID := requestID(rand.Int63())
|
||||
ctx := context.WithValue(topCtx, "id", reqID)
|
||||
ctx := context.WithValue(topCtx, idKey, reqID)
|
||||
|
||||
if debug {
|
||||
log.Println(reqID, req.Method, req.URL)
|
||||
@@ -186,7 +190,7 @@ func (s *querysrv) handler(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
func (s *querysrv) handleGET(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
||||
reqID := ctx.Value("id").(requestID)
|
||||
reqID := ctx.Value(idKey).(requestID)
|
||||
|
||||
deviceID, err := protocol.DeviceIDFromString(req.URL.Query().Get("device"))
|
||||
if err != nil {
|
||||
@@ -238,7 +242,7 @@ func (s *querysrv) handleGET(ctx context.Context, w http.ResponseWriter, req *ht
|
||||
}
|
||||
|
||||
func (s *querysrv) handlePOST(ctx context.Context, remoteIP net.IP, w http.ResponseWriter, req *http.Request) {
|
||||
reqID := ctx.Value("id").(requestID)
|
||||
reqID := ctx.Value(idKey).(requestID)
|
||||
|
||||
rawCert := certificateBytes(req)
|
||||
if rawCert == nil {
|
||||
@@ -299,7 +303,7 @@ func (s *querysrv) Stop() {
|
||||
}
|
||||
|
||||
func (s *querysrv) handleAnnounce(ctx context.Context, remote net.IP, deviceID protocol.DeviceID, addresses []string) (userErr, internalErr error) {
|
||||
reqID := ctx.Value("id").(requestID)
|
||||
reqID := ctx.Value(idKey).(requestID)
|
||||
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
@@ -383,7 +387,7 @@ func (s *querysrv) limit(remote net.IP) bool {
|
||||
}
|
||||
|
||||
func (s *querysrv) updateDevice(ctx context.Context, tx *sql.Tx, device protocol.DeviceID) error {
|
||||
reqID := ctx.Value("id").(requestID)
|
||||
reqID := ctx.Value(idKey).(requestID)
|
||||
t0 := time.Now()
|
||||
res, err := tx.Stmt(s.prep["updateDevice"]).Exec(device.String())
|
||||
if err != nil {
|
||||
|
||||
@@ -116,7 +116,7 @@ func saveCsrfTokens() {
|
||||
// nothing relevant we can do about them anyway...
|
||||
|
||||
name := locations[locCsrfTokens]
|
||||
f, err := osutil.CreateAtomic(name, 0600)
|
||||
f, err := osutil.CreateAtomic(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -507,6 +507,9 @@ func TestCSRFRequired(t *testing.T) {
|
||||
cfg := new(mockedConfig)
|
||||
cfg.gui.APIKey = testAPIKey
|
||||
baseURL, err := startHTTP(cfg)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error from getting base URL:", err)
|
||||
}
|
||||
|
||||
cli := &http.Client{
|
||||
Timeout: time.Second,
|
||||
|
||||
@@ -278,6 +278,11 @@ func parseCommandLineOptions() RuntimeOptions {
|
||||
flag.Usage = usageFor(flag.CommandLine, usage, longUsage)
|
||||
flag.Parse()
|
||||
|
||||
if len(flag.Args()) > 0 {
|
||||
flag.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
|
||||
@@ -249,6 +249,10 @@ ul.three-columns li, ul.two-columns li {
|
||||
text-indent: -0.5em;
|
||||
}
|
||||
|
||||
.navbar-fixed-bottom {
|
||||
z-index: 980;
|
||||
}
|
||||
|
||||
/** Footer nav on small devices **/
|
||||
@media (max-width: 1199px) {
|
||||
/* Stay at the end of the page, with space reserved for the footer
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
"Ignore Patterns": "Πρότυπο για αγνόηση",
|
||||
"Ignore Permissions": "Αγνόησε τα δικαιώματα",
|
||||
"Incoming Rate Limit (KiB/s)": "Περιορισμός ταχύτητας λήψης (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Με μια εσφαλμένη ρύθμιση μπορεί να προκληθεί ζημιά στο περιεχόμενο των φακέλων και το Syncthing ενδέχεται να σταματήσει να λειτουργεί.",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Με μια εσφαλμένη ρύθμιση μπορεί να προκληθεί ζημιά στα περιεχόμενα των φακέλων και το Syncthing ενδέχεται να σταματήσει να λειτουργεί.",
|
||||
"Introducer": "Βασικός κόμβος",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Αντιστροφή της δοσμένης συνθήκης (π.χ. να μην εξαιρείς) ",
|
||||
"Keep Versions": "Διατήρηση εκδόσεων",
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"Documentation": "Dokumentazio",
|
||||
"Download Rate": "Deskargatze emari",
|
||||
"Downloaded": "Telekargatua",
|
||||
"Downloading": "Deskargatua",
|
||||
"Downloading": "Deskargatze",
|
||||
"Edit": "Aldatu",
|
||||
"Editing": "Aldaketa",
|
||||
"Enable NAT traversal": "Ahalbidetu NAT",
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"Documentation": "Documentation",
|
||||
"Download Rate": "Débit de réception",
|
||||
"Downloaded": "Téléchargé",
|
||||
"Downloading": "En cours de téléchargement",
|
||||
"Downloading": "Téléchargement",
|
||||
"Edit": "Modifier",
|
||||
"Editing": "Modifications",
|
||||
"Enable NAT traversal": "Activer transfert d'adresses NAT",
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"Documentation": "Documentation",
|
||||
"Download Rate": "Vitesse de réception",
|
||||
"Downloaded": "Téléchargé",
|
||||
"Downloading": "En cours de téléchargement",
|
||||
"Downloading": "Téléchargement",
|
||||
"Edit": "Modifier",
|
||||
"Editing": "Modifications",
|
||||
"Enable NAT traversal": "Activer la translation d'adresses (NAT)",
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
"Quick guide to supported patterns": "Krótki przewodnik po obsługiwanych wzorcach",
|
||||
"RAM Utilization": "Użycie pamięci RAM",
|
||||
"Random": "Losowo",
|
||||
"Reduced by ignore patterns": "Reduced by ignore patterns",
|
||||
"Reduced by ignore patterns": "Ograniczono przez wzorce ignorowania",
|
||||
"Release Notes": "Informacje o wydaniu",
|
||||
"Remote Devices": "Urządzenia zdalne",
|
||||
"Remove": "Usuń",
|
||||
@@ -231,8 +231,8 @@
|
||||
"Yes": "Tak",
|
||||
"You must keep at least one version.": "Musisz posiadać przynajmniej jedną wersję",
|
||||
"days": "dni",
|
||||
"directories": "directories",
|
||||
"files": "files",
|
||||
"directories": "katalogi",
|
||||
"files": "pliki",
|
||||
"full documentation": "pełna dokumentacja",
|
||||
"items": "pozycji",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce udostępnić folder \"{{folder}}\"",
|
||||
|
||||
@@ -325,7 +325,7 @@ func (w *Wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) {
|
||||
|
||||
// Save writes the configuration to disk, and generates a ConfigSaved event.
|
||||
func (w *Wrapper) Save() error {
|
||||
fd, err := osutil.CreateAtomic(w.path, 0600)
|
||||
fd, err := osutil.CreateAtomic(w.path)
|
||||
if err != nil {
|
||||
l.Debugln("CreateAtomic:", err)
|
||||
return err
|
||||
|
||||
@@ -128,7 +128,7 @@ func (t readWriteTransaction) updateGlobal(folder, device []byte, file protocol.
|
||||
Version: file.Version,
|
||||
}
|
||||
|
||||
insertedAt := -1
|
||||
var insertedAt int
|
||||
// Find a position in the list to insert this file. The file at the front
|
||||
// of the list is the newer, the "global".
|
||||
for i := range fl.Versions {
|
||||
|
||||
96
lib/fs/basicfs.go
Normal file
96
lib/fs/basicfs.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright (C) 2016 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 fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The BasicFilesystem implements all aspects by delegating to package os.
|
||||
type BasicFilesystem struct {
|
||||
}
|
||||
|
||||
func NewBasicFilesystem() *BasicFilesystem {
|
||||
return new(BasicFilesystem)
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) Chmod(name string, mode FileMode) error {
|
||||
return os.Chmod(name, os.FileMode(mode))
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||
return os.Chtimes(name, atime, mtime)
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) Mkdir(name string, perm FileMode) error {
|
||||
return os.Mkdir(name, os.FileMode(perm))
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) Lstat(name string) (FileInfo, error) {
|
||||
fi, err := os.Lstat(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fsFileInfo{fi}, err
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) Remove(name string) error {
|
||||
return os.Remove(name)
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) Rename(oldpath, newpath string) error {
|
||||
return os.Rename(oldpath, newpath)
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) Stat(name string) (FileInfo, error) {
|
||||
fi, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fsFileInfo{fi}, err
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) DirNames(name string) ([]string, error) {
|
||||
fd, err := os.OpenFile(name, os.O_RDONLY, 0777)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
names, err := fd.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return names, nil
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) Open(name string) (File, error) {
|
||||
return os.Open(name)
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) Create(name string) (File, error) {
|
||||
return os.Create(name)
|
||||
}
|
||||
|
||||
// fsFileInfo implements the fs.FileInfo interface on top of an os.FileInfo.
|
||||
type fsFileInfo struct {
|
||||
os.FileInfo
|
||||
}
|
||||
|
||||
func (e fsFileInfo) Mode() FileMode {
|
||||
return FileMode(e.FileInfo.Mode())
|
||||
}
|
||||
|
||||
func (e fsFileInfo) IsRegular() bool {
|
||||
return e.FileInfo.Mode().IsRegular()
|
||||
}
|
||||
|
||||
func (e fsFileInfo) IsSymlink() bool {
|
||||
return e.FileInfo.Mode()&os.ModeSymlink == os.ModeSymlink
|
||||
}
|
||||
43
lib/fs/basicfs_symlink_unix.go
Normal file
43
lib/fs/basicfs_symlink_unix.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (C) 2016 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/.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package fs
|
||||
|
||||
import "os"
|
||||
|
||||
var symlinksSupported = true
|
||||
|
||||
func DisableSymlinks() {
|
||||
symlinksSupported = false
|
||||
}
|
||||
|
||||
func (BasicFilesystem) SymlinksSupported() bool {
|
||||
return symlinksSupported
|
||||
}
|
||||
|
||||
func (BasicFilesystem) CreateSymlink(name, target string, _ LinkTargetType) error {
|
||||
return os.Symlink(target, name)
|
||||
}
|
||||
|
||||
func (BasicFilesystem) ChangeSymlinkType(_ string, _ LinkTargetType) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (BasicFilesystem) ReadSymlink(path string) (string, LinkTargetType, error) {
|
||||
tt := LinkTargetUnknown
|
||||
if stat, err := os.Stat(path); err == nil {
|
||||
if stat.IsDir() {
|
||||
tt = LinkTargetDirectory
|
||||
} else {
|
||||
tt = LinkTargetFile
|
||||
}
|
||||
}
|
||||
|
||||
path, err := os.Readlink(path)
|
||||
return path, tt, err
|
||||
}
|
||||
195
lib/fs/basicfs_symlink_windows.go
Normal file
195
lib/fs/basicfs_symlink_windows.go
Normal file
@@ -0,0 +1,195 @@
|
||||
// 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/.
|
||||
|
||||
// +build windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
|
||||
"syscall"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
win32FsctlGetReparsePoint = 0x900a8
|
||||
win32FileFlagOpenReparsePoint = 0x00200000
|
||||
win32SymbolicLinkFlagDirectory = 0x1
|
||||
)
|
||||
|
||||
var (
|
||||
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
procDeviceIoControl = modkernel32.NewProc("DeviceIoControl")
|
||||
procCreateSymbolicLink = modkernel32.NewProc("CreateSymbolicLinkW")
|
||||
symlinksSupported = false
|
||||
)
|
||||
|
||||
func init() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
// Ensure that the supported flag is disabled when we hit an
|
||||
// error, even though it should already be. Also, silently swallow
|
||||
// the error since it's fine for a system not to support symlinks.
|
||||
symlinksSupported = false
|
||||
}
|
||||
}()
|
||||
|
||||
// Needs administrator privileges.
|
||||
// Let's check that everything works.
|
||||
// This could be done more officially:
|
||||
// http://stackoverflow.com/questions/2094663/determine-if-windows-process-has-privilege-to-create-symbolic-link
|
||||
// But I don't want to define 10 more structs just to look this up.
|
||||
base := os.TempDir()
|
||||
path := filepath.Join(base, "symlinktest")
|
||||
defer os.Remove(path)
|
||||
|
||||
err := DefaultFilesystem.CreateSymlink(path, base, LinkTargetDirectory)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
stat, err := osutil.Lstat(path)
|
||||
if err != nil || stat.Mode()&os.ModeSymlink == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
target, tt, err := DefaultFilesystem.ReadSymlink(path)
|
||||
if err != nil || osutil.NativeFilename(target) != base || tt != LinkTargetDirectory {
|
||||
return
|
||||
}
|
||||
symlinksSupported = true
|
||||
}
|
||||
|
||||
func DisableSymlinks() {
|
||||
symlinksSupported = false
|
||||
}
|
||||
|
||||
func (BasicFilesystem) SymlinksSupported() bool {
|
||||
return symlinksSupported
|
||||
}
|
||||
|
||||
func (BasicFilesystem) ReadSymlink(path string) (string, LinkTargetType, error) {
|
||||
ptr, err := syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return "", LinkTargetUnknown, err
|
||||
}
|
||||
handle, err := syscall.CreateFile(ptr, 0, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS|win32FileFlagOpenReparsePoint, 0)
|
||||
if err != nil || handle == syscall.InvalidHandle {
|
||||
return "", LinkTargetUnknown, err
|
||||
}
|
||||
defer syscall.Close(handle)
|
||||
var ret uint16
|
||||
var data reparseData
|
||||
|
||||
r1, _, err := syscall.Syscall9(procDeviceIoControl.Addr(), 8, uintptr(handle), win32FsctlGetReparsePoint, 0, 0, uintptr(unsafe.Pointer(&data)), unsafe.Sizeof(data), uintptr(unsafe.Pointer(&ret)), 0, 0)
|
||||
if r1 == 0 {
|
||||
return "", LinkTargetUnknown, err
|
||||
}
|
||||
|
||||
tt := LinkTargetUnknown
|
||||
if attr, err := syscall.GetFileAttributes(ptr); err == nil {
|
||||
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||
tt = LinkTargetDirectory
|
||||
} else {
|
||||
tt = LinkTargetFile
|
||||
}
|
||||
}
|
||||
|
||||
return osutil.NormalizedFilename(data.printName()), tt, nil
|
||||
}
|
||||
|
||||
func (BasicFilesystem) CreateSymlink(path, target string, tt LinkTargetType) error {
|
||||
srcp, err := syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
trgp, err := syscall.UTF16PtrFromString(osutil.NativeFilename(target))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sadly for Windows we need to specify the type of the symlink,
|
||||
// whether it's a directory symlink or a file symlink.
|
||||
// If the flags doesn't reveal the target type, try to evaluate it
|
||||
// ourselves, and worst case default to the symlink pointing to a file.
|
||||
mode := 0
|
||||
if tt == LinkTargetUnknown {
|
||||
path := target
|
||||
if !filepath.IsAbs(target) {
|
||||
path = filepath.Join(filepath.Dir(path), target)
|
||||
}
|
||||
|
||||
stat, err := os.Stat(path)
|
||||
if err == nil && stat.IsDir() {
|
||||
mode = win32SymbolicLinkFlagDirectory
|
||||
}
|
||||
} else if tt == LinkTargetDirectory {
|
||||
mode = win32SymbolicLinkFlagDirectory
|
||||
}
|
||||
|
||||
r0, _, err := syscall.Syscall(procCreateSymbolicLink.Addr(), 3, uintptr(unsafe.Pointer(srcp)), uintptr(unsafe.Pointer(trgp)), uintptr(mode))
|
||||
if r0 == 1 {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (fs BasicFilesystem) ChangeSymlinkType(path string, tt LinkTargetType) error {
|
||||
target, existingTargetType, err := fs.ReadSymlink(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// If it's the same type, nothing to do.
|
||||
if tt == existingTargetType {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the actual type is unknown, but the new type is file, nothing to do
|
||||
if existingTargetType == LinkTargetUnknown && tt != LinkTargetDirectory {
|
||||
return nil
|
||||
}
|
||||
return osutil.InWritableDir(func(path string) error {
|
||||
// It should be a symlink as well hence no need to change permissions on
|
||||
// the file.
|
||||
os.Remove(path)
|
||||
return fs.CreateSymlink(path, target, tt)
|
||||
}, path)
|
||||
}
|
||||
|
||||
type reparseData struct {
|
||||
reparseTag uint32
|
||||
reparseDataLength uint16
|
||||
reserved uint16
|
||||
substitueNameOffset uint16
|
||||
substitueNameLength uint16
|
||||
printNameOffset uint16
|
||||
printNameLength uint16
|
||||
flags uint32
|
||||
// substituteName - 264 widechars max = 528 bytes
|
||||
// printName - 260 widechars max = 520 bytes
|
||||
// = 1048 bytes total
|
||||
buffer [1048 / 2]uint16
|
||||
}
|
||||
|
||||
func (r *reparseData) printName() string {
|
||||
// offset and length are in bytes but we're indexing a []uint16
|
||||
offset := r.printNameOffset / 2
|
||||
length := r.printNameLength / 2
|
||||
return string(utf16.Decode(r.buffer[offset : offset+length]))
|
||||
}
|
||||
|
||||
func (r *reparseData) substituteName() string {
|
||||
// offset and length are in bytes but we're indexing a []uint16
|
||||
offset := r.substitueNameOffset / 2
|
||||
length := r.substitueNameLength / 2
|
||||
return string(utf16.Decode(r.buffer[offset : offset+length]))
|
||||
}
|
||||
81
lib/fs/basicfs_walk.go
Normal file
81
lib/fs/basicfs_walk.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This part copied directly from golang.org/src/path/filepath/path.go (Go
|
||||
// 1.6) and lightly modified to be methods on BasicFilesystem.
|
||||
|
||||
// In our Walk() all paths given to a WalkFunc() are relative to the
|
||||
// filesystem root.
|
||||
|
||||
package fs
|
||||
|
||||
import "path/filepath"
|
||||
|
||||
// WalkFunc is the type of the function called for each file or directory
|
||||
// visited by Walk. The path argument contains the argument to Walk as a
|
||||
// prefix; that is, if Walk is called with "dir", which is a directory
|
||||
// containing the file "a", the walk function will be called with argument
|
||||
// "dir/a". The info argument is the FileInfo for the named path.
|
||||
//
|
||||
// If there was a problem walking to the file or directory named by path, the
|
||||
// incoming error will describe the problem and the function can decide how
|
||||
// to handle that error (and Walk will not descend into that directory). If
|
||||
// an error is returned, processing stops. The sole exception is when the function
|
||||
// returns the special value SkipDir. If the function returns SkipDir when invoked
|
||||
// on a directory, Walk skips the directory's contents entirely.
|
||||
// If the function returns SkipDir when invoked on a non-directory file,
|
||||
// Walk skips the remaining files in the containing directory.
|
||||
type WalkFunc func(path string, info FileInfo, err error) error
|
||||
|
||||
// walk recursively descends path, calling walkFn.
|
||||
func (f *BasicFilesystem) walk(path string, info FileInfo, walkFn WalkFunc) error {
|
||||
err := walkFn(path, info, nil)
|
||||
if err != nil {
|
||||
if info.IsDir() && err == SkipDir {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
names, err := f.DirNames(path)
|
||||
if err != nil {
|
||||
return walkFn(path, info, err)
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
filename := filepath.Join(path, name)
|
||||
fileInfo, err := f.Lstat(filename)
|
||||
if err != nil {
|
||||
if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = f.walk(filename, fileInfo, walkFn)
|
||||
if err != nil {
|
||||
if !fileInfo.IsDir() || err != SkipDir {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Walk walks the file tree rooted at root, calling walkFn for each file or
|
||||
// directory in the tree, including root. All errors that arise visiting files
|
||||
// and directories are filtered by walkFn. The files are walked in lexical
|
||||
// order, which makes the output deterministic but means that for very
|
||||
// large directories Walk can be inefficient.
|
||||
// Walk does not follow symbolic links.
|
||||
func (f *BasicFilesystem) Walk(root string, walkFn WalkFunc) error {
|
||||
info, err := f.Lstat(root)
|
||||
if err != nil {
|
||||
return walkFn(root, nil, err)
|
||||
}
|
||||
return f.walk(root, info, walkFn)
|
||||
}
|
||||
77
lib/fs/filesystem.go
Normal file
77
lib/fs/filesystem.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright (C) 2016 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 fs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LinkTargetType int
|
||||
|
||||
const (
|
||||
LinkTargetFile LinkTargetType = iota
|
||||
LinkTargetDirectory
|
||||
LinkTargetUnknown
|
||||
)
|
||||
|
||||
// The Filesystem interface abstracts access to the file system.
|
||||
type Filesystem interface {
|
||||
ChangeSymlinkType(name string, tt LinkTargetType) error
|
||||
Chmod(name string, mode FileMode) error
|
||||
Chtimes(name string, atime time.Time, mtime time.Time) error
|
||||
Create(name string) (File, error)
|
||||
CreateSymlink(name, target string, tt LinkTargetType) error
|
||||
DirNames(name string) ([]string, error)
|
||||
Lstat(name string) (FileInfo, error)
|
||||
Mkdir(name string, perm FileMode) error
|
||||
Open(name string) (File, error)
|
||||
ReadSymlink(name string) (string, LinkTargetType, error)
|
||||
Remove(name string) error
|
||||
Rename(oldname, newname string) error
|
||||
Stat(name string) (FileInfo, error)
|
||||
SymlinksSupported() bool
|
||||
Walk(root string, walkFn WalkFunc) error
|
||||
}
|
||||
|
||||
// The File interface abstracts access to a regular file, being a somewhat
|
||||
// smaller interface than os.File
|
||||
type File interface {
|
||||
io.Reader
|
||||
io.WriterAt
|
||||
io.Closer
|
||||
Truncate(size int64) error
|
||||
}
|
||||
|
||||
// The FileInfo interface is almost the same as os.FileInfo, but with the
|
||||
// Sys method removed (as we don't want to expose whatever is underlying)
|
||||
// and with a couple of convenience methods added.
|
||||
type FileInfo interface {
|
||||
// Standard things present in os.FileInfo
|
||||
Name() string
|
||||
Mode() FileMode
|
||||
Size() int64
|
||||
ModTime() time.Time
|
||||
IsDir() bool
|
||||
// Extensions
|
||||
IsRegular() bool
|
||||
IsSymlink() bool
|
||||
}
|
||||
|
||||
// FileMode is similar to os.FileMode
|
||||
type FileMode uint32
|
||||
|
||||
// DefaultFilesystem is the fallback to use when nothing explicitly has
|
||||
// been passed.
|
||||
var DefaultFilesystem Filesystem = new(BasicFilesystem)
|
||||
|
||||
// SkipDir is used as a return value from WalkFuncs to indicate that
|
||||
// the directory named in the call is to be skipped. It is not returned
|
||||
// as an error by any function.
|
||||
var errSkipDir = errors.New("skip this directory")
|
||||
var SkipDir = errSkipDir // silences the lint warning...
|
||||
@@ -69,6 +69,7 @@ type Matcher struct {
|
||||
matches *cache
|
||||
curHash string
|
||||
stop chan struct{}
|
||||
modtimes map[string]time.Time
|
||||
mut sync.Mutex
|
||||
}
|
||||
|
||||
@@ -85,25 +86,41 @@ func New(withCache bool) *Matcher {
|
||||
}
|
||||
|
||||
func (m *Matcher) Load(file string) error {
|
||||
// No locking, Parse() does the locking
|
||||
m.mut.Lock()
|
||||
defer m.mut.Unlock()
|
||||
|
||||
if m.patternsUnchanged(file) {
|
||||
return nil
|
||||
}
|
||||
|
||||
fd, err := os.Open(file)
|
||||
if err != nil {
|
||||
// We do a parse with empty patterns to clear out the hash, cache etc.
|
||||
m.Parse(&bytes.Buffer{}, file)
|
||||
m.parseLocked(&bytes.Buffer{}, file)
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
return m.Parse(fd, file)
|
||||
info, err := fd.Stat()
|
||||
if err != nil {
|
||||
m.parseLocked(&bytes.Buffer{}, file)
|
||||
return err
|
||||
}
|
||||
|
||||
m.modtimes = map[string]time.Time{
|
||||
file: info.ModTime(),
|
||||
}
|
||||
|
||||
return m.parseLocked(fd, file)
|
||||
}
|
||||
|
||||
func (m *Matcher) Parse(r io.Reader, file string) error {
|
||||
m.mut.Lock()
|
||||
defer m.mut.Unlock()
|
||||
return m.parseLocked(r, file)
|
||||
}
|
||||
|
||||
seen := map[string]bool{file: true}
|
||||
patterns, err := parseIgnoreFile(r, file, seen)
|
||||
func (m *Matcher) parseLocked(r io.Reader, file string) error {
|
||||
patterns, err := parseIgnoreFile(r, file, m.modtimes)
|
||||
// Error is saved and returned at the end. We process the patterns
|
||||
// (possibly blank) anyway.
|
||||
|
||||
@@ -122,6 +139,26 @@ func (m *Matcher) Parse(r io.Reader, file string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// patternsUnchanged returns true if none of the files making up the loaded
|
||||
// patterns have changed since last check.
|
||||
func (m *Matcher) patternsUnchanged(file string) bool {
|
||||
if _, ok := m.modtimes[file]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
for filename, modtime := range m.modtimes {
|
||||
info, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if !info.ModTime().Equal(modtime) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *Matcher) Match(file string) (result Result) {
|
||||
if m == nil {
|
||||
return resultNotMatched
|
||||
@@ -221,11 +258,10 @@ func hashPatterns(patterns []Pattern) string {
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
func loadIgnoreFile(file string, seen map[string]bool) ([]Pattern, error) {
|
||||
if seen[file] {
|
||||
func loadIgnoreFile(file string, modtimes map[string]time.Time) ([]Pattern, error) {
|
||||
if _, ok := modtimes[file]; ok {
|
||||
return nil, fmt.Errorf("Multiple include of ignore file %q", file)
|
||||
}
|
||||
seen[file] = true
|
||||
|
||||
fd, err := os.Open(file)
|
||||
if err != nil {
|
||||
@@ -233,10 +269,16 @@ func loadIgnoreFile(file string, seen map[string]bool) ([]Pattern, error) {
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
return parseIgnoreFile(fd, file, seen)
|
||||
info, err := fd.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
modtimes[file] = info.ModTime()
|
||||
|
||||
return parseIgnoreFile(fd, file, modtimes)
|
||||
}
|
||||
|
||||
func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]Pattern, error) {
|
||||
func parseIgnoreFile(fd io.Reader, currentFile string, modtimes map[string]time.Time) ([]Pattern, error) {
|
||||
var patterns []Pattern
|
||||
|
||||
defaultResult := resultInclude
|
||||
@@ -302,7 +344,7 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
|
||||
} else if strings.HasPrefix(line, "#include ") {
|
||||
includeRel := line[len("#include "):]
|
||||
includeFile := filepath.Join(filepath.Dir(currentFile), includeRel)
|
||||
includes, err := loadIgnoreFile(includeFile, seen)
|
||||
includes, err := loadIgnoreFile(includeFile, modtimes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("include of %q: %v", includeRel, err)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIgnore(t *testing.T) {
|
||||
@@ -276,9 +277,13 @@ func TestCaching(t *testing.T) {
|
||||
t.Fatal("Expected 4 cached results")
|
||||
}
|
||||
|
||||
// Modify the include file, expect empty cache
|
||||
// Modify the include file, expect empty cache. Ensure the timestamp on
|
||||
// the file changes.
|
||||
|
||||
fd2.WriteString("/z/\n")
|
||||
fd2.Sync()
|
||||
fakeTime := time.Now().Add(5 * time.Second)
|
||||
os.Chtimes(fd2.Name(), fakeTime, fakeTime)
|
||||
|
||||
err = pats.Load(fd1.Name())
|
||||
if err != nil {
|
||||
@@ -308,6 +313,9 @@ func TestCaching(t *testing.T) {
|
||||
// Modify the root file, expect cache to be invalidated
|
||||
|
||||
fd1.WriteString("/a/\n")
|
||||
fd1.Sync()
|
||||
fakeTime = time.Now().Add(5 * time.Second)
|
||||
os.Chtimes(fd1.Name(), fakeTime, fakeTime)
|
||||
|
||||
err = pats.Load(fd1.Name())
|
||||
if err != nil {
|
||||
@@ -484,6 +492,9 @@ func TestCacheReload(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fd.Sync()
|
||||
fakeTime := time.Now().Add(5 * time.Second)
|
||||
os.Chtimes(fd.Name(), fakeTime, fakeTime)
|
||||
|
||||
err = pats.Load(fd.Name())
|
||||
if err != nil {
|
||||
|
||||
@@ -1248,7 +1248,7 @@ func (m *Model) SetIgnores(folder string, content []string) error {
|
||||
|
||||
path := filepath.Join(cfg.Path(), ".stignore")
|
||||
|
||||
fd, err := osutil.CreateAtomic(path, 0644)
|
||||
fd, err := osutil.CreateAtomic(path)
|
||||
if err != nil {
|
||||
l.Warnln("Saving .stignore:", err)
|
||||
return err
|
||||
|
||||
@@ -921,7 +921,7 @@ func TestIgnores(t *testing.T) {
|
||||
t.Errorf("Incorrect ignores: %v != %v", ignores, expected)
|
||||
}
|
||||
|
||||
ignores, _, err = m.GetIgnores("doesnotexist")
|
||||
_, _, err = m.GetIgnores("doesnotexist")
|
||||
if err == nil {
|
||||
t.Error("No error")
|
||||
}
|
||||
@@ -1773,6 +1773,8 @@ func TestScanNoDatabaseWrite(t *testing.T) {
|
||||
}
|
||||
defer m.SetIgnores("default", curIgn)
|
||||
m.SetIgnores("default", nil)
|
||||
fakeTime := time.Now().Add(5 * time.Second)
|
||||
os.Chtimes("testdata/.stignore", fakeTime, fakeTime)
|
||||
|
||||
// Scan the folder twice. The second scan should be a no-op database wise
|
||||
|
||||
@@ -1789,6 +1791,8 @@ func TestScanNoDatabaseWrite(t *testing.T) {
|
||||
// Ignore a file we know exists. It'll be updated in the database.
|
||||
|
||||
m.SetIgnores("default", []string{"foo"})
|
||||
fakeTime = time.Now().Add(10 * time.Second)
|
||||
os.Chtimes("testdata/.stignore", fakeTime, fakeTime)
|
||||
|
||||
m.ScanFolder("default")
|
||||
c2 := db.Committed()
|
||||
|
||||
@@ -22,11 +22,11 @@ type roFolder struct {
|
||||
folder
|
||||
}
|
||||
|
||||
func newROFolder(model *Model, config config.FolderConfiguration, _ versioner.Versioner, _ *fs.MtimeFS) service {
|
||||
func newROFolder(model *Model, cfg config.FolderConfiguration, _ versioner.Versioner, _ *fs.MtimeFS) service {
|
||||
return &roFolder{
|
||||
folder: folder{
|
||||
stateTracker: newStateTracker(config.ID),
|
||||
scan: newFolderScanner(config),
|
||||
stateTracker: newStateTracker(cfg.ID),
|
||||
scan: newFolderScanner(cfg),
|
||||
stop: make(chan struct{}),
|
||||
model: model,
|
||||
},
|
||||
|
||||
@@ -143,7 +143,7 @@ func newRWFolder(model *Model, cfg config.FolderConfiguration, ver versioner.Ver
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *rwFolder) configureCopiersAndPullers(config config.FolderConfiguration) {
|
||||
func (f *rwFolder) configureCopiersAndPullers(cfg config.FolderConfiguration) {
|
||||
if f.copiers == 0 {
|
||||
f.copiers = defaultCopiers
|
||||
}
|
||||
@@ -151,16 +151,16 @@ func (f *rwFolder) configureCopiersAndPullers(config config.FolderConfiguration)
|
||||
f.pullers = defaultPullers
|
||||
}
|
||||
|
||||
if config.PullerPauseS == 0 {
|
||||
if cfg.PullerPauseS == 0 {
|
||||
f.pause = defaultPullerPause
|
||||
} else {
|
||||
f.pause = time.Duration(config.PullerPauseS) * time.Second
|
||||
f.pause = time.Duration(cfg.PullerPauseS) * time.Second
|
||||
}
|
||||
|
||||
if config.PullerSleepS == 0 {
|
||||
if cfg.PullerSleepS == 0 {
|
||||
f.sleep = defaultPullerSleep
|
||||
} else {
|
||||
f.sleep = time.Duration(config.PullerSleepS) * time.Second
|
||||
f.sleep = time.Duration(cfg.PullerSleepS) * time.Second
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,23 +29,19 @@ type AtomicWriter struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// CreateAtomic is like os.Create with a FileMode, except a temporary file
|
||||
// name is used instead of the given name.
|
||||
func CreateAtomic(path string, mode os.FileMode) (*AtomicWriter, error) {
|
||||
// CreateAtomic is like os.Create, except a temporary file name is used
|
||||
// instead of the given name. The file is created with secure (0600)
|
||||
// permissions.
|
||||
func CreateAtomic(path string) (*AtomicWriter, error) {
|
||||
// The security of this depends on the tempfile having secure
|
||||
// permissions, 0600, from the beginning. This is what ioutil.TempFile
|
||||
// does. We have a test that verifies that that is the case, should this
|
||||
// ever change in the standard library in the future.
|
||||
fd, err := ioutil.TempFile(filepath.Dir(path), TempPrefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// chmod fails on Android so don't even try
|
||||
if runtime.GOOS != "android" {
|
||||
if err := os.Chmod(fd.Name(), mode); err != nil {
|
||||
fd.Close()
|
||||
os.Remove(fd.Name())
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
w := &AtomicWriter{
|
||||
path: path,
|
||||
next: fd,
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestCreateAtomicCreate(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
w, err := CreateAtomic("testdata/file", 0644)
|
||||
w, err := CreateAtomic("testdata/file")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func TestCreateAtomicReplace(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
w, err := CreateAtomic("testdata/file", 0644)
|
||||
w, err := CreateAtomic("testdata/file")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
44
lib/osutil/atomic_unix_test.go
Normal file
44
lib/osutil/atomic_unix_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright (C) 2016 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/.
|
||||
|
||||
//+build !windows
|
||||
|
||||
// (No syscall.Umask or the equivalent on Windows)
|
||||
|
||||
package osutil
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTempFilePermissions(t *testing.T) {
|
||||
// Set a zero umask, so any files created will have the permission bits
|
||||
// asked for in the create call and nothing less.
|
||||
oldMask := syscall.Umask(0)
|
||||
defer syscall.Umask(oldMask)
|
||||
|
||||
fd, err := ioutil.TempFile("", "test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
info, err := fd.Stat()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(fd.Name())
|
||||
defer fd.Close()
|
||||
|
||||
// The temp file should have 0600 permissions at the most, or we have a
|
||||
// security problem in CreateAtomic.
|
||||
t.Logf("Got 0%03o", info.Mode())
|
||||
if info.Mode()&^0600 != 0 {
|
||||
t.Errorf("Permission 0%03o is too generous", info.Mode())
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
)
|
||||
|
||||
var ErrNoHome = errors.New("No home directory found - set $HOME (or the platform equivalent).")
|
||||
var errNoHome = errors.New("no home directory found - set $HOME (or the platform equivalent)")
|
||||
|
||||
// Try to keep this entire operation atomic-like. We shouldn't be doing this
|
||||
// often enough that there is any contention on this lock.
|
||||
@@ -123,7 +123,7 @@ func getHomeDir() (string, error) {
|
||||
}
|
||||
|
||||
if home == "" {
|
||||
return "", ErrNoHome
|
||||
return "", errNoHome
|
||||
}
|
||||
|
||||
return home, nil
|
||||
|
||||
@@ -85,7 +85,6 @@ func (h holder) String() string {
|
||||
|
||||
type loggedMutex struct {
|
||||
sync.Mutex
|
||||
start time.Time
|
||||
holder atomic.Value
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "STDISCOSRV" "1" "November 21, 2016" "v0.14" "Syncthing"
|
||||
.TH "STDISCOSRV" "1" "November 25, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
stdiscosrv \- Syncthing Discovery Server
|
||||
.
|
||||
@@ -46,7 +46,7 @@ stdiscosrv [\-cert=<file>] [\-db\-backend=<string>] [\-db\-dsn=<string>] [\-debu
|
||||
.SH DESCRIPTION
|
||||
.sp
|
||||
Syncthing relies on a discovery server to find peers on the internet. Anyone
|
||||
can run a discovery server and point its syncthing installations to it.
|
||||
can run a discovery server and point Syncthing installations to it.
|
||||
.SH OPTIONS
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
@@ -137,9 +137,9 @@ to select a different location.
|
||||
\fBNOTE:\fP
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
If you are running an instance of syncthing on the discovery server,
|
||||
you must either add that instance to other nodes using a static
|
||||
address or bind the discovery server and syncthing instances to
|
||||
If you are running an instance of Syncthing on the discovery server,
|
||||
you must either add that instance to other devices using a static
|
||||
address or bind the discovery server and Syncthing instances to
|
||||
different IP addresses.
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "STRELAYSRV" "1" "November 21, 2016" "v0.14" "Syncthing"
|
||||
.TH "STRELAYSRV" "1" "November 25, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
strelaysrv \- Syncthing Relay Server
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-BEP" "7" "November 21, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-BEP" "7" "November 25, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-bep \- Block Exchange Protocol v1
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-CONFIG" "5" "November 21, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-CONFIG" "5" "November 25, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-config \- Syncthing Configuration
|
||||
.
|
||||
@@ -233,7 +233,7 @@ If the original introducer unshares this folder with this device, our device wil
|
||||
and unshare the folder (subject to skipIntroductionRemovals being false on the introducer device).
|
||||
All mentioned devices are those that will be sharing the folder in question.
|
||||
Each mentioned device must have a separate \fBdevice\fP element later in the file.
|
||||
It is customary that the local device ID is included in all repositories.
|
||||
It is customary that the local device ID is included in all folders.
|
||||
Syncthing will currently add this automatically if it is not present in
|
||||
the configuration file.
|
||||
.TP
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-DEVICE-IDS" "7" "November 21, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-DEVICE-IDS" "7" "November 25, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-device-ids \- Understanding Device IDs
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-EVENT-API" "7" "November 21, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-EVENT-API" "7" "November 25, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-event-api \- Event API
|
||||
.
|
||||
@@ -572,7 +572,7 @@ New in version 0.11.10: The \fBmetadata\fP action.
|
||||
.sp
|
||||
Generated upon scan whenever the local disk has discovered an updated file from the
|
||||
previous scan. This does NOT include events that are discovered and copied from
|
||||
other nodes, only files that were changed on the local filesystem.
|
||||
other devices, only files that were changed on the local filesystem.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-FAQ" "7" "November 21, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-FAQ" "7" "November 25, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-faq \- Frequently Asked Questions
|
||||
.
|
||||
@@ -45,12 +45,12 @@ It\(aqs \fBSyncthing\fP, although the command and source repository is spelled
|
||||
\fBsyncthing\fP so it may be referred to in that way as well. It\(aqs definitely not
|
||||
SyncThing, even though the abbreviation \fBst\fP is used in some
|
||||
circumstances and file names.
|
||||
.SS How does Syncthing differ from BitTorrent Sync?
|
||||
.SS How does Syncthing differ from BitTorrent/Resilio Sync?
|
||||
.sp
|
||||
The two are different and not related. Syncthing and BitTorrent Sync accomplish
|
||||
The two are different and not related. Syncthing and BitTorrent/Resilio Sync accomplish
|
||||
some of the same things, namely syncing files between two or more computers.
|
||||
.sp
|
||||
BitTorrent Sync by BitTorrent, Inc is a proprietary peer\-to\-peer file
|
||||
BitTorrent Sync, now called Resilio Sync, is a proprietary peer\-to\-peer file
|
||||
synchronization tool available for Windows, Mac, Linux, Android, iOS, Windows
|
||||
Phone, Amazon Kindle Fire and BSD. [1] Syncthing is an open source file
|
||||
synchronization tool.
|
||||
@@ -103,8 +103,8 @@ Sparse file sparseness (will become sparse, when supported by the OS & filesyste
|
||||
.sp
|
||||
Syncthing segments files into pieces, called blocks, to transfer data from one
|
||||
device to another. Therefore, multiple devices can share the synchronization
|
||||
load, in a similar way as the torrent protocol. The more devices you have online
|
||||
(and synchronized), the faster an additional device will receive the data
|
||||
load, in a similar way to the torrent protocol. The more devices you have online,
|
||||
the faster an additional device will receive the data
|
||||
because small blocks will be fetched from all devices in parallel.
|
||||
.sp
|
||||
Syncthing handles renaming files and updating their metadata in an efficient
|
||||
@@ -176,11 +176,11 @@ Android. For other setups, consider using \fI\%syncthing\-inotify\fP <\fBhttps:/
|
||||
.SS Should I keep my device IDs secret?
|
||||
.sp
|
||||
No. The IDs are not sensitive. Given a device ID it\(aqs possible to find the IP
|
||||
address for that node, if global discovery is enabled on it. Knowing the device
|
||||
ID doesn\(aqt help you actually establish a connection to that node or get a list
|
||||
address for that device, if global discovery is enabled on it. Knowing the device
|
||||
ID doesn\(aqt help you actually establish a connection to that device or get a list
|
||||
of files, etc.
|
||||
.sp
|
||||
For a connection to be established, both nodes need to know about the other\(aqs
|
||||
For a connection to be established, both devices need to know about the other\(aqs
|
||||
device ID. It\(aqs not possible (in practice) to forge a device ID. (To forge a
|
||||
device ID you need to create a TLS certificate with that specific SHA\-256 hash.
|
||||
If you can do that, you can spoof any TLS certificate. The world is your
|
||||
@@ -226,12 +226,12 @@ the new path.
|
||||
.sp
|
||||
It\(aqs best to do this when the folder is already in sync between your
|
||||
devices, as it is otherwise unpredictable which changes will "win" after the
|
||||
move. Changes made on other devices may be overwritten, or changed made
|
||||
move. Changes made on other devices may be overwritten, or changes made
|
||||
locally may be overwritten by those on other devices.
|
||||
.sp
|
||||
An alternative way is to shut down Syncthing, move the folder on disk, edit
|
||||
the path directly in the configuration file and then start Syncthing again.
|
||||
.SS How to configure multiple users on a single machine?
|
||||
.SS How do I configure multiple users on a single machine?
|
||||
.sp
|
||||
Each user should run their own Syncthing instance. Be aware that you might need
|
||||
to configure listening ports such that they do not overlap (see config).
|
||||
@@ -243,7 +243,7 @@ programs to achieve this such as rsync or Unison.
|
||||
.SS Is Syncthing my ideal backup application?
|
||||
.sp
|
||||
No. Syncthing is not a great backup application because all changes to your
|
||||
files (modifications, deletions, etc) will be propagated to all your
|
||||
files (modifications, deletions, etc.) will be propagated to all your
|
||||
devices. You can enable versioning, but we encourage the use of other tools
|
||||
to keep your data safe from your (or our) mistakes.
|
||||
.SS Why is there no iOS client?
|
||||
@@ -264,11 +264,11 @@ the brackets, like so: \fBq\e[abc\e]x\fP\&.
|
||||
On Windows, escaping special characters is not supported as the \fB\e\fP
|
||||
character is used as a path separator. On the other hand, special characters
|
||||
such as \fB[\fP and \fB?\fP are not allowed in file names on Windows.
|
||||
.SS Why is the setup more complicated than BTSync?
|
||||
.SS Why is the setup more complicated than BitTorrent/Resilio Sync?
|
||||
.sp
|
||||
Security over convenience. In Syncthing you have to setup both sides to
|
||||
connect two nodes. An attacker can\(aqt do much with a stolen node ID, because
|
||||
you have to add the node on the other side too. You have better control
|
||||
connect two devices. An attacker can\(aqt do much with a stolen device ID, because
|
||||
you have to add the device on the other side too. You have better control
|
||||
where your files are transferred.
|
||||
.sp
|
||||
This is an area that we are working to improve in the long term.
|
||||
@@ -306,7 +306,7 @@ to
|
||||
Then the GUI is accessible from everywhere. You should set a password and
|
||||
enable HTTPS with this configuration. You can do this from inside the GUI.
|
||||
.sp
|
||||
If both your computers are Unixy (Linux, Mac, etc) You can also leave the
|
||||
If both your computers are Unix\-like (Linux, Mac, etc.) you can also leave the
|
||||
GUI settings at default and use an ssh port forward to access it. For
|
||||
example,
|
||||
.INDENT 0.0
|
||||
@@ -336,7 +336,7 @@ $ ssh \-N \-L 9090:127.0.0.1:8384 user@othercomputer.example.com
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
If only your remote computer is Unixy,
|
||||
If only your remote computer is Unix\-like,
|
||||
you can still access it with ssh from Windows.
|
||||
.sp
|
||||
Under Windows 10 (64 bit) you can use the same ssh command if you install
|
||||
@@ -374,7 +374,7 @@ In all cases, username/password authentication and HTTPS should be used.
|
||||
.SS My Syncthing database is corrupt
|
||||
.sp
|
||||
This is almost always a result of bad RAM, storage device or other hardware. When the index database is found to be corrupt Syncthing cannot operate and will note this in the logs and exit. To overcome this delete the \fI\%database folder\fP <\fBhttps://docs.syncthing.net/users/config.html#description\fP> inside Syncthing\(aqs home directory and re\-start Syncthing. It will then need to perform a full re\-hashing of all shared folders. You should check your system in case the underlying cause is indeed faulty hardware which may put the system at risk of further data loss.
|
||||
.SS I don\(aqt like the GUI / Theme. Can it be changed?
|
||||
.SS I don\(aqt like the GUI or the theme. Can it be changed?
|
||||
.sp
|
||||
You can change the theme in the settings. Syncthing ships with other themes
|
||||
than the default.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-GLOBALDISCO" "7" "November 21, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-GLOBALDISCO" "7" "November 25, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-globaldisco \- Global Discovery Protocol v3
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-LOCALDISCO" "7" "November 21, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-LOCALDISCO" "7" "November 25, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-localdisco \- Local Discovery Protocol v4
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-NETWORKING" "7" "November 21, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-NETWORKING" "7" "November 25, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-networking \- Firewall Setup
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-RELAY" "7" "November 21, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-RELAY" "7" "November 25, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-relay \- Relay Protocol v1
|
||||
.
|
||||
@@ -604,12 +604,12 @@ did capture all the traffic, and even if the attacker did get their hands on the
|
||||
device keys, they would still not be able to recover/decrypt any traffic which
|
||||
was transported via the relay.
|
||||
.sp
|
||||
After establishing a relay session, syncthing looks at the SessionInvitation
|
||||
After establishing a relay session, Syncthing looks at the SessionInvitation
|
||||
message, and depending which side it has received, wraps the raw socket in
|
||||
either a TLS client socket or a TLS server socket depending on the ServerSocket
|
||||
boolean value in the SessionInvitation, and starts the TLS handshake.
|
||||
.sp
|
||||
From that point onwards it functions exactly the same way as if syncthing was
|
||||
From that point onwards it functions exactly the same way as if Syncthing was
|
||||
establishing a direct connection with the other device over the internet,
|
||||
performing device ID validation, and full TLS encryption, and provides the same
|
||||
security properties as it would provide when connecting over the internet.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-REST-API" "7" "November 21, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-REST-API" "7" "November 25, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-rest-api \- REST API
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-SECURITY" "7" "November 21, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-SECURITY" "7" "November 25, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-security \- Security Principles
|
||||
.
|
||||
@@ -36,9 +36,9 @@ possible for an attacker to join a cluster uninvited, and it should not be
|
||||
possible to extract private information from intercepted traffic. Currently this
|
||||
is implemented as follows.
|
||||
.sp
|
||||
All device to device traffic is protected by TLS. To prevent uninvited nodes
|
||||
from joining a cluster, the certificate fingerprint of each node is compared
|
||||
to a preset list of acceptable nodes at connection establishment. The
|
||||
All device to device traffic is protected by TLS. To prevent uninvited devices
|
||||
from joining a cluster, the certificate fingerprint of each device is compared
|
||||
to a preset list of acceptable devices at connection establishment. The
|
||||
fingerprint is computed as the SHA\-256 hash of the certificate and displayed
|
||||
in BASE32 encoding to form a reasonably compact and convenient string.
|
||||
.sp
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-STIGNORE" "5" "November 21, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-STIGNORE" "5" "November 25, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-stignore \- Prevent files from being synchronized to other nodes
|
||||
.
|
||||
@@ -43,12 +43,12 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.UNINDENT
|
||||
.SH DESCRIPTION
|
||||
.sp
|
||||
If some files should not be synchronized to other nodes, a file called
|
||||
If some files should not be synchronized to other devices, a file called
|
||||
\fB\&.stignore\fP can be created containing file patterns to ignore. The
|
||||
\fB\&.stignore\fP file must be placed in the root of the repository. The
|
||||
\fB\&.stignore\fP file itself will never be synced to other nodes, although it can
|
||||
\fB#include\fP files that \fIare\fP synchronized between nodes. All patterns are
|
||||
relative to the repository root.
|
||||
\fB\&.stignore\fP file must be placed in the root of the folder. The
|
||||
\fB\&.stignore\fP file itself will never be synced to other devices, although it can
|
||||
\fB#include\fP files that \fIare\fP synchronized between devices. All patterns are
|
||||
relative to the folder root.
|
||||
.sp
|
||||
\fBNOTE:\fP
|
||||
.INDENT 0.0
|
||||
@@ -88,7 +88,7 @@ A pattern beginning with \fB#include\fP results in loading patterns
|
||||
from the named file. It is an error for a file to not exist or be
|
||||
included more than once. Note that while this can be used to include
|
||||
patterns from a file in a subdirectory, the patterns themselves are
|
||||
still relative to the repository \fIroot\fP\&. Example:
|
||||
still relative to the folder \fIroot\fP\&. Example:
|
||||
\fB#include more\-patterns.txt\fP\&.
|
||||
.IP \(bu 2
|
||||
A pattern beginning with a \fB!\fP prefix negates the pattern: matching files
|
||||
@@ -201,7 +201,7 @@ Currently the effects on who is in sync with what can be a bit confusing
|
||||
when using ignore patterns. This should be cleared up in a future
|
||||
version...
|
||||
.sp
|
||||
Assume two nodes, Alice and Bob, where Alice has 100 files to share, but
|
||||
Assume two devices, Alice and Bob, where Alice has 100 files to share, but
|
||||
Bob ignores 25 of these. From Alice\(aqs point of view Bob will become
|
||||
about 75% in sync (the actual number depends on the sizes of the
|
||||
individual files) and remain in "Syncing" state even though it is in
|
||||
@@ -211,7 +211,7 @@ view.
|
||||
.sp
|
||||
If Bob adds files that have already been synced to the ignore list, they
|
||||
will remain in the "global" view but disappear from the "local" view.
|
||||
The end result is more files in the global repository than in the local,
|
||||
The end result is more files in the global folder than in the local,
|
||||
but still 100% in sync (\fI\%issue #624\fP <\fBhttps://github.com/syncthing/syncthing/issues/624\fP>). From Alice\(aqs point of view, Bob
|
||||
will remain 100% in sync until the next reconnect, because Bob has
|
||||
already announced that he has the files that are now suddenly ignored.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-VERSIONING" "7" "November 21, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-VERSIONING" "7" "November 25, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-versioning \- Keep automatic backups of deleted files by other nodes
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING" "1" "November 21, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING" "1" "November 25, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing \- Syncthing
|
||||
.
|
||||
|
||||
Reference in New Issue
Block a user