mirror of
https://github.com/octoleo/syncthing.git
synced 2024-12-23 03:18:59 +00:00
lib/symlinks need not depend on protocol
This commit is contained in:
parent
21c3994650
commit
446a938b06
@ -1080,7 +1080,11 @@ func (p *rwFolder) shortcutFile(file protocol.FileInfo) error {
|
|||||||
|
|
||||||
// shortcutSymlink changes the symlinks type if necessary.
|
// shortcutSymlink changes the symlinks type if necessary.
|
||||||
func (p *rwFolder) shortcutSymlink(file protocol.FileInfo) (err error) {
|
func (p *rwFolder) shortcutSymlink(file protocol.FileInfo) (err error) {
|
||||||
err = symlinks.ChangeType(filepath.Join(p.dir, file.Name), file.Flags)
|
tt := symlinks.TargetFile
|
||||||
|
if file.IsDirectory() {
|
||||||
|
tt = symlinks.TargetDirectory
|
||||||
|
}
|
||||||
|
err = symlinks.ChangeType(filepath.Join(p.dir, file.Name), tt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Infof("Puller (folder %q, file %q): symlink shortcut: %v", p.folder, file.Name, err)
|
l.Infof("Puller (folder %q, file %q): symlink shortcut: %v", p.folder, file.Name, err)
|
||||||
p.newError(file.Name, err)
|
p.newError(file.Name, err)
|
||||||
@ -1316,7 +1320,11 @@ func (p *rwFolder) performFinish(state *sharedPullerState) error {
|
|||||||
// Remove the file, and replace it with a symlink.
|
// Remove the file, and replace it with a symlink.
|
||||||
err = osutil.InWritableDir(func(path string) error {
|
err = osutil.InWritableDir(func(path string) error {
|
||||||
os.Remove(path)
|
os.Remove(path)
|
||||||
return symlinks.Create(path, string(content), state.file.Flags)
|
tt := symlinks.TargetFile
|
||||||
|
if state.file.IsDirectory() {
|
||||||
|
tt = symlinks.TargetDirectory
|
||||||
|
}
|
||||||
|
return symlinks.Create(path, string(content), tt)
|
||||||
}, state.realName)
|
}, state.realName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -311,8 +311,7 @@ func (w *Walker) walkAndHashFiles(fchan, dchan chan protocol.FileInfo) filepath.
|
|||||||
// checking that their existing blocks match with the blocks in
|
// checking that their existing blocks match with the blocks in
|
||||||
// the index.
|
// the index.
|
||||||
|
|
||||||
target, flags, err := symlinks.Read(p)
|
target, targetType, err := symlinks.Read(p)
|
||||||
flags = flags & protocol.SymlinkTypeMask
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugln("readlink error:", p, err)
|
l.Debugln("readlink error:", p, err)
|
||||||
@ -337,7 +336,7 @@ func (w *Walker) walkAndHashFiles(fchan, dchan chan protocol.FileInfo) filepath.
|
|||||||
// - the symlink type (file/dir) was the same
|
// - the symlink type (file/dir) was the same
|
||||||
// - the block list (i.e. hash of target) was the same
|
// - the block list (i.e. hash of target) was the same
|
||||||
cf, ok = w.CurrentFiler.CurrentFile(rn)
|
cf, ok = w.CurrentFiler.CurrentFile(rn)
|
||||||
if ok && !cf.IsDeleted() && cf.IsSymlink() && !cf.IsInvalid() && SymlinkTypeEqual(flags, cf.Flags) && BlocksEqual(cf.Blocks, blocks) {
|
if ok && !cf.IsDeleted() && cf.IsSymlink() && !cf.IsInvalid() && SymlinkTypeEqual(targetType, cf) && BlocksEqual(cf.Blocks, blocks) {
|
||||||
return skip
|
return skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -345,7 +344,7 @@ func (w *Walker) walkAndHashFiles(fchan, dchan chan protocol.FileInfo) filepath.
|
|||||||
f := protocol.FileInfo{
|
f := protocol.FileInfo{
|
||||||
Name: rn,
|
Name: rn,
|
||||||
Version: cf.Version.Update(w.ShortID),
|
Version: cf.Version.Update(w.ShortID),
|
||||||
Flags: protocol.FlagSymlink | flags | protocol.FlagNoPermBits | 0666,
|
Flags: uint32(protocol.FlagSymlink | protocol.FlagNoPermBits | 0666 | SymlinkFlags(targetType)),
|
||||||
Modified: 0,
|
Modified: 0,
|
||||||
Blocks: blocks,
|
Blocks: blocks,
|
||||||
}
|
}
|
||||||
@ -467,7 +466,7 @@ func PermsEqual(a, b uint32) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SymlinkTypeEqual(disk, index uint32) bool {
|
func SymlinkTypeEqual(disk symlinks.TargetType, f protocol.FileInfo) bool {
|
||||||
// If the target is missing, Unix never knows what type of symlink it is
|
// If the target is missing, Unix never knows what type of symlink it is
|
||||||
// and Windows always knows even if there is no target. Which means that
|
// and Windows always knows even if there is no target. Which means that
|
||||||
// without this special check a Unix node would be fighting with a Windows
|
// without this special check a Unix node would be fighting with a Windows
|
||||||
@ -476,8 +475,25 @@ func SymlinkTypeEqual(disk, index uint32) bool {
|
|||||||
// know means you are on Unix, and on Unix you don't really care what the
|
// know means you are on Unix, and on Unix you don't really care what the
|
||||||
// target type is. The moment you do know, and if something doesn't match,
|
// target type is. The moment you do know, and if something doesn't match,
|
||||||
// that will propagate through the cluster.
|
// that will propagate through the cluster.
|
||||||
if disk&protocol.FlagSymlinkMissingTarget != 0 && index&protocol.FlagSymlinkMissingTarget == 0 {
|
switch disk {
|
||||||
|
case symlinks.TargetUnknown:
|
||||||
return true
|
return true
|
||||||
|
case symlinks.TargetDirectory:
|
||||||
|
return f.IsDirectory() && f.Flags&protocol.FlagSymlinkMissingTarget == 0
|
||||||
|
case symlinks.TargetFile:
|
||||||
|
return !f.IsDirectory() && f.Flags&protocol.FlagSymlinkMissingTarget == 0
|
||||||
}
|
}
|
||||||
return disk&protocol.SymlinkTypeMask == index&protocol.SymlinkTypeMask
|
panic("unknown symlink TargetType")
|
||||||
|
}
|
||||||
|
|
||||||
|
func SymlinkFlags(t symlinks.TargetType) uint32 {
|
||||||
|
switch t {
|
||||||
|
case symlinks.TargetFile:
|
||||||
|
return 0
|
||||||
|
case symlinks.TargetDirectory:
|
||||||
|
return protocol.FlagDirectory
|
||||||
|
case symlinks.TargetUnknown:
|
||||||
|
return protocol.FlagSymlinkMissingTarget
|
||||||
|
}
|
||||||
|
panic("unknown symlink TargetType")
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/syncthing/protocol"
|
"github.com/syncthing/protocol"
|
||||||
"github.com/syncthing/syncthing/lib/ignore"
|
"github.com/syncthing/syncthing/lib/ignore"
|
||||||
"github.com/syncthing/syncthing/lib/osutil"
|
"github.com/syncthing/syncthing/lib/osutil"
|
||||||
|
"github.com/syncthing/syncthing/lib/symlinks"
|
||||||
"golang.org/x/text/unicode/norm"
|
"golang.org/x/text/unicode/norm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -343,3 +344,31 @@ func (l testfileList) String() string {
|
|||||||
b.WriteString("}")
|
b.WriteString("}")
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSymlinkTypeEqual(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
onDiskType symlinks.TargetType
|
||||||
|
inIndexFlags uint32
|
||||||
|
equal bool
|
||||||
|
}{
|
||||||
|
// File is only equal to file
|
||||||
|
{symlinks.TargetFile, 0, true},
|
||||||
|
{symlinks.TargetFile, protocol.FlagDirectory, false},
|
||||||
|
{symlinks.TargetFile, protocol.FlagSymlinkMissingTarget, false},
|
||||||
|
// Directory is only equal to directory
|
||||||
|
{symlinks.TargetDirectory, 0, false},
|
||||||
|
{symlinks.TargetDirectory, protocol.FlagDirectory, true},
|
||||||
|
{symlinks.TargetDirectory, protocol.FlagSymlinkMissingTarget, false},
|
||||||
|
// Unknown is equal to anything
|
||||||
|
{symlinks.TargetUnknown, 0, true},
|
||||||
|
{symlinks.TargetUnknown, protocol.FlagDirectory, true},
|
||||||
|
{symlinks.TargetUnknown, protocol.FlagSymlinkMissingTarget, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
res := SymlinkTypeEqual(tc.onDiskType, protocol.FileInfo{Flags: tc.inIndexFlags})
|
||||||
|
if res != tc.equal {
|
||||||
|
t.Errorf("Incorrect result %v for %v, %v", res, tc.onDiskType, tc.inIndexFlags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -11,7 +11,6 @@ package symlinks
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/syncthing/protocol"
|
|
||||||
"github.com/syncthing/syncthing/lib/osutil"
|
"github.com/syncthing/syncthing/lib/osutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,23 +18,24 @@ var (
|
|||||||
Supported = true
|
Supported = true
|
||||||
)
|
)
|
||||||
|
|
||||||
func Read(path string) (string, uint32, error) {
|
func Read(path string) (string, TargetType, error) {
|
||||||
var mode uint32
|
tt := TargetUnknown
|
||||||
stat, err := os.Stat(path)
|
if stat, err := os.Stat(path); err == nil {
|
||||||
if err != nil {
|
if stat.IsDir() {
|
||||||
mode = protocol.FlagSymlinkMissingTarget
|
tt = TargetDirectory
|
||||||
} else if stat.IsDir() {
|
} else {
|
||||||
mode = protocol.FlagDirectory
|
tt = TargetFile
|
||||||
|
}
|
||||||
}
|
}
|
||||||
path, err = os.Readlink(path)
|
path, err := os.Readlink(path)
|
||||||
|
|
||||||
return osutil.NormalizedFilename(path), mode, err
|
return osutil.NormalizedFilename(path), tt, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func Create(source, target string, flags uint32) error {
|
func Create(source, target string, tt TargetType) error {
|
||||||
return os.Symlink(osutil.NativeFilename(target), source)
|
return os.Symlink(osutil.NativeFilename(target), source)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ChangeType(path string, flags uint32) error {
|
func ChangeType(path string, tt TargetType) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -101,14 +101,14 @@ func (r *reparseData) SubstituteName() string {
|
|||||||
return string(utf16.Decode(r.buffer[offset : offset+length]))
|
return string(utf16.Decode(r.buffer[offset : offset+length]))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Read(path string) (string, uint32, error) {
|
func Read(path string) (string, TargetType, error) {
|
||||||
ptr, err := syscall.UTF16PtrFromString(path)
|
ptr, err := syscall.UTF16PtrFromString(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", protocol.FlagSymlinkMissingTarget, err
|
return "", TargetUnknown, 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)
|
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 {
|
if err != nil || handle == syscall.InvalidHandle {
|
||||||
return "", protocol.FlagSymlinkMissingTarget, err
|
return "", TargetUnknown, err
|
||||||
}
|
}
|
||||||
defer syscall.Close(handle)
|
defer syscall.Close(handle)
|
||||||
var ret uint16
|
var ret uint16
|
||||||
@ -116,21 +116,22 @@ func Read(path string) (string, uint32, error) {
|
|||||||
|
|
||||||
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)
|
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 {
|
if r1 == 0 {
|
||||||
return "", protocol.FlagSymlinkMissingTarget, err
|
return "", TargetUnknown, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var flags uint32
|
tt := TargetUnknown
|
||||||
attr, err := syscall.GetFileAttributes(ptr)
|
if attr, err := syscall.GetFileAttributes(ptr); err == nil {
|
||||||
if err != nil {
|
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||||
flags = protocol.FlagSymlinkMissingTarget
|
tt = TargetDirectory
|
||||||
} else if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
} else {
|
||||||
flags = protocol.FlagDirectory
|
tt = TargetFile
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return osutil.NormalizedFilename(data.PrintName()), flags, nil
|
return osutil.NormalizedFilename(data.PrintName()), tt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Create(source, target string, flags uint32) error {
|
func Create(source, target string, tt TargetType) error {
|
||||||
srcp, err := syscall.UTF16PtrFromString(source)
|
srcp, err := syscall.UTF16PtrFromString(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -146,7 +147,7 @@ func Create(source, target string, flags uint32) error {
|
|||||||
// If the flags doesn't reveal the target type, try to evaluate it
|
// 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.
|
// ourselves, and worst case default to the symlink pointing to a file.
|
||||||
mode := 0
|
mode := 0
|
||||||
if flags&protocol.FlagSymlinkMissingTarget != 0 {
|
if tt == TargetUnknown {
|
||||||
path := target
|
path := target
|
||||||
if !filepath.IsAbs(target) {
|
if !filepath.IsAbs(target) {
|
||||||
path = filepath.Join(filepath.Dir(source), target)
|
path = filepath.Join(filepath.Dir(source), target)
|
||||||
@ -156,7 +157,7 @@ func Create(source, target string, flags uint32) error {
|
|||||||
if err == nil && stat.IsDir() {
|
if err == nil && stat.IsDir() {
|
||||||
mode = Win32SymbolicLinkFlagDirectory
|
mode = Win32SymbolicLinkFlagDirectory
|
||||||
}
|
}
|
||||||
} else if flags&protocol.FlagDirectory != 0 {
|
} else if tt == TargetDirectory {
|
||||||
mode = Win32SymbolicLinkFlagDirectory
|
mode = Win32SymbolicLinkFlagDirectory
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,24 +168,24 @@ func Create(source, target string, flags uint32) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ChangeType(path string, flags uint32) error {
|
func ChangeType(path string, tt TargetType) error {
|
||||||
target, cflags, err := Read(path)
|
target, exTt, err := Read(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// If it's the same type, nothing to do.
|
// If it's the same type, nothing to do.
|
||||||
if cflags&protocol.SymlinkTypeMask == flags&protocol.SymlinkTypeMask {
|
if tt == exTt {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the actual type is unknown, but the new type is file, nothing to do
|
// If the actual type is unknown, but the new type is file, nothing to do
|
||||||
if cflags&protocol.FlagSymlinkMissingTarget != 0 && flags&protocol.FlagDirectory == 0 {
|
if exTt == TargetUnknown && tt != TargetDirectory {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return osutil.InWritableDir(func(path string) error {
|
return osutil.InWritableDir(func(path string) error {
|
||||||
// It should be a symlink as well hence no need to change permissions on
|
// It should be a symlink as well hence no need to change permissions on
|
||||||
// the file.
|
// the file.
|
||||||
os.Remove(path)
|
os.Remove(path)
|
||||||
return Create(path, target, flags)
|
return Create(path, target, tt)
|
||||||
}, path)
|
}, path)
|
||||||
}
|
}
|
||||||
|
15
lib/symlinks/targets.go
Normal file
15
lib/symlinks/targets.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// Copyright (C) 2015 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 symlinks
|
||||||
|
|
||||||
|
type TargetType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
TargetFile TargetType = iota
|
||||||
|
TargetDirectory
|
||||||
|
TargetUnknown
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user