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

View as plain text