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 and Win32finddata
    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
    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  // newFileStatFromWin32finddata copies all required information
    84  // from syscall.Win32finddata d into the newly created fileStat.
    85  func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat {
    86  	fs := &fileStat{
    87  		FileAttributes: d.FileAttributes,
    88  		CreationTime:   d.CreationTime,
    89  		LastAccessTime: d.LastAccessTime,
    90  		LastWriteTime:  d.LastWriteTime,
    91  		FileSizeHigh:   d.FileSizeHigh,
    92  		FileSizeLow:    d.FileSizeLow,
    93  	}
    94  	if d.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
    95  		// Per https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw:
    96  		// “If the dwFileAttributes member includes the FILE_ATTRIBUTE_REPARSE_POINT
    97  		// attribute, this member specifies the reparse point tag. Otherwise, this
    98  		// value is undefined and should not be used.”
    99  		fs.ReparseTag = d.Reserved0
   100  	}
   101  	return fs
   102  }
   103  
   104  func (fs *fileStat) isSymlink() bool {
   105  	// As of https://go.dev/cl/86556, we treat MOUNT_POINT reparse points as
   106  	// symlinks because otherwise certain directory junction tests in the
   107  	// path/filepath package would fail.
   108  	//
   109  	// However,
   110  	// https://learn.microsoft.com/en-us/windows/win32/fileio/hard-links-and-junctions
   111  	// seems to suggest that directory junctions should be treated like hard
   112  	// links, not symlinks.
   113  	//
   114  	// TODO(bcmills): Get more input from Microsoft on what the behavior ought to
   115  	// be for MOUNT_POINT reparse points.
   116  
   117  	return fs.ReparseTag == syscall.IO_REPARSE_TAG_SYMLINK ||
   118  		fs.ReparseTag == windows.IO_REPARSE_TAG_MOUNT_POINT
   119  }
   120  
   121  func (fs *fileStat) Size() int64 {
   122  	return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow)
   123  }
   124  
   125  func (fs *fileStat) Mode() (m FileMode) {
   126  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
   127  		m |= 0444
   128  	} else {
   129  		m |= 0666
   130  	}
   131  	if fs.isSymlink() {
   132  		return m | ModeSymlink
   133  	}
   134  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
   135  		m |= ModeDir | 0111
   136  	}
   137  	switch fs.filetype {
   138  	case syscall.FILE_TYPE_PIPE:
   139  		m |= ModeNamedPipe
   140  	case syscall.FILE_TYPE_CHAR:
   141  		m |= ModeDevice | ModeCharDevice
   142  	}
   143  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 && m&ModeType == 0 {
   144  		if fs.ReparseTag == windows.IO_REPARSE_TAG_DEDUP {
   145  			// If the Data Deduplication service is enabled on Windows Server, its
   146  			// Optimization job may convert regular files to IO_REPARSE_TAG_DEDUP
   147  			// whenever that job runs.
   148  			//
   149  			// However, DEDUP reparse points remain similar in most respects to
   150  			// regular files: they continue to support random-access reads and writes
   151  			// of persistent data, and they shouldn't add unexpected latency or
   152  			// unavailability in the way that a network filesystem might.
   153  			//
   154  			// Go programs may use ModeIrregular to filter out unusual files (such as
   155  			// raw device files on Linux, POSIX FIFO special files, and so on), so
   156  			// to avoid files changing unpredictably from regular to irregular we will
   157  			// consider DEDUP files to be close enough to regular to treat as such.
   158  		} else {
   159  			m |= ModeIrregular
   160  		}
   161  	}
   162  	return m
   163  }
   164  
   165  func (fs *fileStat) ModTime() time.Time {
   166  	return time.Unix(0, fs.LastWriteTime.Nanoseconds())
   167  }
   168  
   169  // Sys returns syscall.Win32FileAttributeData for file fs.
   170  func (fs *fileStat) Sys() any {
   171  	return &syscall.Win32FileAttributeData{
   172  		FileAttributes: fs.FileAttributes,
   173  		CreationTime:   fs.CreationTime,
   174  		LastAccessTime: fs.LastAccessTime,
   175  		LastWriteTime:  fs.LastWriteTime,
   176  		FileSizeHigh:   fs.FileSizeHigh,
   177  		FileSizeLow:    fs.FileSizeLow,
   178  	}
   179  }
   180  
   181  func (fs *fileStat) loadFileId() error {
   182  	fs.Lock()
   183  	defer fs.Unlock()
   184  	if fs.path == "" {
   185  		// already done
   186  		return nil
   187  	}
   188  	var path string
   189  	if fs.appendNameToPath {
   190  		path = fs.path + `\` + fs.name
   191  	} else {
   192  		path = fs.path
   193  	}
   194  	pathp, err := syscall.UTF16PtrFromString(path)
   195  	if err != nil {
   196  		return err
   197  	}
   198  
   199  	// Per https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-points-and-file-operations,
   200  	// “Applications that use the CreateFile function should specify the
   201  	// FILE_FLAG_OPEN_REPARSE_POINT flag when opening the file if it is a reparse
   202  	// point.”
   203  	//
   204  	// And per https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew,
   205  	// “If the file is not a reparse point, then this flag is ignored.”
   206  	//
   207  	// So we set FILE_FLAG_OPEN_REPARSE_POINT unconditionally, since we want
   208  	// information about the reparse point itself.
   209  	//
   210  	// If the file is a symlink, the symlink target should have already been
   211  	// resolved when the fileStat was created, so we don't need to worry about
   212  	// resolving symlink reparse points again here.
   213  	attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
   214  
   215  	h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
   216  	if err != nil {
   217  		return err
   218  	}
   219  	defer syscall.CloseHandle(h)
   220  	var i syscall.ByHandleFileInformation
   221  	err = syscall.GetFileInformationByHandle(h, &i)
   222  	if err != nil {
   223  		return err
   224  	}
   225  	fs.path = ""
   226  	fs.vol = i.VolumeSerialNumber
   227  	fs.idxhi = i.FileIndexHigh
   228  	fs.idxlo = i.FileIndexLow
   229  	return nil
   230  }
   231  
   232  // saveInfoFromPath saves full path of the file to be used by os.SameFile later,
   233  // and set name from path.
   234  func (fs *fileStat) saveInfoFromPath(path string) error {
   235  	fs.path = path
   236  	if !isAbs(fs.path) {
   237  		var err error
   238  		fs.path, err = syscall.FullPath(fs.path)
   239  		if err != nil {
   240  			return &PathError{Op: "FullPath", Path: path, Err: err}
   241  		}
   242  	}
   243  	fs.name = basename(path)
   244  	return nil
   245  }
   246  
   247  func sameFile(fs1, fs2 *fileStat) bool {
   248  	e := fs1.loadFileId()
   249  	if e != nil {
   250  		return false
   251  	}
   252  	e = fs2.loadFileId()
   253  	if e != nil {
   254  		return false
   255  	}
   256  	return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo
   257  }
   258  
   259  // For testing.
   260  func atime(fi FileInfo) time.Time {
   261  	return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
   262  }
   263  

View as plain text