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  	Reserved0 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  		Reserved0:      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  	return &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  		Reserved0:      d.Reserved0,
    94  	}
    95  }
    96  
    97  func (fs *fileStat) isSymlink() bool {
    98  	// Use instructions described at
    99  	// https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/
   100  	// to recognize whether it's a symlink.
   101  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
   102  		return false
   103  	}
   104  	return fs.Reserved0 == syscall.IO_REPARSE_TAG_SYMLINK ||
   105  		fs.Reserved0 == windows.IO_REPARSE_TAG_MOUNT_POINT
   106  }
   107  
   108  func (fs *fileStat) Size() int64 {
   109  	return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow)
   110  }
   111  
   112  func (fs *fileStat) Mode() (m FileMode) {
   113  	if fs == &devNullStat {
   114  		return ModeDevice | ModeCharDevice | 0666
   115  	}
   116  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
   117  		m |= 0444
   118  	} else {
   119  		m |= 0666
   120  	}
   121  	if fs.isSymlink() {
   122  		return m | ModeSymlink
   123  	}
   124  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
   125  		m |= ModeDir | 0111
   126  	}
   127  	switch fs.filetype {
   128  	case syscall.FILE_TYPE_PIPE:
   129  		m |= ModeNamedPipe
   130  	case syscall.FILE_TYPE_CHAR:
   131  		m |= ModeDevice | ModeCharDevice
   132  	}
   133  	return m
   134  }
   135  
   136  func (fs *fileStat) ModTime() time.Time {
   137  	return time.Unix(0, fs.LastWriteTime.Nanoseconds())
   138  }
   139  
   140  // Sys returns syscall.Win32FileAttributeData for file fs.
   141  func (fs *fileStat) Sys() any {
   142  	return &syscall.Win32FileAttributeData{
   143  		FileAttributes: fs.FileAttributes,
   144  		CreationTime:   fs.CreationTime,
   145  		LastAccessTime: fs.LastAccessTime,
   146  		LastWriteTime:  fs.LastWriteTime,
   147  		FileSizeHigh:   fs.FileSizeHigh,
   148  		FileSizeLow:    fs.FileSizeLow,
   149  	}
   150  }
   151  
   152  func (fs *fileStat) loadFileId() error {
   153  	fs.Lock()
   154  	defer fs.Unlock()
   155  	if fs.path == "" {
   156  		// already done
   157  		return nil
   158  	}
   159  	var path string
   160  	if fs.appendNameToPath {
   161  		path = fs.path + `\` + fs.name
   162  	} else {
   163  		path = fs.path
   164  	}
   165  	pathp, err := syscall.UTF16PtrFromString(path)
   166  	if err != nil {
   167  		return err
   168  	}
   169  	attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
   170  	if fs.isSymlink() {
   171  		// Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink.
   172  		// See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted
   173  		attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT
   174  	}
   175  	h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
   176  	if err != nil {
   177  		return err
   178  	}
   179  	defer syscall.CloseHandle(h)
   180  	var i syscall.ByHandleFileInformation
   181  	err = syscall.GetFileInformationByHandle(h, &i)
   182  	if err != nil {
   183  		return err
   184  	}
   185  	fs.path = ""
   186  	fs.vol = i.VolumeSerialNumber
   187  	fs.idxhi = i.FileIndexHigh
   188  	fs.idxlo = i.FileIndexLow
   189  	return nil
   190  }
   191  
   192  // saveInfoFromPath saves full path of the file to be used by os.SameFile later,
   193  // and set name from path.
   194  func (fs *fileStat) saveInfoFromPath(path string) error {
   195  	fs.path = path
   196  	if !isAbs(fs.path) {
   197  		var err error
   198  		fs.path, err = syscall.FullPath(fs.path)
   199  		if err != nil {
   200  			return &PathError{Op: "FullPath", Path: path, Err: err}
   201  		}
   202  	}
   203  	fs.name = basename(path)
   204  	return nil
   205  }
   206  
   207  // devNullStat is fileStat structure describing DevNull file ("NUL").
   208  var devNullStat = fileStat{
   209  	name: DevNull,
   210  	// hopefully this will work for SameFile
   211  	vol:   0,
   212  	idxhi: 0,
   213  	idxlo: 0,
   214  }
   215  
   216  func sameFile(fs1, fs2 *fileStat) bool {
   217  	e := fs1.loadFileId()
   218  	if e != nil {
   219  		return false
   220  	}
   221  	e = fs2.loadFileId()
   222  	if e != nil {
   223  		return false
   224  	}
   225  	return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo
   226  }
   227  
   228  // For testing.
   229  func atime(fi FileInfo) time.Time {
   230  	return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
   231  }
   232  

View as plain text