symlink_windows.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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/syncthing/lib/osutil"
  12. "syscall"
  13. "unicode/utf16"
  14. "unsafe"
  15. )
  16. const (
  17. Win32FsctlGetReparsePoint = 0x900a8
  18. Win32FileFlagOpenReparsePoint = 0x00200000
  19. Win32FileAttributeReparsePoint = 0x400
  20. Win32IOReparseTagSymlink = 0xA000000C
  21. Win32SymbolicLinkFlagDirectory = 0x1
  22. )
  23. var (
  24. modkernel32 = syscall.NewLazyDLL("kernel32.dll")
  25. procDeviceIoControl = modkernel32.NewProc("DeviceIoControl")
  26. procCreateSymbolicLink = modkernel32.NewProc("CreateSymbolicLinkW")
  27. Supported = false
  28. )
  29. func init() {
  30. defer func() {
  31. if err := recover(); err != nil {
  32. // Ensure that the supported flag is disabled when we hit an
  33. // error, even though it should already be. Also, silently swallow
  34. // the error since it's fine for a system not to support symlinks.
  35. Supported = false
  36. }
  37. }()
  38. // Needs administrator privileges.
  39. // Let's check that everything works.
  40. // This could be done more officially:
  41. // http://stackoverflow.com/questions/2094663/determine-if-windows-process-has-privilege-to-create-symbolic-link
  42. // But I don't want to define 10 more structs just to look this up.
  43. base := os.TempDir()
  44. path := filepath.Join(base, "symlinktest")
  45. defer os.Remove(path)
  46. err := Create(path, base, TargetDirectory)
  47. if err != nil {
  48. return
  49. }
  50. stat, err := osutil.Lstat(path)
  51. if err != nil || stat.Mode()&os.ModeSymlink == 0 {
  52. return
  53. }
  54. target, tt, err := Read(path)
  55. if err != nil || osutil.NativeFilename(target) != base || tt != TargetDirectory {
  56. return
  57. }
  58. Supported = true
  59. }
  60. type reparseData struct {
  61. reparseTag uint32
  62. reparseDataLength uint16
  63. reserved uint16
  64. substitueNameOffset uint16
  65. substitueNameLength uint16
  66. printNameOffset uint16
  67. printNameLength uint16
  68. flags uint32
  69. // substituteName - 264 widechars max = 528 bytes
  70. // printName - 260 widechars max = 520 bytes
  71. // = 1048 bytes total
  72. buffer [1048]uint16
  73. }
  74. func (r *reparseData) PrintName() string {
  75. // No clue why the offset and length is doubled...
  76. offset := r.printNameOffset / 2
  77. length := r.printNameLength / 2
  78. return string(utf16.Decode(r.buffer[offset : offset+length]))
  79. }
  80. func (r *reparseData) SubstituteName() string {
  81. // No clue why the offset and length is doubled...
  82. offset := r.substitueNameOffset / 2
  83. length := r.substitueNameLength / 2
  84. return string(utf16.Decode(r.buffer[offset : offset+length]))
  85. }
  86. func Read(path string) (string, TargetType, error) {
  87. ptr, err := syscall.UTF16PtrFromString(path)
  88. if err != nil {
  89. return "", TargetUnknown, err
  90. }
  91. 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)
  92. if err != nil || handle == syscall.InvalidHandle {
  93. return "", TargetUnknown, err
  94. }
  95. defer syscall.Close(handle)
  96. var ret uint16
  97. var data reparseData
  98. 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)
  99. if r1 == 0 {
  100. return "", TargetUnknown, err
  101. }
  102. tt := TargetUnknown
  103. if attr, err := syscall.GetFileAttributes(ptr); err == nil {
  104. if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
  105. tt = TargetDirectory
  106. } else {
  107. tt = TargetFile
  108. }
  109. }
  110. return osutil.NormalizedFilename(data.PrintName()), tt, nil
  111. }
  112. func Create(source, target string, tt TargetType) 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 tt == TargetUnknown {
  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 = Win32SymbolicLinkFlagDirectory
  134. }
  135. } else if tt == TargetDirectory {
  136. mode = Win32SymbolicLinkFlagDirectory
  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. }