basicfs_lstat_windows.go 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. // Copyright (C) 2015 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. //go:build windows
  7. // +build windows
  8. package fs
  9. import (
  10. "fmt"
  11. "os"
  12. "syscall"
  13. "unsafe"
  14. "golang.org/x/sys/windows"
  15. )
  16. func isDirectoryJunction(path string) (bool, error) {
  17. namep, err := syscall.UTF16PtrFromString(path)
  18. if err != nil {
  19. return false, fmt.Errorf("syscall.UTF16PtrFromString failed with: %s", err)
  20. }
  21. attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
  22. h, err := syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
  23. if err != nil {
  24. return false, fmt.Errorf("syscall.CreateFile failed with: %s", err)
  25. }
  26. defer syscall.CloseHandle(h)
  27. //https://docs.microsoft.com/windows/win32/api/winbase/ns-winbase-file_attribute_tag_info
  28. const fileAttributeTagInfo = 9
  29. type FILE_ATTRIBUTE_TAG_INFO struct {
  30. FileAttributes uint32
  31. ReparseTag uint32
  32. }
  33. var ti FILE_ATTRIBUTE_TAG_INFO
  34. err = windows.GetFileInformationByHandleEx(windows.Handle(h), fileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti)))
  35. if err != nil {
  36. if errno, ok := err.(syscall.Errno); ok && errno == windows.ERROR_INVALID_PARAMETER {
  37. // It appears calling GetFileInformationByHandleEx with
  38. // FILE_ATTRIBUTE_TAG_INFO fails on FAT file system with
  39. // ERROR_INVALID_PARAMETER. Clear ti.ReparseTag in that
  40. // instance to indicate no symlinks are possible.
  41. ti.ReparseTag = 0
  42. } else {
  43. return false, fmt.Errorf("windows.GetFileInformationByHandleEx failed with: %s", err)
  44. }
  45. }
  46. return ti.ReparseTag == windows.IO_REPARSE_TAG_MOUNT_POINT, nil
  47. }
  48. type dirJunctFileInfo struct {
  49. os.FileInfo
  50. }
  51. func (fi *dirJunctFileInfo) Mode() os.FileMode {
  52. // Simulate a directory and not a symlink; also set the execute
  53. // bits so the directory can be traversed Unix-side.
  54. return fi.FileInfo.Mode() ^ os.ModeSymlink | os.ModeDir | 0111
  55. }
  56. func (fi *dirJunctFileInfo) IsDir() bool {
  57. return true
  58. }
  59. func (f *BasicFilesystem) underlyingLstat(name string) (os.FileInfo, error) {
  60. var fi, err = os.Lstat(name)
  61. // NTFS directory junctions can be treated as ordinary directories,
  62. // see https://forum.syncthing.net/t/option-to-follow-directory-junctions-symbolic-links/14750
  63. if err == nil && f.junctionsAsDirs && fi.Mode()&os.ModeSymlink != 0 {
  64. var isJunct bool
  65. isJunct, err = isDirectoryJunction(name)
  66. if err == nil && isJunct {
  67. return &dirJunctFileInfo{fi}, nil
  68. }
  69. }
  70. return fi, err
  71. }