basicfs_symlink_windows.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at http://mozilla.org/MPL/2.0/.
  6. // +build windows
  7. package fs
  8. import (
  9. "os"
  10. "path/filepath"
  11. "github.com/syncthing/syncthing/lib/osutil"
  12. "syscall"
  13. "unicode/utf16"
  14. "unsafe"
  15. )
  16. const (
  17. win32FsctlGetReparsePoint = 0x900a8
  18. win32FileFlagOpenReparsePoint = 0x00200000
  19. win32SymbolicLinkFlagDirectory = 0x1
  20. )
  21. var (
  22. modkernel32 = syscall.NewLazyDLL("kernel32.dll")
  23. procDeviceIoControl = modkernel32.NewProc("DeviceIoControl")
  24. procCreateSymbolicLink = modkernel32.NewProc("CreateSymbolicLinkW")
  25. symlinksSupported = false
  26. )
  27. func init() {
  28. defer func() {
  29. if err := recover(); err != nil {
  30. // Ensure that the supported flag is disabled when we hit an
  31. // error, even though it should already be. Also, silently swallow
  32. // the error since it's fine for a system not to support symlinks.
  33. symlinksSupported = false
  34. }
  35. }()
  36. // Needs administrator privileges.
  37. // Let's check that everything works.
  38. // This could be done more officially:
  39. // http://stackoverflow.com/questions/2094663/determine-if-windows-process-has-privilege-to-create-symbolic-link
  40. // But I don't want to define 10 more structs just to look this up.
  41. base := os.TempDir()
  42. path := filepath.Join(base, "symlinktest")
  43. defer os.Remove(path)
  44. err := DefaultFilesystem.CreateSymlink(path, base, LinkTargetDirectory)
  45. if err != nil {
  46. return
  47. }
  48. stat, err := osutil.Lstat(path)
  49. if err != nil || stat.Mode()&os.ModeSymlink == 0 {
  50. return
  51. }
  52. target, tt, err := DefaultFilesystem.ReadSymlink(path)
  53. if err != nil || osutil.NativeFilename(target) != base || tt != LinkTargetDirectory {
  54. return
  55. }
  56. symlinksSupported = true
  57. }
  58. func DisableSymlinks() {
  59. symlinksSupported = false
  60. }
  61. func (BasicFilesystem) SymlinksSupported() bool {
  62. return symlinksSupported
  63. }
  64. func (BasicFilesystem) ReadSymlink(path string) (string, LinkTargetType, error) {
  65. ptr, err := syscall.UTF16PtrFromString(path)
  66. if err != nil {
  67. return "", LinkTargetUnknown, err
  68. }
  69. 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)
  70. if err != nil || handle == syscall.InvalidHandle {
  71. return "", LinkTargetUnknown, err
  72. }
  73. defer syscall.Close(handle)
  74. var ret uint16
  75. var data reparseData
  76. 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)
  77. if r1 == 0 {
  78. return "", LinkTargetUnknown, err
  79. }
  80. tt := LinkTargetUnknown
  81. if attr, err := syscall.GetFileAttributes(ptr); err == nil {
  82. if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
  83. tt = LinkTargetDirectory
  84. } else {
  85. tt = LinkTargetFile
  86. }
  87. }
  88. return osutil.NormalizedFilename(data.printName()), tt, nil
  89. }
  90. func (BasicFilesystem) CreateSymlink(path, target string, tt LinkTargetType) error {
  91. srcp, err := syscall.UTF16PtrFromString(path)
  92. if err != nil {
  93. return err
  94. }
  95. trgp, err := syscall.UTF16PtrFromString(osutil.NativeFilename(target))
  96. if err != nil {
  97. return err
  98. }
  99. // Sadly for Windows we need to specify the type of the symlink,
  100. // whether it's a directory symlink or a file symlink.
  101. // If the flags doesn't reveal the target type, try to evaluate it
  102. // ourselves, and worst case default to the symlink pointing to a file.
  103. mode := 0
  104. if tt == LinkTargetUnknown {
  105. path := target
  106. if !filepath.IsAbs(target) {
  107. path = filepath.Join(filepath.Dir(path), target)
  108. }
  109. stat, err := os.Stat(path)
  110. if err == nil && stat.IsDir() {
  111. mode = win32SymbolicLinkFlagDirectory
  112. }
  113. } else if tt == LinkTargetDirectory {
  114. mode = win32SymbolicLinkFlagDirectory
  115. }
  116. r0, _, err := syscall.Syscall(procCreateSymbolicLink.Addr(), 3, uintptr(unsafe.Pointer(srcp)), uintptr(unsafe.Pointer(trgp)), uintptr(mode))
  117. if r0 == 1 {
  118. return nil
  119. }
  120. return err
  121. }
  122. func (fs BasicFilesystem) ChangeSymlinkType(path string, tt LinkTargetType) error {
  123. target, existingTargetType, err := fs.ReadSymlink(path)
  124. if err != nil {
  125. return err
  126. }
  127. // If it's the same type, nothing to do.
  128. if tt == existingTargetType {
  129. return nil
  130. }
  131. // If the actual type is unknown, but the new type is file, nothing to do
  132. if existingTargetType == LinkTargetUnknown && tt != LinkTargetDirectory {
  133. return nil
  134. }
  135. return osutil.InWritableDir(func(path string) error {
  136. // It should be a symlink as well hence no need to change permissions on
  137. // the file.
  138. os.Remove(path)
  139. return fs.CreateSymlink(path, target, tt)
  140. }, path)
  141. }
  142. type reparseData struct {
  143. reparseTag uint32
  144. reparseDataLength uint16
  145. reserved uint16
  146. substitueNameOffset uint16
  147. substitueNameLength uint16
  148. printNameOffset uint16
  149. printNameLength uint16
  150. flags uint32
  151. // substituteName - 264 widechars max = 528 bytes
  152. // printName - 260 widechars max = 520 bytes
  153. // = 1048 bytes total
  154. buffer [1048 / 2]uint16
  155. }
  156. func (r *reparseData) printName() string {
  157. // offset and length are in bytes but we're indexing a []uint16
  158. offset := r.printNameOffset / 2
  159. length := r.printNameLength / 2
  160. return string(utf16.Decode(r.buffer[offset : offset+length]))
  161. }
  162. func (r *reparseData) substituteName() string {
  163. // offset and length are in bytes but we're indexing a []uint16
  164. offset := r.substitueNameOffset / 2
  165. length := r.substitueNameLength / 2
  166. return string(utf16.Decode(r.buffer[offset : offset+length]))
  167. }