github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/filesystem/open_windows.go (about) 1 package filesystem 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 10 "golang.org/x/sys/windows" 11 12 osvendor "github.com/mutagen-io/mutagen/pkg/filesystem/internal/third_party/os" 13 ) 14 15 // Open opens a filesystem path for traversal and/or other operations. It will 16 // return either a Directory or an io.ReadSeekCloser object (as an io.Closer for 17 // convenient closing access without casting), along with Metadata that can be 18 // used to determine the type of object being returned. Unless requested, this 19 // function does not allow the leaf component of path to be a symbolic link 20 // (though intermediate components of the path can be symbolic links and will be 21 // resolved in the resolution of the path), and an error will be returned if 22 // this is the case. However, if allowSymbolicLinkLeaf is true, then this 23 // function will allow resolution of a path leaf component that's a symbolic 24 // link. In this case, the referenced object must still be a directory or 25 // regular file, and the returned object will still be either a Directory or an 26 // io.ReadSeekCloser. 27 func Open(path string, allowSymbolicLinkLeaf bool) (io.Closer, *Metadata, error) { 28 // Verify that the provided path is absolute. This is a requirement on 29 // Windows, where all of our operations are path-based. 30 if !filepath.IsAbs(path) { 31 return nil, nil, errors.New("path is not absolute") 32 } 33 34 // Fix long paths. 35 path = osvendor.FixLongPath(path) 36 37 // Convert the path to UTF-16. 38 path16, err := windows.UTF16PtrFromString(path) 39 if err != nil { 40 return nil, nil, fmt.Errorf("unable to convert path to UTF-16: %w", err) 41 } 42 43 // Open the path in a manner that is suitable for reading, doesn't allow for 44 // other threads or processes to delete or rename the file while open, 45 // avoids symbolic link traversal (at the path leaf), and has suitable 46 // semantics for both files and directories. 47 flags := uint32(windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_BACKUP_SEMANTICS) 48 if !allowSymbolicLinkLeaf { 49 flags |= windows.FILE_FLAG_OPEN_REPARSE_POINT 50 } 51 handle, err := windows.CreateFile( 52 path16, 53 windows.GENERIC_READ, 54 windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE, 55 nil, 56 windows.OPEN_EXISTING, 57 flags, 58 0, 59 ) 60 if err != nil { 61 if os.IsNotExist(err) { 62 return nil, nil, err 63 } 64 return nil, nil, fmt.Errorf("unable to open path: %w", err) 65 } 66 67 // Query handle metadata. 68 metadata, err := queryHandleMetadata(filepath.Base(path), handle) 69 if err != nil { 70 windows.CloseHandle(handle) 71 return nil, nil, fmt.Errorf("unable to query file handle metadata: %w", err) 72 } 73 74 // Verify that we're not dealing with a symbolic link. If we are allowing 75 // symbolic links, then they should have been resolved by CreateFile. 76 if metadata.Mode&ModeTypeSymbolicLink != 0 { 77 windows.CloseHandle(handle) 78 return nil, nil, ErrUnsupportedOpenType 79 } 80 81 // Handle os.File creation based on type. 82 var file *os.File 83 isDirectory := metadata.Mode&ModeTypeDirectory != 0 84 if isDirectory { 85 file, err = os.Open(path) 86 if err != nil { 87 windows.CloseHandle(handle) 88 return nil, nil, fmt.Errorf("unable to open file object for directory: %w", err) 89 } 90 } else { 91 file = os.NewFile(uintptr(handle), path) 92 } 93 94 // Success. 95 if isDirectory { 96 return &Directory{ 97 handle: handle, 98 file: file, 99 }, metadata, nil 100 } else { 101 return file, metadata, nil 102 } 103 }