From 5cb1039daff6735e7f94d81c88df5e578fb6b6c7 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 29 Nov 2014 20:44:09 +0100 Subject: [PATCH 1/4] Use Go 1.4 build environment (currently 1.4rc2) --- build.sh | 2 +- docker/Dockerfile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sh b/build.sh index f5ffe76f1..b3f4fddc4 100755 --- a/build.sh +++ b/build.sh @@ -2,7 +2,7 @@ set -euo pipefail IFS=$'\n\t' -DOCKERIMGV=1.3.3-4 +DOCKERIMGV=1.4-1 case "${1:-default}" in default) diff --git a/docker/Dockerfile b/docker/Dockerfile index 564179273..8f015f1db 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,7 +1,7 @@ FROM debian:squeeze MAINTAINER Jakob Borg -ENV GOLANG_VERSION 1.3.3 +ENV GOLANG_VERSION 1.4rc2 # SCMs for "go get", gcc for cgo RUN apt-get update && apt-get install -y \ @@ -47,7 +47,7 @@ RUN bash -xec '\ # Install packages needed for test coverage RUN go get github.com/tools/godep \ - && go get code.google.com/p/go.tools/cmd/cover \ + && go get golang.org/x/tools/cmd/cover \ && go get github.com/axw/gocov/gocov \ && go get github.com/AlekSi/gocov-xml From 1ff9bb8fdc234bdbc3dc017be92aa78d4b346bde Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Thu, 4 Dec 2014 05:55:57 +0100 Subject: [PATCH 2/4] Remove Windows specific implementation --- .../symlinks/{symlink_unix.go => symlink.go} | 30 ++- internal/symlinks/symlink_windows.go | 212 ------------------ 2 files changed, 27 insertions(+), 215 deletions(-) rename internal/symlinks/{symlink_unix.go => symlink.go} (68%) delete mode 100644 internal/symlinks/symlink_windows.go diff --git a/internal/symlinks/symlink_unix.go b/internal/symlinks/symlink.go similarity index 68% rename from internal/symlinks/symlink_unix.go rename to internal/symlinks/symlink.go index 814c74118..eb6440773 100644 --- a/internal/symlinks/symlink_unix.go +++ b/internal/symlinks/symlink.go @@ -13,12 +13,11 @@ // You should have received a copy of the GNU General Public License along // with this program. If not, see . -// +build !windows - package symlinks import ( "os" + "runtime" "github.com/syncthing/syncthing/internal/osutil" "github.com/syncthing/syncthing/internal/protocol" @@ -54,5 +53,30 @@ func Create(source, target string, flags uint32) error { } func ChangeType(path string, flags uint32) error { - return nil + if runtime.GOOS != "windows" { + // This is a Windows-only concept. + return nil + } + + target, cflags, err := Read(path) + if err != nil { + return err + } + + // If it's the same type, nothing to do. + if cflags&protocol.SymlinkTypeMask == flags&protocol.SymlinkTypeMask { + return nil + } + + // If the actual type is unknown, but the new type is file, nothing to do + if cflags&protocol.FlagSymlinkMissingTarget != 0 && flags&protocol.FlagDirectory == 0 { + 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 Create(path, target, flags) + }, path) } diff --git a/internal/symlinks/symlink_windows.go b/internal/symlinks/symlink_windows.go deleted file mode 100644 index 93a14b588..000000000 --- a/internal/symlinks/symlink_windows.go +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright (C) 2014 The Syncthing Authors. -// -// This program is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) -// any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -// more details. -// -// You should have received a copy of the GNU General Public License along -// with this program. If not, see . - -// +build windows - -package symlinks - -import ( - "os" - "path/filepath" - - "github.com/syncthing/syncthing/internal/osutil" - "github.com/syncthing/syncthing/internal/protocol" - - "syscall" - "unicode/utf16" - "unsafe" -) - -const ( - FSCTL_GET_REPARSE_POINT = 0x900a8 - FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 - FILE_ATTRIBUTE_REPARSE_POINT = 0x400 - IO_REPARSE_TAG_SYMLINK = 0xA000000C - SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1 -) - -var ( - modkernel32 = syscall.NewLazyDLL("kernel32.dll") - procDeviceIoControl = modkernel32.NewProc("DeviceIoControl") - procCreateSymbolicLink = modkernel32.NewProc("CreateSymbolicLinkW") - - Supported = 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. - Supported = false - } - }() - - // Needs administrator priviledges. - // 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 := Create(path, base, protocol.FlagDirectory) - if err != nil { - return - } - - isLink, err := IsSymlink(path) - if err != nil || !isLink { - return - } - - target, flags, err := Read(path) - if err != nil || osutil.NativeFilename(target) != base || flags&protocol.FlagDirectory == 0 { - return - } - Supported = true -} - -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]uint16 -} - -func (r *reparseData) PrintName() string { - // No clue why the offset and length is doubled... - offset := r.printNameOffset / 2 - length := r.printNameLength / 2 - return string(utf16.Decode(r.buffer[offset : offset+length])) -} - -func (r *reparseData) SubstituteName() string { - // No clue why the offset and length is doubled... - offset := r.substitueNameOffset / 2 - length := r.substitueNameLength / 2 - return string(utf16.Decode(r.buffer[offset : offset+length])) -} - -func Read(path string) (string, uint32, error) { - ptr, err := syscall.UTF16PtrFromString(path) - if err != nil { - return "", protocol.FlagSymlinkMissingTarget, 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|FILE_FLAG_OPEN_REPARSE_POINT, 0) - if err != nil || handle == syscall.InvalidHandle { - return "", protocol.FlagSymlinkMissingTarget, err - } - defer syscall.Close(handle) - var ret uint16 - var data reparseData - - r1, _, err := syscall.Syscall9(procDeviceIoControl.Addr(), 8, uintptr(handle), FSCTL_GET_REPARSE_POINT, 0, 0, uintptr(unsafe.Pointer(&data)), unsafe.Sizeof(data), uintptr(unsafe.Pointer(&ret)), 0, 0) - if r1 == 0 { - return "", protocol.FlagSymlinkMissingTarget, err - } - - var flags uint32 = 0 - attr, err := syscall.GetFileAttributes(ptr) - if err != nil { - flags = protocol.FlagSymlinkMissingTarget - } else if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { - flags = protocol.FlagDirectory - } - - return osutil.NormalizedFilename(data.PrintName()), flags, nil -} - -func IsSymlink(path string) (bool, error) { - ptr, err := syscall.UTF16PtrFromString(path) - if err != nil { - return false, err - } - - attr, err := syscall.GetFileAttributes(ptr) - if err != nil { - return false, err - } - return attr&FILE_ATTRIBUTE_REPARSE_POINT != 0, nil -} - -func Create(source, target string, flags uint32) error { - srcp, err := syscall.UTF16PtrFromString(source) - 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 flags&protocol.FlagSymlinkMissingTarget != 0 { - path := target - if !filepath.IsAbs(target) { - path = filepath.Join(filepath.Dir(source), target) - } - - stat, err := os.Stat(path) - if err == nil && stat.IsDir() { - mode = SYMBOLIC_LINK_FLAG_DIRECTORY - } - } else if flags&protocol.FlagDirectory != 0 { - mode = SYMBOLIC_LINK_FLAG_DIRECTORY - } - - 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 ChangeType(path string, flags uint32) error { - target, cflags, err := Read(path) - if err != nil { - return err - } - // If it's the same type, nothing to do. - if cflags&protocol.SymlinkTypeMask == flags&protocol.SymlinkTypeMask { - return nil - } - - // If the actual type is unknown, but the new type is file, nothing to do - if cflags&protocol.FlagSymlinkMissingTarget != 0 && flags&protocol.FlagDirectory == 0 { - 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 Create(path, target, flags) - }, path) -} From 2abe792f3639f13eeae6e61b4c4320add8519298 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Thu, 4 Dec 2014 06:00:21 +0100 Subject: [PATCH 3/4] Use same order of parameters as os.Symlink --- internal/model/puller.go | 2 +- internal/symlinks/symlink.go | 4 ++-- test/symlink_test.go | 18 +++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/model/puller.go b/internal/model/puller.go index 3124940d2..1559b1a40 100644 --- a/internal/model/puller.go +++ b/internal/model/puller.go @@ -817,7 +817,7 @@ func (p *Puller) performFinish(state *sharedPullerState) { // Remove the file, and replace it with a symlink. err = osutil.InWritableDir(func(path string) error { os.Remove(path) - return symlinks.Create(path, string(content), state.file.Flags) + return symlinks.Create(string(content), path, state.file.Flags) }, state.realName) if err != nil { l.Warnln("puller: final: creating symlink:", err) diff --git a/internal/symlinks/symlink.go b/internal/symlinks/symlink.go index eb6440773..19a039de8 100644 --- a/internal/symlinks/symlink.go +++ b/internal/symlinks/symlink.go @@ -48,7 +48,7 @@ func IsSymlink(path string) (bool, error) { return lstat.Mode()&os.ModeSymlink != 0, nil } -func Create(source, target string, flags uint32) error { +func Create(target, source string, flags uint32) error { return os.Symlink(osutil.NativeFilename(target), source) } @@ -77,6 +77,6 @@ func ChangeType(path string, flags uint32) error { // It should be a symlink as well hence no need to change permissions // on the file. os.Remove(path) - return Create(path, target, flags) + return Create(target, path, flags) }, path) } diff --git a/test/symlink_test.go b/test/symlink_test.go index 8cb9e4b43..d11c8ae77 100644 --- a/test/symlink_test.go +++ b/test/symlink_test.go @@ -62,7 +62,7 @@ func TestSymlinks(t *testing.T) { t.Fatal(err) } fd.Close() - err = symlinks.Create("s1/fileLink", "file", 0) + err = symlinks.Create("file", "s1/fileLink", 0) if err != nil { log.Fatal(err) } @@ -73,35 +73,35 @@ func TestSymlinks(t *testing.T) { if err != nil { t.Fatal(err) } - err = symlinks.Create("s1/dirLink", "dir", 0) + err = symlinks.Create("dir", "s1/dirLink", 0) if err != nil { log.Fatal(err) } // A link to something in the repo that does not exist - err = symlinks.Create("s1/noneLink", "does/not/exist", 0) + err = symlinks.Create("does/not/exist", "s1/noneLink", 0) if err != nil { log.Fatal(err) } // A link we will replace with a file later - err = symlinks.Create("s1/repFileLink", "does/not/exist", 0) + err = symlinks.Create("does/not/exist", "s1/repFileLink", 0) if err != nil { log.Fatal(err) } // A link we will replace with a directory later - err = symlinks.Create("s1/repDirLink", "does/not/exist", 0) + err = symlinks.Create("does/not/exist", "s1/repDirLink", 0) if err != nil { log.Fatal(err) } // A link we will remove later - err = symlinks.Create("s1/removeLink", "does/not/exist", 0) + err = symlinks.Create("does/not/exist", "s1/removeLink", 0) if err != nil { log.Fatal(err) } @@ -184,7 +184,7 @@ func TestSymlinks(t *testing.T) { if err != nil { log.Fatal(err) } - err = symlinks.Create("s1/dirLink", "file", 0) + err = symlinks.Create("file", "s1/dirLink", 0) if err != nil { log.Fatal(err) } @@ -220,7 +220,7 @@ func TestSymlinks(t *testing.T) { if err != nil { log.Fatal(err) } - err = symlinks.Create("s1/fileToReplace", "somewhere/non/existent", 0) + err = symlinks.Create("somewhere/non/existent", "s1/fileToReplace", 0) if err != nil { log.Fatal(err) } @@ -231,7 +231,7 @@ func TestSymlinks(t *testing.T) { if err != nil { log.Fatal(err) } - err = symlinks.Create("s1/dirToReplace", "somewhere/non/existent", 0) + err = symlinks.Create("somewhere/non/existent", "s1/dirToReplace", 0) if err != nil { log.Fatal(err) } From 5af6cbae2cfc9e86e96cbb5d1695e0b932b7401e Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Thu, 4 Dec 2014 12:08:04 +0100 Subject: [PATCH 4/4] Verify Windows support, report appropriate errors when unsupported --- internal/symlinks/symlink.go | 58 +++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/internal/symlinks/symlink.go b/internal/symlinks/symlink.go index 19a039de8..30d597f84 100644 --- a/internal/symlinks/symlink.go +++ b/internal/symlinks/symlink.go @@ -16,7 +16,9 @@ package symlinks import ( + "errors" "os" + "path/filepath" "runtime" "github.com/syncthing/syncthing/internal/osutil" @@ -24,10 +26,19 @@ import ( ) var ( - Supported = true + Supported = false + ErrUnsupported = errors.New("symlinks not supported") ) +func init() { + Supported = symlinksSupported() +} + func Read(path string) (string, uint32, error) { + if !Supported { + return "", 0, ErrUnsupported + } + var mode uint32 stat, err := os.Stat(path) if err != nil { @@ -41,6 +52,10 @@ func Read(path string) (string, uint32, error) { } func IsSymlink(path string) (bool, error) { + if !Supported { + return false, ErrUnsupported + } + lstat, err := os.Lstat(path) if err != nil { return false, err @@ -49,6 +64,10 @@ func IsSymlink(path string) (bool, error) { } func Create(target, source string, flags uint32) error { + if !Supported { + return ErrUnsupported + } + return os.Symlink(osutil.NativeFilename(target), source) } @@ -58,6 +77,10 @@ func ChangeType(path string, flags uint32) error { return nil } + if !Supported { + return ErrUnsupported + } + target, cflags, err := Read(path) if err != nil { return err @@ -80,3 +103,36 @@ func ChangeType(path string, flags uint32) error { return Create(target, path, flags) }, path) } + +func symlinksSupported() bool { + if runtime.GOOS != "windows" { + // Symlinks are supported. In practice there may be deviations (FAT + // filesystems etc), but these get handled and reported as the errors + // they are when they happen. + return true + } + + // We try to create a symlink and verify that it looks like we expected. + // Needs administrator priviledges and a version higher than XP. + + base := os.TempDir() + path := filepath.Join(base, "syncthing-symlink-test") + defer os.Remove(path) + + err := Create(base, path, protocol.FlagDirectory) + if err != nil { + return false + } + + isLink, err := IsSymlink(path) + if err != nil || !isLink { + return false + } + + target, flags, err := Read(path) + if err != nil || osutil.NativeFilename(target) != base || flags&protocol.FlagDirectory == 0 { + return false + } + + return true +}