basicfs_windows.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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. "fmt"
  12. "os"
  13. "path/filepath"
  14. "strings"
  15. "syscall"
  16. "unsafe"
  17. )
  18. var errNotSupported = errors.New("symlinks not supported")
  19. func (BasicFilesystem) SymlinksSupported() bool {
  20. return false
  21. }
  22. func (BasicFilesystem) ReadSymlink(path string) (string, error) {
  23. return "", errNotSupported
  24. }
  25. func (BasicFilesystem) CreateSymlink(target, name string) error {
  26. return errNotSupported
  27. }
  28. // Required due to https://github.com/golang/go/issues/10900
  29. func (f *BasicFilesystem) mkdirAll(path string, perm os.FileMode) error {
  30. // Fast path: if we can tell whether path is a directory or file, stop with success or error.
  31. dir, err := os.Stat(path)
  32. if err == nil {
  33. if dir.IsDir() {
  34. return nil
  35. }
  36. return &os.PathError{
  37. Op: "mkdir",
  38. Path: path,
  39. Err: syscall.ENOTDIR,
  40. }
  41. }
  42. // Slow path: make sure parent exists and then call Mkdir for path.
  43. i := len(path)
  44. for i > 0 && IsPathSeparator(path[i-1]) { // Skip trailing path separator.
  45. i--
  46. }
  47. j := i
  48. for j > 0 && !IsPathSeparator(path[j-1]) { // Scan backward over element.
  49. j--
  50. }
  51. if j > 1 {
  52. // Create parent
  53. parent := path[0 : j-1]
  54. if parent != filepath.VolumeName(parent) {
  55. err = f.mkdirAll(parent, perm)
  56. if err != nil {
  57. return err
  58. }
  59. }
  60. }
  61. // Parent now exists; invoke Mkdir and use its result.
  62. err = os.Mkdir(path, perm)
  63. if err != nil {
  64. // Handle arguments like "foo/." by
  65. // double-checking that directory doesn't exist.
  66. dir, err1 := os.Lstat(path)
  67. if err1 == nil && dir.IsDir() {
  68. return nil
  69. }
  70. return err
  71. }
  72. return nil
  73. }
  74. func (f *BasicFilesystem) Unhide(name string) error {
  75. name, err := f.rooted(name)
  76. if err != nil {
  77. return err
  78. }
  79. p, err := syscall.UTF16PtrFromString(name)
  80. if err != nil {
  81. return err
  82. }
  83. attrs, err := syscall.GetFileAttributes(p)
  84. if err != nil {
  85. return err
  86. }
  87. attrs &^= syscall.FILE_ATTRIBUTE_HIDDEN
  88. return syscall.SetFileAttributes(p, attrs)
  89. }
  90. func (f *BasicFilesystem) Hide(name string) error {
  91. name, err := f.rooted(name)
  92. if err != nil {
  93. return err
  94. }
  95. p, err := syscall.UTF16PtrFromString(name)
  96. if err != nil {
  97. return err
  98. }
  99. attrs, err := syscall.GetFileAttributes(p)
  100. if err != nil {
  101. return err
  102. }
  103. attrs |= syscall.FILE_ATTRIBUTE_HIDDEN
  104. return syscall.SetFileAttributes(p, attrs)
  105. }
  106. func (f *BasicFilesystem) Roots() ([]string, error) {
  107. kernel32, err := syscall.LoadDLL("kernel32.dll")
  108. if err != nil {
  109. return nil, err
  110. }
  111. getLogicalDriveStringsHandle, err := kernel32.FindProc("GetLogicalDriveStringsA")
  112. if err != nil {
  113. return nil, err
  114. }
  115. buffer := [1024]byte{}
  116. bufferSize := uint32(len(buffer))
  117. hr, _, _ := getLogicalDriveStringsHandle.Call(uintptr(unsafe.Pointer(&bufferSize)), uintptr(unsafe.Pointer(&buffer)))
  118. if hr == 0 {
  119. return nil, fmt.Errorf("Syscall failed")
  120. }
  121. var drives []string
  122. parts := bytes.Split(buffer[:], []byte{0})
  123. for _, part := range parts {
  124. if len(part) == 0 {
  125. break
  126. }
  127. drives = append(drives, string(part))
  128. }
  129. return drives, nil
  130. }
  131. // unrootedChecked returns the path relative to the folder root (same as
  132. // unrooted). It panics if the given path is not a subpath and handles the
  133. // special case when the given path is the folder root without a trailing
  134. // pathseparator.
  135. func (f *BasicFilesystem) unrootedChecked(absPath, root string) string {
  136. absPath = f.resolveWin83(absPath)
  137. lowerAbsPath := UnicodeLowercase(absPath)
  138. lowerRoot := UnicodeLowercase(root)
  139. if lowerAbsPath+string(PathSeparator) == lowerRoot {
  140. return "."
  141. }
  142. if !strings.HasPrefix(lowerAbsPath, lowerRoot) {
  143. panic(fmt.Sprintf("bug: Notify backend is processing a change outside of the filesystem root: f.root==%v, root==%v (lower), path==%v (lower)", f.root, lowerRoot, lowerAbsPath))
  144. }
  145. return rel(absPath, root)
  146. }
  147. func rel(path, prefix string) string {
  148. lowerRel := strings.TrimPrefix(strings.TrimPrefix(UnicodeLowercase(path), UnicodeLowercase(prefix)), string(PathSeparator))
  149. return path[len(path)-len(lowerRel):]
  150. }
  151. func (f *BasicFilesystem) resolveWin83(absPath string) string {
  152. if !isMaybeWin83(absPath) {
  153. return absPath
  154. }
  155. if in, err := syscall.UTF16FromString(absPath); err == nil {
  156. out := make([]uint16, 4*len(absPath)) // *2 for UTF16 and *2 to double path length
  157. if n, err := syscall.GetLongPathName(&in[0], &out[0], uint32(len(out))); err == nil {
  158. if n <= uint32(len(out)) {
  159. return syscall.UTF16ToString(out[:n])
  160. }
  161. out = make([]uint16, n)
  162. if _, err = syscall.GetLongPathName(&in[0], &out[0], n); err == nil {
  163. return syscall.UTF16ToString(out)
  164. }
  165. }
  166. }
  167. // Failed getting the long path. Return the part of the path which is
  168. // already a long path.
  169. lowerRoot := UnicodeLowercase(f.root)
  170. for absPath = filepath.Dir(absPath); strings.HasPrefix(UnicodeLowercase(absPath), lowerRoot); absPath = filepath.Dir(absPath) {
  171. if !isMaybeWin83(absPath) {
  172. return absPath
  173. }
  174. }
  175. return f.root
  176. }
  177. func isMaybeWin83(absPath string) bool {
  178. if !strings.Contains(absPath, "~") {
  179. return false
  180. }
  181. if strings.Contains(filepath.Dir(absPath), "~") {
  182. return true
  183. }
  184. return strings.Contains(strings.TrimPrefix(filepath.Base(absPath), WindowsTempPrefix), "~")
  185. }