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  }