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  }