Source file src/os/types_windows.go

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package os
     6  
     7  import (
     8  	"internal/syscall/windows"
     9  	"sync"
    10  	"syscall"
    11  	"time"
    12  	"unsafe"
    13  )
    14  
    15  // A fileStat is the implementation of FileInfo returned by Stat and Lstat.
    16  type fileStat struct {
    17  	name string
    18  
    19  	// from ByHandleFileInformation, Win32FileAttributeData, Win32finddata, and GetFileInformationByHandleEx
    20  	FileAttributes uint32
    21  	CreationTime   syscall.Filetime
    22  	LastAccessTime syscall.Filetime
    23  	LastWriteTime  syscall.Filetime
    24  	FileSizeHigh   uint32
    25  	FileSizeLow    uint32
    26  
    27  	// from Win32finddata and GetFileInformationByHandleEx
    28  	ReparseTag uint32
    29  
    30  	// what syscall.GetFileType returns
    31  	filetype uint32
    32  
    33  	// used to implement SameFile
    34  	sync.Mutex
    35  	path             string
    36  	vol              uint32
    37  	idxhi            uint32
    38  	idxlo            uint32
    39  	appendNameToPath bool
    40  }
    41  
    42  // newFileStatFromGetFileInformationByHandle calls GetFileInformationByHandle
    43  // to gather all required information about the file handle h.
    44  func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (fs *fileStat, err error) {
    45  	var d syscall.ByHandleFileInformation
    46  	err = syscall.GetFileInformationByHandle(h, &d)
    47  	if err != nil {
    48  		return nil, &PathError{Op: "GetFileInformationByHandle", Path: path, Err: err}
    49  	}
    50  
    51  	var ti windows.FILE_ATTRIBUTE_TAG_INFO
    52  	err = windows.GetFileInformationByHandleEx(h, windows.FileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti)))
    53  	if err != nil {
    54  		if errno, ok := err.(syscall.Errno); ok && errno == windows.ERROR_INVALID_PARAMETER {
    55  			// It appears calling GetFileInformationByHandleEx with
    56  			// FILE_ATTRIBUTE_TAG_INFO fails on FAT file system with
    57  			// ERROR_INVALID_PARAMETER. Clear ti.ReparseTag in that
    58  			// instance to indicate no symlinks are possible.
    59  			ti.ReparseTag = 0
    60  		} else {
    61  			return nil, &PathError{Op: "GetFileInformationByHandleEx", Path: path, Err: err}
    62  		}
    63  	}
    64  
    65  	return &fileStat{
    66  		name:           basename(path),
    67  		FileAttributes: d.FileAttributes,
    68  		CreationTime:   d.CreationTime,
    69  		LastAccessTime: d.LastAccessTime,
    70  		LastWriteTime:  d.LastWriteTime,
    71  		FileSizeHigh:   d.FileSizeHigh,
    72  		FileSizeLow:    d.FileSizeLow,
    73  		vol:            d.VolumeSerialNumber,
    74  		idxhi:          d.FileIndexHigh,
    75  		idxlo:          d.FileIndexLow,
    76  		ReparseTag:     ti.ReparseTag,
    77  		// fileStat.path is used by os.SameFile to decide if it needs
    78  		// to fetch vol, idxhi and idxlo. But these are already set,
    79  		// so set fileStat.path to "" to prevent os.SameFile doing it again.
    80  	}, nil
    81  }
    82  
    83  // newFileStatFromFileIDBothDirInfo copies all required information
    84  // from windows.FILE_ID_BOTH_DIR_INFO d into the newly created fileStat.
    85  func newFileStatFromFileIDBothDirInfo(d *windows.FILE_ID_BOTH_DIR_INFO) *fileStat {
    86  	// The FILE_ID_BOTH_DIR_INFO MSDN documentations isn't completely correct.
    87  	// FileAttributes can contain any file attributes that is currently set on the file,
    88  	// not just the ones documented.
    89  	// EaSize contains the reparse tag if the file is a reparse point.
    90  	return &fileStat{
    91  		FileAttributes: d.FileAttributes,
    92  		CreationTime:   d.CreationTime,
    93  		LastAccessTime: d.LastAccessTime,
    94  		LastWriteTime:  d.LastWriteTime,
    95  		FileSizeHigh:   uint32(d.EndOfFile >> 32),
    96  		FileSizeLow:    uint32(d.EndOfFile),
    97  		ReparseTag:     d.EaSize,
    98  		idxhi:          uint32(d.FileID >> 32),
    99  		idxlo:          uint32(d.FileID),
   100  	}
   101  }
   102  
   103  // newFileStatFromFileFullDirInfo copies all required information
   104  // from windows.FILE_FULL_DIR_INFO d into the newly created fileStat.
   105  func newFileStatFromFileFullDirInfo(d *windows.FILE_FULL_DIR_INFO) *fileStat {
   106  	return &fileStat{
   107  		FileAttributes: d.FileAttributes,
   108  		CreationTime:   d.CreationTime,
   109  		LastAccessTime: d.LastAccessTime,
   110  		LastWriteTime:  d.LastWriteTime,
   111  		FileSizeHigh:   uint32(d.EndOfFile >> 32),
   112  		FileSizeLow:    uint32(d.EndOfFile),
   113  		ReparseTag:     d.EaSize,
   114  	}
   115  }
   116  
   117  // newFileStatFromWin32finddata copies all required information
   118  // from syscall.Win32finddata d into the newly created fileStat.
   119  func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat {
   120  	fs := &fileStat{
   121  		FileAttributes: d.FileAttributes,
   122  		CreationTime:   d.CreationTime,
   123  		LastAccessTime: d.LastAccessTime,
   124  		LastWriteTime:  d.LastWriteTime,
   125  		FileSizeHigh:   d.FileSizeHigh,
   126  		FileSizeLow:    d.FileSizeLow,
   127  	}
   128  	if d.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
   129  		// Per https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw:
   130  		// “If the dwFileAttributes member includes the FILE_ATTRIBUTE_REPARSE_POINT
   131  		// attribute, this member specifies the reparse point tag. Otherwise, this
   132  		// value is undefined and should not be used.”
   133  		fs.ReparseTag = d.Reserved0
   134  	}
   135  	return fs
   136  }
   137  
   138  // isReparseTagNameSurrogate determines whether a tag's associated
   139  // reparse point is a surrogate for another named entity (for example, a mounted folder).
   140  //
   141  // See https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-isreparsetagnamesurrogate
   142  // and https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-point-tags.
   143  func (fs *fileStat) isReparseTagNameSurrogate() bool {
   144  	// True for IO_REPARSE_TAG_SYMLINK and IO_REPARSE_TAG_MOUNT_POINT.
   145  	return fs.ReparseTag&0x20000000 != 0
   146  }
   147  
   148  func (fs *fileStat) isSymlink() bool {
   149  	// As of https://go.dev/cl/86556, we treat MOUNT_POINT reparse points as
   150  	// symlinks because otherwise certain directory junction tests in the
   151  	// path/filepath package would fail.
   152  	//
   153  	// However,
   154  	// https://learn.microsoft.com/en-us/windows/win32/fileio/hard-links-and-junctions
   155  	// seems to suggest that directory junctions should be treated like hard
   156  	// links, not symlinks.
   157  	//
   158  	// TODO(bcmills): Get more input from Microsoft on what the behavior ought to
   159  	// be for MOUNT_POINT reparse points.
   160  
   161  	return fs.ReparseTag == syscall.IO_REPARSE_TAG_SYMLINK ||
   162  		fs.ReparseTag == windows.IO_REPARSE_TAG_MOUNT_POINT
   163  }
   164  
   165  func (fs *fileStat) Size() int64 {
   166  	return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow)
   167  }
   168  
   169  func (fs *fileStat) Mode() (m FileMode) {
   170  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
   171  		m |= 0444
   172  	} else {
   173  		m |= 0666
   174  	}
   175  	if fs.isSymlink() {
   176  		return m | ModeSymlink
   177  	}
   178  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
   179  		m |= ModeDir | 0111
   180  	}
   181  	switch fs.filetype {
   182  	case syscall.FILE_TYPE_PIPE:
   183  		m |= ModeNamedPipe
   184  	case syscall.FILE_TYPE_CHAR:
   185  		m |= ModeDevice | ModeCharDevice
   186  	}
   187  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 && m&ModeType == 0 {
   188  		if fs.ReparseTag == windows.IO_REPARSE_TAG_DEDUP {
   189  			// If the Data Deduplication service is enabled on Windows Server, its
   190  			// Optimization job may convert regular files to IO_REPARSE_TAG_DEDUP
   191  			// whenever that job runs.
   192  			//
   193  			// However, DEDUP reparse points remain similar in most respects to
   194  			// regular files: they continue to support random-access reads and writes
   195  			// of persistent data, and they shouldn't add unexpected latency or
   196  			// unavailability in the way that a network filesystem might.
   197  			//
   198  			// Go programs may use ModeIrregular to filter out unusual files (such as
   199  			// raw device files on Linux, POSIX FIFO special files, and so on), so
   200  			// to avoid files changing unpredictably from regular to irregular we will
   201  			// consider DEDUP files to be close enough to regular to treat as such.
   202  		} else {
   203  			m |= ModeIrregular
   204  		}
   205  	}
   206  	return m
   207  }
   208  
   209  func (fs *fileStat) ModTime() time.Time {
   210  	return time.Unix(0, fs.LastWriteTime.Nanoseconds())
   211  }
   212  
   213  // Sys returns syscall.Win32FileAttributeData for file fs.
   214  func (fs *fileStat) Sys() any {
   215  	return &syscall.Win32FileAttributeData{
   216  		FileAttributes: fs.FileAttributes,
   217  		CreationTime:   fs.CreationTime,
   218  		LastAccessTime: fs.LastAccessTime,
   219  		LastWriteTime:  fs.LastWriteTime,
   220  		FileSizeHigh:   fs.FileSizeHigh,
   221  		FileSizeLow:    fs.FileSizeLow,
   222  	}
   223  }
   224  
   225  func (fs *fileStat) loadFileId() error {
   226  	fs.Lock()
   227  	defer fs.Unlock()
   228  	if fs.path == "" {
   229  		// already done
   230  		return nil
   231  	}
   232  	var path string
   233  	if fs.appendNameToPath {
   234  		path = fixLongPath(fs.path + `\` + fs.name)
   235  	} else {
   236  		path = fs.path
   237  	}
   238  	pathp, err := syscall.UTF16PtrFromString(path)
   239  	if err != nil {
   240  		return err
   241  	}
   242  
   243  	// Per https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-points-and-file-operations,
   244  	// “Applications that use the CreateFile function should specify the
   245  	// FILE_FLAG_OPEN_REPARSE_POINT flag when opening the file if it is a reparse
   246  	// point.”
   247  	//
   248  	// And per https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew,
   249  	// “If the file is not a reparse point, then this flag is ignored.”
   250  	//
   251  	// So we set FILE_FLAG_OPEN_REPARSE_POINT unconditionally, since we want
   252  	// information about the reparse point itself.
   253  	//
   254  	// If the file is a symlink, the symlink target should have already been
   255  	// resolved when the fileStat was created, so we don't need to worry about
   256  	// resolving symlink reparse points again here.
   257  	attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
   258  
   259  	h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
   260  	if err != nil {
   261  		return err
   262  	}
   263  	defer syscall.CloseHandle(h)
   264  	var i syscall.ByHandleFileInformation
   265  	err = syscall.GetFileInformationByHandle(h, &i)
   266  	if err != nil {
   267  		return err
   268  	}
   269  	fs.path = ""
   270  	fs.vol = i.VolumeSerialNumber
   271  	fs.idxhi = i.FileIndexHigh
   272  	fs.idxlo = i.FileIndexLow
   273  	return nil
   274  }
   275  
   276  // saveInfoFromPath saves full path of the file to be used by os.SameFile later,
   277  // and set name from path.
   278  func (fs *fileStat) saveInfoFromPath(path string) error {
   279  	fs.path = path
   280  	if !isAbs(fs.path) {
   281  		var err error
   282  		fs.path, err = syscall.FullPath(fs.path)
   283  		if err != nil {
   284  			return &PathError{Op: "FullPath", Path: path, Err: err}
   285  		}
   286  	}
   287  	fs.name = basename(path)
   288  	return nil
   289  }
   290  
   291  func sameFile(fs1, fs2 *fileStat) bool {
   292  	e := fs1.loadFileId()
   293  	if e != nil {
   294  		return false
   295  	}
   296  	e = fs2.loadFileId()
   297  	if e != nil {
   298  		return false
   299  	}
   300  	return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo
   301  }
   302  
   303  // For testing.
   304  func atime(fi FileInfo) time.Time {
   305  	return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
   306  }
   307  

View as plain text