symlink_windows.go 5.5 KB

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