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/filepathlite"
     9  	"internal/godebug"
    10  	"internal/syscall/windows"
    11  	"sync"
    12  	"syscall"
    13  	"time"
    14  	"unsafe"
    15  )
    16  
    17  // A fileStat is the implementation of FileInfo returned by Stat and Lstat.
    18  type fileStat struct {
    19  	name string
    20  
    21  	// from ByHandleFileInformation, Win32FileAttributeData, Win32finddata, and GetFileInformationByHandleEx
    22  	FileAttributes uint32
    23  	CreationTime   syscall.Filetime
    24  	LastAccessTime syscall.Filetime
    25  	LastWriteTime  syscall.Filetime
    26  	FileSizeHigh   uint32
    27  	FileSizeLow    uint32
    28  
    29  	// from Win32finddata and GetFileInformationByHandleEx
    30  	ReparseTag uint32
    31  
    32  	// what syscall.GetFileType returns
    33  	filetype uint32
    34  
    35  	// used to implement SameFile
    36  	sync.Mutex
    37  	path             string
    38  	vol              uint32
    39  	idxhi            uint32
    40  	idxlo            uint32
    41  	appendNameToPath bool
    42  }
    43  
    44  // newFileStatFromGetFileInformationByHandle calls GetFileInformationByHandle
    45  // to gather all required information about the file handle h.
    46  func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (fs *fileStat, err error) {
    47  	var d syscall.ByHandleFileInformation
    48  	err = syscall.GetFileInformationByHandle(h, &d)
    49  	if err != nil {
    50  		return nil, &PathError{Op: "GetFileInformationByHandle", Path: path, Err: err}
    51  	}
    52  
    53  	var reparseTag uint32
    54  	if d.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
    55  		var ti windows.FILE_ATTRIBUTE_TAG_INFO
    56  		err = windows.GetFileInformationByHandleEx(h, windows.FileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti)))
    57  		if err != nil {
    58  			return nil, &PathError{Op: "GetFileInformationByHandleEx", Path: path, Err: err}
    59  		}
    60  		reparseTag = ti.ReparseTag
    61  	}
    62  
    63  	return &fileStat{
    64  		name:           filepathlite.Base(path),
    65  		FileAttributes: d.FileAttributes,
    66  		CreationTime:   d.CreationTime,
    67  		LastAccessTime: d.LastAccessTime,
    68  		LastWriteTime:  d.LastWriteTime,
    69  		FileSizeHigh:   d.FileSizeHigh,
    70  		FileSizeLow:    d.FileSizeLow,
    71  		vol:            d.VolumeSerialNumber,
    72  		idxhi:          d.FileIndexHigh,
    73  		idxlo:          d.FileIndexLow,
    74  		ReparseTag:     reparseTag,
    75  		// fileStat.path is used by os.SameFile to decide if it needs
    76  		// to fetch vol, idxhi and idxlo. But these are already set,
    77  		// so set fileStat.path to "" to prevent os.SameFile doing it again.
    78  	}, nil
    79  }
    80  
    81  // newFileStatFromWin32FileAttributeData copies all required information
    82  // from syscall.Win32FileAttributeData d into the newly created fileStat.
    83  func newFileStatFromWin32FileAttributeData(d *syscall.Win32FileAttributeData) *fileStat {
    84  	return &fileStat{
    85  		FileAttributes: d.FileAttributes,
    86  		CreationTime:   d.CreationTime,
    87  		LastAccessTime: d.LastAccessTime,
    88  		LastWriteTime:  d.LastWriteTime,
    89  		FileSizeHigh:   d.FileSizeHigh,
    90  		FileSizeLow:    d.FileSizeLow,
    91  	}
    92  }
    93  
    94  // newFileStatFromFileIDBothDirInfo copies all required information
    95  // from windows.FILE_ID_BOTH_DIR_INFO d into the newly created fileStat.
    96  func newFileStatFromFileIDBothDirInfo(d *windows.FILE_ID_BOTH_DIR_INFO) *fileStat {
    97  	// The FILE_ID_BOTH_DIR_INFO MSDN documentations isn't completely correct.
    98  	// FileAttributes can contain any file attributes that is currently set on the file,
    99  	// not just the ones documented.
   100  	// EaSize contains the reparse tag if the file is a reparse point.
   101  	return &fileStat{
   102  		FileAttributes: d.FileAttributes,
   103  		CreationTime:   d.CreationTime,
   104  		LastAccessTime: d.LastAccessTime,
   105  		LastWriteTime:  d.LastWriteTime,
   106  		FileSizeHigh:   uint32(d.EndOfFile >> 32),
   107  		FileSizeLow:    uint32(d.EndOfFile),
   108  		ReparseTag:     d.EaSize,
   109  		idxhi:          uint32(d.FileID >> 32),
   110  		idxlo:          uint32(d.FileID),
   111  	}
   112  }
   113  
   114  // newFileStatFromFileFullDirInfo copies all required information
   115  // from windows.FILE_FULL_DIR_INFO d into the newly created fileStat.
   116  func newFileStatFromFileFullDirInfo(d *windows.FILE_FULL_DIR_INFO) *fileStat {
   117  	return &fileStat{
   118  		FileAttributes: d.FileAttributes,
   119  		CreationTime:   d.CreationTime,
   120  		LastAccessTime: d.LastAccessTime,
   121  		LastWriteTime:  d.LastWriteTime,
   122  		FileSizeHigh:   uint32(d.EndOfFile >> 32),
   123  		FileSizeLow:    uint32(d.EndOfFile),
   124  		ReparseTag:     d.EaSize,
   125  	}
   126  }
   127  
   128  // newFileStatFromWin32finddata copies all required information
   129  // from syscall.Win32finddata d into the newly created fileStat.
   130  func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat {
   131  	fs := &fileStat{
   132  		FileAttributes: d.FileAttributes,
   133  		CreationTime:   d.CreationTime,
   134  		LastAccessTime: d.LastAccessTime,
   135  		LastWriteTime:  d.LastWriteTime,
   136  		FileSizeHigh:   d.FileSizeHigh,
   137  		FileSizeLow:    d.FileSizeLow,
   138  	}
   139  	if d.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
   140  		// Per https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw:
   141  		// “If the dwFileAttributes member includes the FILE_ATTRIBUTE_REPARSE_POINT
   142  		// attribute, this member specifies the reparse point tag. Otherwise, this
   143  		// value is undefined and should not be used.”
   144  		fs.ReparseTag = d.Reserved0
   145  	}
   146  	return fs
   147  }
   148  
   149  // isReparseTagNameSurrogate determines whether a tag's associated
   150  // reparse point is a surrogate for another named entity (for example, a mounted folder).
   151  //
   152  // See https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-isreparsetagnamesurrogate
   153  // and https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-point-tags.
   154  func (fs *fileStat) isReparseTagNameSurrogate() bool {
   155  	// True for IO_REPARSE_TAG_SYMLINK and IO_REPARSE_TAG_MOUNT_POINT.
   156  	return fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 && fs.ReparseTag&0x20000000 != 0
   157  }
   158  
   159  func (fs *fileStat) Size() int64 {
   160  	return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow)
   161  }
   162  
   163  var winsymlink = godebug.New("winsymlink")
   164  
   165  func (fs *fileStat) Mode() FileMode {
   166  	m := fs.mode()
   167  	if winsymlink.Value() == "0" {
   168  		old := fs.modePreGo1_23()
   169  		if old != m {
   170  			winsymlink.IncNonDefault()
   171  			m = old
   172  		}
   173  	}
   174  	return m
   175  }
   176  
   177  func (fs *fileStat) mode() (m FileMode) {
   178  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
   179  		m |= 0444
   180  	} else {
   181  		m |= 0666
   182  	}
   183  
   184  	// Windows reports the FILE_ATTRIBUTE_DIRECTORY bit for reparse points
   185  	// that refer to directories, such as symlinks and mount points.
   186  	// However, we follow symlink POSIX semantics and do not set the mode bits.
   187  	// This allows users to walk directories without following links
   188  	// by just calling "fi, err := os.Lstat(name); err == nil && fi.IsDir()".
   189  	// Note that POSIX only defines the semantics for symlinks, not for
   190  	// mount points or other surrogate reparse points, but we treat them
   191  	// the same way for consistency. Also, mount points can contain infinite
   192  	// loops, so it is not safe to walk them without special handling.
   193  	if !fs.isReparseTagNameSurrogate() {
   194  		if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
   195  			m |= ModeDir | 0111
   196  		}
   197  
   198  		switch fs.filetype {
   199  		case syscall.FILE_TYPE_PIPE:
   200  			m |= ModeNamedPipe
   201  		case syscall.FILE_TYPE_CHAR:
   202  			m |= ModeDevice | ModeCharDevice
   203  		}
   204  	}
   205  
   206  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
   207  		switch fs.ReparseTag {
   208  		case syscall.IO_REPARSE_TAG_SYMLINK:
   209  			m |= ModeSymlink
   210  		case windows.IO_REPARSE_TAG_AF_UNIX:
   211  			m |= ModeSocket
   212  		case windows.IO_REPARSE_TAG_DEDUP:
   213  			// If the Data Deduplication service is enabled on Windows Server, its
   214  			// Optimization job may convert regular files to IO_REPARSE_TAG_DEDUP
   215  			// whenever that job runs.
   216  			//
   217  			// However, DEDUP reparse points remain similar in most respects to
   218  			// regular files: they continue to support random-access reads and writes
   219  			// of persistent data, and they shouldn't add unexpected latency or
   220  			// unavailability in the way that a network filesystem might.
   221  			//
   222  			// Go programs may use ModeIrregular to filter out unusual files (such as
   223  			// raw device files on Linux, POSIX FIFO special files, and so on), so
   224  			// to avoid files changing unpredictably from regular to irregular we will
   225  			// consider DEDUP files to be close enough to regular to treat as such.
   226  		default:
   227  			m |= ModeIrregular
   228  		}
   229  	}
   230  	return
   231  }
   232  
   233  // modePreGo1_23 returns the FileMode for the fileStat, using the pre-Go 1.23
   234  // logic for determining the file mode.
   235  // The logic is subtle and not well-documented, so it is better to keep it
   236  // separate from the new logic.
   237  func (fs *fileStat) modePreGo1_23() (m FileMode) {
   238  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
   239  		m |= 0444
   240  	} else {
   241  		m |= 0666
   242  	}
   243  	if fs.ReparseTag == syscall.IO_REPARSE_TAG_SYMLINK ||
   244  		fs.ReparseTag == windows.IO_REPARSE_TAG_MOUNT_POINT {
   245  		return m | ModeSymlink
   246  	}
   247  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
   248  		m |= ModeDir | 0111
   249  	}
   250  	switch fs.filetype {
   251  	case syscall.FILE_TYPE_PIPE:
   252  		m |= ModeNamedPipe
   253  	case syscall.FILE_TYPE_CHAR:
   254  		m |= ModeDevice | ModeCharDevice
   255  	}
   256  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
   257  		if fs.ReparseTag == windows.IO_REPARSE_TAG_AF_UNIX {
   258  			m |= ModeSocket
   259  		}
   260  		if m&ModeType == 0 {
   261  			if fs.ReparseTag == windows.IO_REPARSE_TAG_DEDUP {
   262  				// See comment in fs.Mode.
   263  			} else {
   264  				m |= ModeIrregular
   265  			}
   266  		}
   267  	}
   268  	return m
   269  }
   270  
   271  func (fs *fileStat) ModTime() time.Time {
   272  	return time.Unix(0, fs.LastWriteTime.Nanoseconds())
   273  }
   274  
   275  // Sys returns syscall.Win32FileAttributeData for file fs.
   276  func (fs *fileStat) Sys() any {
   277  	return &syscall.Win32FileAttributeData{
   278  		FileAttributes: fs.FileAttributes,
   279  		CreationTime:   fs.CreationTime,
   280  		LastAccessTime: fs.LastAccessTime,
   281  		LastWriteTime:  fs.LastWriteTime,
   282  		FileSizeHigh:   fs.FileSizeHigh,
   283  		FileSizeLow:    fs.FileSizeLow,
   284  	}
   285  }
   286  
   287  func (fs *fileStat) loadFileId() error {
   288  	fs.Lock()
   289  	defer fs.Unlock()
   290  	if fs.path == "" {
   291  		// already done
   292  		return nil
   293  	}
   294  	var path string
   295  	if fs.appendNameToPath {
   296  		path = fixLongPath(fs.path + `\` + fs.name)
   297  	} else {
   298  		path = fs.path
   299  	}
   300  	pathp, err := syscall.UTF16PtrFromString(path)
   301  	if err != nil {
   302  		return err
   303  	}
   304  
   305  	// Per https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-points-and-file-operations,
   306  	// “Applications that use the CreateFile function should specify the
   307  	// FILE_FLAG_OPEN_REPARSE_POINT flag when opening the file if it is a reparse
   308  	// point.”
   309  	//
   310  	// And per https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew,
   311  	// “If the file is not a reparse point, then this flag is ignored.”
   312  	//
   313  	// So we set FILE_FLAG_OPEN_REPARSE_POINT unconditionally, since we want
   314  	// information about the reparse point itself.
   315  	//
   316  	// If the file is a symlink, the symlink target should have already been
   317  	// resolved when the fileStat was created, so we don't need to worry about
   318  	// resolving symlink reparse points again here.
   319  	attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
   320  
   321  	h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
   322  	if err != nil {
   323  		return err
   324  	}
   325  	defer syscall.CloseHandle(h)
   326  	var i syscall.ByHandleFileInformation
   327  	err = syscall.GetFileInformationByHandle(h, &i)
   328  	if err != nil {
   329  		return err
   330  	}
   331  	fs.path = ""
   332  	fs.vol = i.VolumeSerialNumber
   333  	fs.idxhi = i.FileIndexHigh
   334  	fs.idxlo = i.FileIndexLow
   335  	return nil
   336  }
   337  
   338  // saveInfoFromPath saves full path of the file to be used by os.SameFile later,
   339  // and set name from path.
   340  func (fs *fileStat) saveInfoFromPath(path string) error {
   341  	fs.path = path
   342  	if !filepathlite.IsAbs(fs.path) {
   343  		var err error
   344  		fs.path, err = syscall.FullPath(fs.path)
   345  		if err != nil {
   346  			return &PathError{Op: "FullPath", Path: path, Err: err}
   347  		}
   348  	}
   349  	fs.name = filepathlite.Base(path)
   350  	return nil
   351  }
   352  
   353  func sameFile(fs1, fs2 *fileStat) bool {
   354  	e := fs1.loadFileId()
   355  	if e != nil {
   356  		return false
   357  	}
   358  	e = fs2.loadFileId()
   359  	if e != nil {
   360  		return false
   361  	}
   362  	return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo
   363  }
   364  
   365  // For testing.
   366  func atime(fi FileInfo) time.Time {
   367  	return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
   368  }
   369  

View as plain text