github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/filesystem/metadata_windows.go (about) 1 package filesystem 2 3 import ( 4 "errors" 5 "fmt" 6 "time" 7 "unsafe" 8 9 "golang.org/x/sys/windows" 10 11 "github.com/mutagen-io/mutagen/pkg/filesystem/internal/syscall" 12 ) 13 14 // queryHandleMetadata performs a metadata query using a Windows file handle. It 15 // must be passed the base name of the path used to open the handle. It supports 16 // files, directories, and symbolic links. It behavior designed to match that of 17 // the os.File.Stat method on Windows, specifically the unexported 18 // newFileStatFromGetFileInformationByHandle function and the various methods of 19 // os.fileStat. The behavior of this function, in particular its classification 20 // of file types in modes, must match that of the Go standard library in order 21 // for this package to function correctly (because the Windows implementation of 22 // this package partially relies on the standard os package and certain 23 // invariants will break if this classification behavior differs). 24 func queryHandleMetadata(name string, handle windows.Handle) (*Metadata, error) { 25 // Query the file type to ensure that it's an on-disk type (i.e. a file, 26 // directory, or symbolic link). 27 if t, err := windows.GetFileType(handle); err != nil { 28 return nil, fmt.Errorf("unable to determine file type: %w", err) 29 } else if t != windows.FILE_TYPE_DISK { 30 return nil, errors.New("handle does not refer to on-disk type") 31 } 32 33 // Perform a general file metadata query. 34 var metadata windows.ByHandleFileInformation 35 if err := windows.GetFileInformationByHandle(handle, &metadata); err != nil { 36 return nil, fmt.Errorf("unable to query file metadata: %w", err) 37 } 38 39 // If the handle refers to a reparse point, then determine whether or not 40 // it's a symbolic link. When dealing with reparse points, we need to 41 // perform an attribute query. Unfortunately this query isn't supported on 42 // some (or perhaps any) non-NTFS filesystems, so we can't use it as our 43 // only query (even though it returns the same FileAttributes field as the 44 // general query above). In cases where this query returns an invalid 45 // parameter error, we assume that we're on a non-NTFS filesystem, in which 46 // case symbolic links aren't supported in any case. See golang/go#29214 for 47 // more information. 48 var symbolicLink bool 49 if metadata.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 { 50 var attributes syscall.FileAttributeTagInfo 51 if err := windows.GetFileInformationByHandleEx( 52 handle, 53 windows.FileAttributeTagInfo, 54 (*byte)(unsafe.Pointer(&attributes)), 55 uint32(unsafe.Sizeof(attributes)), 56 ); err != nil { 57 if err != windows.ERROR_INVALID_PARAMETER { 58 return nil, fmt.Errorf("unable to query reparse point attributes: %w", err) 59 } 60 } else { 61 // Determine whether or not we're dealing with a symbolic link. This 62 // logic follows that in os.fileStat.isSymlink. 63 // 64 // TODO: Update this definition once golang/go#42184 is resolved. 65 symbolicLink = attributes.ReparseTag == windows.IO_REPARSE_TAG_SYMLINK || 66 attributes.ReparseTag == windows.IO_REPARSE_TAG_MOUNT_POINT 67 } 68 } 69 70 // Compute the mode. Note that the logic here needs to match that of 71 // os.fileStat.Mode. 72 mode := Mode(0666) 73 if metadata.FileAttributes&windows.FILE_ATTRIBUTE_READONLY != 0 { 74 mode = Mode(0444) 75 } 76 if symbolicLink { 77 mode |= ModeTypeSymbolicLink 78 } else if metadata.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY != 0 { 79 mode |= ModeTypeDirectory | 0111 80 } 81 82 // Compute the size. 83 size := uint64(metadata.FileSizeHigh)<<32 + uint64(metadata.FileSizeLow) 84 85 // Compute the modification time. 86 modificationTime := time.Unix(0, metadata.LastWriteTime.Nanoseconds()) 87 88 // Success. 89 return &Metadata{ 90 Name: name, 91 Mode: mode, 92 Size: size, 93 ModificationTime: modificationTime, 94 }, nil 95 }