basicfs_windows.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  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 https://mozilla.org/MPL/2.0/.
  6. // +build windows
  7. package fs
  8. import (
  9. "bytes"
  10. "errors"
  11. "os"
  12. "path/filepath"
  13. "strings"
  14. "syscall"
  15. "unsafe"
  16. )
  17. var errNotSupported = errors.New("symlinks not supported")
  18. func (BasicFilesystem) SymlinksSupported() bool {
  19. return false
  20. }
  21. func (BasicFilesystem) ReadSymlink(path string) (string, error) {
  22. return "", errNotSupported
  23. }
  24. func (BasicFilesystem) CreateSymlink(target, name string) error {
  25. return errNotSupported
  26. }
  27. // Required due to https://github.com/golang/go/issues/10900
  28. func (f *BasicFilesystem) mkdirAll(path string, perm os.FileMode) error {
  29. // Fast path: if we can tell whether path is a directory or file, stop with success or error.
  30. dir, err := os.Stat(path)
  31. if err == nil {
  32. if dir.IsDir() {
  33. return nil
  34. }
  35. return &os.PathError{
  36. Op: "mkdir",
  37. Path: path,
  38. Err: syscall.ENOTDIR,
  39. }
  40. }
  41. // Slow path: make sure parent exists and then call Mkdir for path.
  42. i := len(path)
  43. for i > 0 && IsPathSeparator(path[i-1]) { // Skip trailing path separator.
  44. i--
  45. }
  46. j := i
  47. for j > 0 && !IsPathSeparator(path[j-1]) { // Scan backward over element.
  48. j--
  49. }
  50. if j > 1 {
  51. // Create parent
  52. parent := path[0 : j-1]
  53. if parent != filepath.VolumeName(parent) {
  54. err = f.mkdirAll(parent, perm)
  55. if err != nil {
  56. return err
  57. }
  58. }
  59. }
  60. // Parent now exists; invoke Mkdir and use its result.
  61. err = os.Mkdir(path, perm)
  62. if err != nil {
  63. // Handle arguments like "foo/." by
  64. // double-checking that directory doesn't exist.
  65. dir, err1 := os.Lstat(path)
  66. if err1 == nil && dir.IsDir() {
  67. return nil
  68. }
  69. return err
  70. }
  71. return nil
  72. }
  73. func (f *BasicFilesystem) Unhide(name string) error {
  74. name, err := f.rooted(name)
  75. if err != nil {
  76. return err
  77. }
  78. p, err := syscall.UTF16PtrFromString(name)
  79. if err != nil {
  80. return err
  81. }
  82. attrs, err := syscall.GetFileAttributes(p)
  83. if err != nil {
  84. return err
  85. }
  86. attrs &^= syscall.FILE_ATTRIBUTE_HIDDEN
  87. return syscall.SetFileAttributes(p, attrs)
  88. }
  89. func (f *BasicFilesystem) Hide(name string) error {
  90. name, err := f.rooted(name)
  91. if err != nil {
  92. return err
  93. }
  94. p, err := syscall.UTF16PtrFromString(name)
  95. if err != nil {
  96. return err
  97. }
  98. attrs, err := syscall.GetFileAttributes(p)
  99. if err != nil {
  100. return err
  101. }
  102. attrs |= syscall.FILE_ATTRIBUTE_HIDDEN
  103. return syscall.SetFileAttributes(p, attrs)
  104. }
  105. func (f *BasicFilesystem) Roots() ([]string, error) {
  106. kernel32, err := syscall.LoadDLL("kernel32.dll")
  107. if err != nil {
  108. return nil, err
  109. }
  110. getLogicalDriveStringsHandle, err := kernel32.FindProc("GetLogicalDriveStringsA")
  111. if err != nil {
  112. return nil, err
  113. }
  114. buffer := [1024]byte{}
  115. bufferSize := uint32(len(buffer))
  116. hr, _, _ := getLogicalDriveStringsHandle.Call(uintptr(unsafe.Pointer(&bufferSize)), uintptr(unsafe.Pointer(&buffer)))
  117. if hr == 0 {
  118. return nil, errors.New("syscall failed")
  119. }
  120. var drives []string
  121. parts := bytes.Split(buffer[:], []byte{0})
  122. for _, part := range parts {
  123. if len(part) == 0 {
  124. break
  125. }
  126. drives = append(drives, string(part))
  127. }
  128. return drives, nil
  129. }
  130. // unrootedChecked returns the path relative to the folder root (same as
  131. // unrooted) or an error if the given path is not a subpath and handles the
  132. // special case when the given path is the folder root without a trailing
  133. // pathseparator.
  134. func (f *BasicFilesystem) unrootedChecked(absPath string, roots []string) (string, error) {
  135. absPath = f.resolveWin83(absPath)
  136. lowerAbsPath := UnicodeLowercaseNormalized(absPath)
  137. for _, root := range roots {
  138. lowerRoot := UnicodeLowercaseNormalized(root)
  139. if lowerAbsPath+string(PathSeparator) == lowerRoot {
  140. return ".", nil
  141. }
  142. if strings.HasPrefix(lowerAbsPath, lowerRoot) {
  143. return rel(absPath, root), nil
  144. }
  145. }
  146. return "", f.newErrWatchEventOutsideRoot(lowerAbsPath, roots)
  147. }
  148. func rel(path, prefix string) string {
  149. lowerRel := strings.TrimPrefix(strings.TrimPrefix(UnicodeLowercaseNormalized(path), UnicodeLowercaseNormalized(prefix)), string(PathSeparator))
  150. return path[len(path)-len(lowerRel):]
  151. }
  152. func (f *BasicFilesystem) resolveWin83(absPath string) string {
  153. if !isMaybeWin83(absPath) {
  154. return absPath
  155. }
  156. if in, err := syscall.UTF16FromString(absPath); err == nil {
  157. out := make([]uint16, 4*len(absPath)) // *2 for UTF16 and *2 to double path length
  158. if n, err := syscall.GetLongPathName(&in[0], &out[0], uint32(len(out))); err == nil {
  159. if n <= uint32(len(out)) {
  160. return syscall.UTF16ToString(out[:n])
  161. }
  162. out = make([]uint16, n)
  163. if _, err = syscall.GetLongPathName(&in[0], &out[0], n); err == nil {
  164. return syscall.UTF16ToString(out)
  165. }
  166. }
  167. }
  168. // Failed getting the long path. Return the part of the path which is
  169. // already a long path.
  170. lowerRoot := UnicodeLowercaseNormalized(f.root)
  171. for absPath = filepath.Dir(absPath); strings.HasPrefix(UnicodeLowercaseNormalized(absPath), lowerRoot); absPath = filepath.Dir(absPath) {
  172. if !isMaybeWin83(absPath) {
  173. return absPath
  174. }
  175. }
  176. return f.root
  177. }
  178. func isMaybeWin83(absPath string) bool {
  179. if !strings.Contains(absPath, "~") {
  180. return false
  181. }
  182. if strings.Contains(filepath.Dir(absPath), "~") {
  183. return true
  184. }
  185. return strings.Contains(strings.TrimPrefix(filepath.Base(absPath), WindowsTempPrefix), "~")
  186. }
  187. func getFinalPathName(in string) (string, error) {
  188. // Return the normalized path
  189. // Wrap the call to GetFinalPathNameByHandleW
  190. // The string returned by this function uses the \?\ syntax
  191. // Implies GetFullPathName + GetLongPathName
  192. kernel32, err := syscall.LoadDLL("kernel32.dll")
  193. if err != nil {
  194. return "", err
  195. }
  196. GetFinalPathNameByHandleW, err := kernel32.FindProc("GetFinalPathNameByHandleW")
  197. // https://github.com/golang/go/blob/ff048033e4304898245d843e79ed1a0897006c6d/src/internal/syscall/windows/syscall_windows.go#L303
  198. if err != nil {
  199. return "", err
  200. }
  201. inPath, err := syscall.UTF16PtrFromString(in)
  202. if err != nil {
  203. return "", err
  204. }
  205. // Get a file handler
  206. h, err := syscall.CreateFile(inPath,
  207. syscall.GENERIC_READ,
  208. syscall.FILE_SHARE_READ,
  209. nil,
  210. syscall.OPEN_EXISTING,
  211. uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS),
  212. 0)
  213. if err != nil {
  214. return "", err
  215. }
  216. defer syscall.CloseHandle(h)
  217. // Call GetFinalPathNameByHandleW
  218. var VOLUME_NAME_DOS uint32 = 0x0 // not yet defined in syscall
  219. var bufSize uint32 = syscall.MAX_PATH // 260
  220. for i := 0; i < 2; i++ {
  221. buf := make([]uint16, bufSize)
  222. var ret uintptr
  223. ret, _, err = GetFinalPathNameByHandleW.Call(
  224. uintptr(h), // HANDLE hFile
  225. uintptr(unsafe.Pointer(&buf[0])), // LPWSTR lpszFilePath
  226. uintptr(bufSize), // DWORD cchFilePath
  227. uintptr(VOLUME_NAME_DOS), // DWORD dwFlags
  228. )
  229. // The returned value is the actual length of the norm path
  230. // After Win 10 build 1607, MAX_PATH limitations have been removed
  231. // so it is necessary to check newBufSize
  232. newBufSize := uint32(ret) + 1
  233. if ret == 0 || newBufSize > bufSize*100 {
  234. break
  235. }
  236. if newBufSize <= bufSize {
  237. return syscall.UTF16ToString(buf), nil
  238. }
  239. bufSize = newBufSize
  240. }
  241. return "", err
  242. }
  243. func evalSymlinks(in string) (string, error) {
  244. out, err := filepath.EvalSymlinks(in)
  245. if err != nil && strings.HasPrefix(in, `\\?\`) {
  246. // Try again without the `\\?\` prefix
  247. out, err = filepath.EvalSymlinks(in[4:])
  248. }
  249. if err != nil {
  250. // Try to get a normalized path from Win-API
  251. var err1 error
  252. out, err1 = getFinalPathName(in)
  253. if err1 != nil {
  254. return "", err // return the prior error
  255. }
  256. // Trim UNC prefix, equivalent to
  257. // https://github.com/golang/go/blob/2396101e0590cb7d77556924249c26af0ccd9eff/src/os/file_windows.go#L470
  258. if strings.HasPrefix(out, `\\?\UNC\`) {
  259. out = `\` + out[7:] // path like \\server\share\...
  260. } else {
  261. out = strings.TrimPrefix(out, `\\?\`)
  262. }
  263. }
  264. return longFilenameSupport(out), nil
  265. }
  266. // watchPaths adjust the folder root for use with the notify backend and the
  267. // corresponding absolute path to be passed to notify to watch name.
  268. func (f *BasicFilesystem) watchPaths(name string) (string, []string, error) {
  269. root, err := evalSymlinks(f.root)
  270. if err != nil {
  271. return "", nil, err
  272. }
  273. // Remove `\\?\` prefix if the path is just a drive letter as a dirty
  274. // fix for https://github.com/syncthing/syncthing/issues/5578
  275. if filepath.Clean(name) == "." && len(root) <= 7 && len(root) > 4 && root[:4] == `\\?\` {
  276. root = root[4:]
  277. }
  278. absName, err := rooted(name, root)
  279. if err != nil {
  280. return "", nil, err
  281. }
  282. roots := []string{f.resolveWin83(root)}
  283. absName = f.resolveWin83(absName)
  284. // Events returned from fs watching are all over the place, so allow
  285. // both the user's input and the result of "canonicalizing" the path.
  286. if roots[0] != f.root {
  287. roots = append(roots, f.root)
  288. }
  289. return filepath.Join(absName, "..."), roots, nil
  290. }