mirror of
https://github.com/syncthing/syncthing.git
synced 2024-11-16 18:41:59 -07:00
fa0101bd60
This changes the BEP protocol to use protocol buffer serialization instead of XDR, and therefore also the database format. The local discovery protocol is also updated to be protocol buffer format. GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3276 LGTM: AudriusButkevicius
191 lines
5.2 KiB
Go
191 lines
5.2 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/.
|
|
|
|
// +build windows
|
|
|
|
package symlinks
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/syncthing/syncthing/lib/osutil"
|
|
|
|
"syscall"
|
|
"unicode/utf16"
|
|
"unsafe"
|
|
)
|
|
|
|
const (
|
|
Win32FsctlGetReparsePoint = 0x900a8
|
|
Win32FileFlagOpenReparsePoint = 0x00200000
|
|
Win32FileAttributeReparsePoint = 0x400
|
|
Win32IOReparseTagSymlink = 0xA000000C
|
|
Win32SymbolicLinkFlagDirectory = 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 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 := Create(path, base, TargetDirectory)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
stat, err := osutil.Lstat(path)
|
|
if err != nil || stat.Mode()&os.ModeSymlink == 0 {
|
|
return
|
|
}
|
|
|
|
target, tt, err := Read(path)
|
|
if err != nil || osutil.NativeFilename(target) != base || tt != TargetDirectory {
|
|
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, TargetType, error) {
|
|
ptr, err := syscall.UTF16PtrFromString(path)
|
|
if err != nil {
|
|
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)
|
|
if err != nil || handle == syscall.InvalidHandle {
|
|
return "", TargetUnknown, 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 "", TargetUnknown, err
|
|
}
|
|
|
|
tt := TargetUnknown
|
|
if attr, err := syscall.GetFileAttributes(ptr); err == nil {
|
|
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
|
tt = TargetDirectory
|
|
} else {
|
|
tt = TargetFile
|
|
}
|
|
}
|
|
|
|
return osutil.NormalizedFilename(data.PrintName()), tt, nil
|
|
}
|
|
|
|
func Create(source, target string, tt TargetType) 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 tt == TargetUnknown {
|
|
path := target
|
|
if !filepath.IsAbs(target) {
|
|
path = filepath.Join(filepath.Dir(source), target)
|
|
}
|
|
|
|
stat, err := os.Stat(path)
|
|
if err == nil && stat.IsDir() {
|
|
mode = Win32SymbolicLinkFlagDirectory
|
|
}
|
|
} else if tt == TargetDirectory {
|
|
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 ChangeType(path string, tt TargetType) error {
|
|
target, exTt, err := Read(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// If it's the same type, nothing to do.
|
|
if tt == exTt {
|
|
return nil
|
|
}
|
|
|
|
// If the actual type is unknown, but the new type is file, nothing to do
|
|
if exTt == TargetUnknown && tt != TargetDirectory {
|
|
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, tt)
|
|
}, path)
|
|
}
|