basicfs_lstat_windows.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  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. const IO_REPARSE_TAG_DEDUP = 0x80000013
  17. func readReparseTag(path string) (uint32, error) {
  18. namep, err := syscall.UTF16PtrFromString(path)
  19. if err != nil {
  20. return 0, fmt.Errorf("syscall.UTF16PtrFromString failed with: %s", err)
  21. }
  22. attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
  23. h, err := syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
  24. if err != nil {
  25. return 0, fmt.Errorf("syscall.CreateFile failed with: %s", err)
  26. }
  27. defer syscall.CloseHandle(h)
  28. //https://docs.microsoft.com/windows/win32/api/winbase/ns-winbase-file_attribute_tag_info
  29. const fileAttributeTagInfo = 9
  30. type FILE_ATTRIBUTE_TAG_INFO struct {
  31. FileAttributes uint32
  32. ReparseTag uint32
  33. }
  34. var ti FILE_ATTRIBUTE_TAG_INFO
  35. err = windows.GetFileInformationByHandleEx(windows.Handle(h), fileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti)))
  36. if err != nil {
  37. if errno, ok := err.(syscall.Errno); ok && errno == windows.ERROR_INVALID_PARAMETER {
  38. // It appears calling GetFileInformationByHandleEx with
  39. // FILE_ATTRIBUTE_TAG_INFO fails on FAT file system with
  40. // ERROR_INVALID_PARAMETER. Clear ti.ReparseTag in that
  41. // instance to indicate no symlinks are possible.
  42. ti.ReparseTag = 0
  43. } else {
  44. return 0, fmt.Errorf("windows.GetFileInformationByHandleEx failed with: %s", err)
  45. }
  46. }
  47. return ti.ReparseTag, nil
  48. }
  49. func isDirectoryJunction(reparseTag uint32) bool {
  50. return reparseTag == windows.IO_REPARSE_TAG_MOUNT_POINT
  51. }
  52. func isDeduplicatedFile(reparseTag uint32) bool {
  53. return reparseTag == IO_REPARSE_TAG_DEDUP
  54. }
  55. type dirJunctFileInfo struct {
  56. os.FileInfo
  57. }
  58. func (fi *dirJunctFileInfo) Mode() os.FileMode {
  59. // Simulate a directory and not a symlink; also set the execute
  60. // bits so the directory can be traversed Unix-side.
  61. return fi.FileInfo.Mode() ^ os.ModeSymlink | os.ModeDir | 0111
  62. }
  63. func (fi *dirJunctFileInfo) IsDir() bool {
  64. return true
  65. }
  66. type dedupFileInfo struct {
  67. os.FileInfo
  68. }
  69. func (fi *dedupFileInfo) Mode() os.FileMode {
  70. // A deduplicated file should be treated as a regular file and not an
  71. // irregular file.
  72. return fi.FileInfo.Mode() &^ os.ModeIrregular
  73. }
  74. func (f *BasicFilesystem) underlyingLstat(name string) (os.FileInfo, error) {
  75. var fi, err = os.Lstat(name)
  76. // There are cases where files are tagged as symlink, but they end up being
  77. // something else. Make sure we properly handle those types.
  78. if err == nil {
  79. // NTFS directory junctions can be treated as ordinary directories,
  80. // see https://forum.syncthing.net/t/option-to-follow-directory-junctions-symbolic-links/14750
  81. if fi.Mode()&os.ModeSymlink != 0 && f.junctionsAsDirs {
  82. if reparseTag, reparseErr := readReparseTag(name); reparseErr == nil && isDirectoryJunction(reparseTag) {
  83. return &dirJunctFileInfo{fi}, nil
  84. }
  85. }
  86. // Workaround for #9120 till golang properly handles deduplicated files by
  87. // considering them regular files.
  88. if fi.Mode()&os.ModeIrregular != 0 {
  89. if reparseTag, reparseErr := readReparseTag(name); reparseErr == nil && isDeduplicatedFile(reparseTag) {
  90. return &dedupFileInfo{fi}, nil
  91. }
  92. }
  93. }
  94. return fi, err
  95. }