github.com/AndrienkoAleksandr/go@v0.0.19/src/os/types_windows.go (about)

     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  }
    40  
    41  // newFileStatFromGetFileInformationByHandle calls GetFileInformationByHandle
    42  // to gather all required information about the file handle h.
    43  func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (fs *fileStat, err error) {
    44  	var d syscall.ByHandleFileInformation
    45  	err = syscall.GetFileInformationByHandle(h, &d)
    46  	if err != nil {
    47  		return nil, &PathError{Op: "GetFileInformationByHandle", Path: path, Err: err}
    48  	}
    49  
    50  	var ti windows.FILE_ATTRIBUTE_TAG_INFO
    51  	err = windows.GetFileInformationByHandleEx(h, windows.FileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti)))
    52  	if err != nil {
    53  		if errno, ok := err.(syscall.Errno); ok && errno == windows.ERROR_INVALID_PARAMETER {
    54  			// It appears calling GetFileInformationByHandleEx with
    55  			// FILE_ATTRIBUTE_TAG_INFO fails on FAT file system with
    56  			// ERROR_INVALID_PARAMETER. Clear ti.ReparseTag in that
    57  			// instance to indicate no symlinks are possible.
    58  			ti.ReparseTag = 0
    59  		} else {
    60  			return nil, &PathError{Op: "GetFileInformationByHandleEx", Path: path, Err: err}
    61  		}
    62  	}
    63  
    64  	return &fileStat{
    65  		name:           basename(path),
    66  		FileAttributes: d.FileAttributes,
    67  		CreationTime:   d.CreationTime,
    68  		LastAccessTime: d.LastAccessTime,
    69  		LastWriteTime:  d.LastWriteTime,
    70  		FileSizeHigh:   d.FileSizeHigh,
    71  		FileSizeLow:    d.FileSizeLow,
    72  		vol:            d.VolumeSerialNumber,
    73  		idxhi:          d.FileIndexHigh,
    74  		idxlo:          d.FileIndexLow,
    75  		ReparseTag:     ti.ReparseTag,
    76  		// fileStat.path is used by os.SameFile to decide if it needs
    77  		// to fetch vol, idxhi and idxlo. But these are already set,
    78  		// so set fileStat.path to "" to prevent os.SameFile doing it again.
    79  	}, nil
    80  }
    81  
    82  // newFileStatFromFileIDBothDirInfo copies all required information
    83  // from windows.FILE_ID_BOTH_DIR_INFO d into the newly created fileStat.
    84  func newFileStatFromFileIDBothDirInfo(d *windows.FILE_ID_BOTH_DIR_INFO) *fileStat {
    85  	// The FILE_ID_BOTH_DIR_INFO MSDN documentations isn't completely correct.
    86  	// FileAttributes can contain any file attributes that is currently set on the file,
    87  	// not just the ones documented.
    88  	// EaSize contains the reparse tag if the file is a reparse point.
    89  	return &fileStat{
    90  		FileAttributes: d.FileAttributes,
    91  		CreationTime:   d.CreationTime,
    92  		LastAccessTime: d.LastAccessTime,
    93  		LastWriteTime:  d.LastWriteTime,
    94  		FileSizeHigh:   uint32(d.EndOfFile >> 32),
    95  		FileSizeLow:    uint32(d.EndOfFile),
    96  		ReparseTag:     d.EaSize,
    97  		idxhi:          uint32(d.FileID >> 32),
    98  		idxlo:          uint32(d.FileID),
    99  	}
   100  }
   101  
   102  // newFileStatFromWin32finddata copies all required information
   103  // from syscall.Win32finddata d into the newly created fileStat.
   104  func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat {
   105  	fs := &fileStat{
   106  		FileAttributes: d.FileAttributes,
   107  		CreationTime:   d.CreationTime,
   108  		LastAccessTime: d.LastAccessTime,
   109  		LastWriteTime:  d.LastWriteTime,
   110  		FileSizeHigh:   d.FileSizeHigh,
   111  		FileSizeLow:    d.FileSizeLow,
   112  	}
   113  	if d.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
   114  		// Per https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw:
   115  		// “If the dwFileAttributes member includes the FILE_ATTRIBUTE_REPARSE_POINT
   116  		// attribute, this member specifies the reparse point tag. Otherwise, this
   117  		// value is undefined and should not be used.”
   118  		fs.ReparseTag = d.Reserved0
   119  	}
   120  	return fs
   121  }
   122  
   123  func (fs *fileStat) isSymlink() bool {
   124  	// As of https://go.dev/cl/86556, we treat MOUNT_POINT reparse points as
   125  	// symlinks because otherwise certain directory junction tests in the
   126  	// path/filepath package would fail.
   127  	//
   128  	// However,
   129  	// https://learn.microsoft.com/en-us/windows/win32/fileio/hard-links-and-junctions
   130  	// seems to suggest that directory junctions should be treated like hard
   131  	// links, not symlinks.
   132  	//
   133  	// TODO(bcmills): Get more input from Microsoft on what the behavior ought to
   134  	// be for MOUNT_POINT reparse points.
   135  
   136  	return fs.ReparseTag == syscall.IO_REPARSE_TAG_SYMLINK ||
   137  		fs.ReparseTag == windows.IO_REPARSE_TAG_MOUNT_POINT
   138  }
   139  
   140  func (fs *fileStat) Size() int64 {
   141  	return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow)
   142  }
   143  
   144  func (fs *fileStat) Mode() (m FileMode) {
   145  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
   146  		m |= 0444
   147  	} else {
   148  		m |= 0666
   149  	}
   150  	if fs.isSymlink() {
   151  		return m | ModeSymlink
   152  	}
   153  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
   154  		m |= ModeDir | 0111
   155  	}
   156  	switch fs.filetype {
   157  	case syscall.FILE_TYPE_PIPE:
   158  		m |= ModeNamedPipe
   159  	case syscall.FILE_TYPE_CHAR:
   160  		m |= ModeDevice | ModeCharDevice
   161  	}
   162  	if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 && m&ModeType == 0 {
   163  		m |= ModeIrregular
   164  	}
   165  	return m
   166  }
   167  
   168  func (fs *fileStat) ModTime() time.Time {
   169  	return time.Unix(0, fs.LastWriteTime.Nanoseconds())
   170  }
   171  
   172  // Sys returns syscall.Win32FileAttributeData for file fs.
   173  func (fs *fileStat) Sys() any {
   174  	return &syscall.Win32FileAttributeData{
   175  		FileAttributes: fs.FileAttributes,
   176  		CreationTime:   fs.CreationTime,
   177  		LastAccessTime: fs.LastAccessTime,
   178  		LastWriteTime:  fs.LastWriteTime,
   179  		FileSizeHigh:   fs.FileSizeHigh,
   180  		FileSizeLow:    fs.FileSizeLow,
   181  	}
   182  }
   183  
   184  func (fs *fileStat) loadFileId() error {
   185  	fs.Lock()
   186  	defer fs.Unlock()
   187  	if fs.path == "" {
   188  		// already done
   189  		return nil
   190  	}
   191  	pathp, err := syscall.UTF16PtrFromString(fs.path)
   192  	if err != nil {
   193  		return err
   194  	}
   195  
   196  	// Per https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-points-and-file-operations,
   197  	// “Applications that use the CreateFile function should specify the
   198  	// FILE_FLAG_OPEN_REPARSE_POINT flag when opening the file if it is a reparse
   199  	// point.”
   200  	//
   201  	// And per https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew,
   202  	// “If the file is not a reparse point, then this flag is ignored.”
   203  	//
   204  	// So we set FILE_FLAG_OPEN_REPARSE_POINT unconditionally, since we want
   205  	// information about the reparse point itself.
   206  	//
   207  	// If the file is a symlink, the symlink target should have already been
   208  	// resolved when the fileStat was created, so we don't need to worry about
   209  	// resolving symlink reparse points again here.
   210  	attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
   211  
   212  	h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
   213  	if err != nil {
   214  		return err
   215  	}
   216  	defer syscall.CloseHandle(h)
   217  	var i syscall.ByHandleFileInformation
   218  	err = syscall.GetFileInformationByHandle(h, &i)
   219  	if err != nil {
   220  		return err
   221  	}
   222  	fs.path = ""
   223  	fs.vol = i.VolumeSerialNumber
   224  	fs.idxhi = i.FileIndexHigh
   225  	fs.idxlo = i.FileIndexLow
   226  	return nil
   227  }
   228  
   229  // saveInfoFromPath saves full path of the file to be used by os.SameFile later,
   230  // and set name from path.
   231  func (fs *fileStat) saveInfoFromPath(path string) error {
   232  	fs.path = path
   233  	if !isAbs(fs.path) {
   234  		var err error
   235  		fs.path, err = syscall.FullPath(fs.path)
   236  		if err != nil {
   237  			return &PathError{Op: "FullPath", Path: path, Err: err}
   238  		}
   239  	}
   240  	fs.name = basename(path)
   241  	return nil
   242  }
   243  
   244  func sameFile(fs1, fs2 *fileStat) bool {
   245  	e := fs1.loadFileId()
   246  	if e != nil {
   247  		return false
   248  	}
   249  	e = fs2.loadFileId()
   250  	if e != nil {
   251  		return false
   252  	}
   253  	return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo
   254  }
   255  
   256  // For testing.
   257  func atime(fi FileInfo) time.Time {
   258  	return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
   259  }