|  | // Copyright 2009 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package os | 
|  |  | 
|  | import ( | 
|  | "internal/filepathlite" | 
|  | "internal/godebug" | 
|  | "internal/syscall/windows" | 
|  | "sync" | 
|  | "syscall" | 
|  | "time" | 
|  | "unsafe" | 
|  | ) | 
|  |  | 
|  | // A fileStat is the implementation of FileInfo returned by Stat and Lstat. | 
|  | type fileStat struct { | 
|  | name string | 
|  |  | 
|  | // from ByHandleFileInformation, Win32FileAttributeData, Win32finddata, and GetFileInformationByHandleEx | 
|  | FileAttributes uint32 | 
|  | CreationTime   syscall.Filetime | 
|  | LastAccessTime syscall.Filetime | 
|  | LastWriteTime  syscall.Filetime | 
|  | FileSizeHigh   uint32 | 
|  | FileSizeLow    uint32 | 
|  |  | 
|  | // from Win32finddata and GetFileInformationByHandleEx | 
|  | ReparseTag uint32 | 
|  |  | 
|  | // what syscall.GetFileType returns | 
|  | filetype uint32 | 
|  |  | 
|  | // used to implement SameFile | 
|  | sync.Mutex | 
|  | path             string | 
|  | vol              uint32 | 
|  | idxhi            uint32 | 
|  | idxlo            uint32 | 
|  | appendNameToPath bool | 
|  | } | 
|  |  | 
|  | // newFileStatFromGetFileInformationByHandle calls GetFileInformationByHandle | 
|  | // to gather all required information about the file handle h. | 
|  | func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (fs *fileStat, err error) { | 
|  | var d syscall.ByHandleFileInformation | 
|  | err = syscall.GetFileInformationByHandle(h, &d) | 
|  | if err != nil { | 
|  | return nil, &PathError{Op: "GetFileInformationByHandle", Path: path, Err: err} | 
|  | } | 
|  |  | 
|  | var reparseTag uint32 | 
|  | if d.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 { | 
|  | var ti windows.FILE_ATTRIBUTE_TAG_INFO | 
|  | err = windows.GetFileInformationByHandleEx(h, windows.FileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti))) | 
|  | if err != nil { | 
|  | return nil, &PathError{Op: "GetFileInformationByHandleEx", Path: path, Err: err} | 
|  | } | 
|  | reparseTag = ti.ReparseTag | 
|  | } | 
|  |  | 
|  | return &fileStat{ | 
|  | name:           filepathlite.Base(path), | 
|  | FileAttributes: d.FileAttributes, | 
|  | CreationTime:   d.CreationTime, | 
|  | LastAccessTime: d.LastAccessTime, | 
|  | LastWriteTime:  d.LastWriteTime, | 
|  | FileSizeHigh:   d.FileSizeHigh, | 
|  | FileSizeLow:    d.FileSizeLow, | 
|  | vol:            d.VolumeSerialNumber, | 
|  | idxhi:          d.FileIndexHigh, | 
|  | idxlo:          d.FileIndexLow, | 
|  | ReparseTag:     reparseTag, | 
|  | // fileStat.path is used by os.SameFile to decide if it needs | 
|  | // to fetch vol, idxhi and idxlo. But these are already set, | 
|  | // so set fileStat.path to "" to prevent os.SameFile doing it again. | 
|  | }, nil | 
|  | } | 
|  |  | 
|  | // newFileStatFromWin32FileAttributeData copies all required information | 
|  | // from syscall.Win32FileAttributeData d into the newly created fileStat. | 
|  | func newFileStatFromWin32FileAttributeData(d *syscall.Win32FileAttributeData) *fileStat { | 
|  | return &fileStat{ | 
|  | FileAttributes: d.FileAttributes, | 
|  | CreationTime:   d.CreationTime, | 
|  | LastAccessTime: d.LastAccessTime, | 
|  | LastWriteTime:  d.LastWriteTime, | 
|  | FileSizeHigh:   d.FileSizeHigh, | 
|  | FileSizeLow:    d.FileSizeLow, | 
|  | } | 
|  | } | 
|  |  | 
|  | // newFileStatFromFileIDBothDirInfo copies all required information | 
|  | // from windows.FILE_ID_BOTH_DIR_INFO d into the newly created fileStat. | 
|  | func newFileStatFromFileIDBothDirInfo(d *windows.FILE_ID_BOTH_DIR_INFO) *fileStat { | 
|  | // The FILE_ID_BOTH_DIR_INFO MSDN documentations isn't completely correct. | 
|  | // FileAttributes can contain any file attributes that is currently set on the file, | 
|  | // not just the ones documented. | 
|  | // EaSize contains the reparse tag if the file is a reparse point. | 
|  | return &fileStat{ | 
|  | FileAttributes: d.FileAttributes, | 
|  | CreationTime:   d.CreationTime, | 
|  | LastAccessTime: d.LastAccessTime, | 
|  | LastWriteTime:  d.LastWriteTime, | 
|  | FileSizeHigh:   uint32(d.EndOfFile >> 32), | 
|  | FileSizeLow:    uint32(d.EndOfFile), | 
|  | ReparseTag:     d.EaSize, | 
|  | idxhi:          uint32(d.FileID >> 32), | 
|  | idxlo:          uint32(d.FileID), | 
|  | } | 
|  | } | 
|  |  | 
|  | // newFileStatFromFileFullDirInfo copies all required information | 
|  | // from windows.FILE_FULL_DIR_INFO d into the newly created fileStat. | 
|  | func newFileStatFromFileFullDirInfo(d *windows.FILE_FULL_DIR_INFO) *fileStat { | 
|  | return &fileStat{ | 
|  | FileAttributes: d.FileAttributes, | 
|  | CreationTime:   d.CreationTime, | 
|  | LastAccessTime: d.LastAccessTime, | 
|  | LastWriteTime:  d.LastWriteTime, | 
|  | FileSizeHigh:   uint32(d.EndOfFile >> 32), | 
|  | FileSizeLow:    uint32(d.EndOfFile), | 
|  | ReparseTag:     d.EaSize, | 
|  | } | 
|  | } | 
|  |  | 
|  | // newFileStatFromWin32finddata copies all required information | 
|  | // from syscall.Win32finddata d into the newly created fileStat. | 
|  | func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat { | 
|  | fs := &fileStat{ | 
|  | FileAttributes: d.FileAttributes, | 
|  | CreationTime:   d.CreationTime, | 
|  | LastAccessTime: d.LastAccessTime, | 
|  | LastWriteTime:  d.LastWriteTime, | 
|  | FileSizeHigh:   d.FileSizeHigh, | 
|  | FileSizeLow:    d.FileSizeLow, | 
|  | } | 
|  | if d.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 { | 
|  | // Per https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw: | 
|  | // “If the dwFileAttributes member includes the FILE_ATTRIBUTE_REPARSE_POINT | 
|  | // attribute, this member specifies the reparse point tag. Otherwise, this | 
|  | // value is undefined and should not be used.” | 
|  | fs.ReparseTag = d.Reserved0 | 
|  | } | 
|  | return fs | 
|  | } | 
|  |  | 
|  | // isReparseTagNameSurrogate determines whether a tag's associated | 
|  | // reparse point is a surrogate for another named entity (for example, a mounted folder). | 
|  | // | 
|  | // See https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-isreparsetagnamesurrogate | 
|  | // and https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-point-tags. | 
|  | func (fs *fileStat) isReparseTagNameSurrogate() bool { | 
|  | // True for IO_REPARSE_TAG_SYMLINK and IO_REPARSE_TAG_MOUNT_POINT. | 
|  | return fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 && fs.ReparseTag&0x20000000 != 0 | 
|  | } | 
|  |  | 
|  | func (fs *fileStat) Size() int64 { | 
|  | return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow) | 
|  | } | 
|  |  | 
|  | var winsymlink = godebug.New("winsymlink") | 
|  |  | 
|  | func (fs *fileStat) Mode() FileMode { | 
|  | m := fs.mode() | 
|  | if winsymlink.Value() == "0" { | 
|  | old := fs.modePreGo1_23() | 
|  | if old != m { | 
|  | winsymlink.IncNonDefault() | 
|  | m = old | 
|  | } | 
|  | } | 
|  | return m | 
|  | } | 
|  |  | 
|  | func (fs *fileStat) mode() (m FileMode) { | 
|  | if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 { | 
|  | m |= 0444 | 
|  | } else { | 
|  | m |= 0666 | 
|  | } | 
|  |  | 
|  | // Windows reports the FILE_ATTRIBUTE_DIRECTORY bit for reparse points | 
|  | // that refer to directories, such as symlinks and mount points. | 
|  | // However, we follow symlink POSIX semantics and do not set the mode bits. | 
|  | // This allows users to walk directories without following links | 
|  | // by just calling "fi, err := os.Lstat(name); err == nil && fi.IsDir()". | 
|  | // Note that POSIX only defines the semantics for symlinks, not for | 
|  | // mount points or other surrogate reparse points, but we treat them | 
|  | // the same way for consistency. Also, mount points can contain infinite | 
|  | // loops, so it is not safe to walk them without special handling. | 
|  | if !fs.isReparseTagNameSurrogate() { | 
|  | if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { | 
|  | m |= ModeDir | 0111 | 
|  | } | 
|  |  | 
|  | switch fs.filetype { | 
|  | case syscall.FILE_TYPE_PIPE: | 
|  | m |= ModeNamedPipe | 
|  | case syscall.FILE_TYPE_CHAR: | 
|  | m |= ModeDevice | ModeCharDevice | 
|  | } | 
|  | } | 
|  |  | 
|  | if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 { | 
|  | switch fs.ReparseTag { | 
|  | case syscall.IO_REPARSE_TAG_SYMLINK: | 
|  | m |= ModeSymlink | 
|  | case windows.IO_REPARSE_TAG_AF_UNIX: | 
|  | m |= ModeSocket | 
|  | case windows.IO_REPARSE_TAG_DEDUP: | 
|  | // If the Data Deduplication service is enabled on Windows Server, its | 
|  | // Optimization job may convert regular files to IO_REPARSE_TAG_DEDUP | 
|  | // whenever that job runs. | 
|  | // | 
|  | // However, DEDUP reparse points remain similar in most respects to | 
|  | // regular files: they continue to support random-access reads and writes | 
|  | // of persistent data, and they shouldn't add unexpected latency or | 
|  | // unavailability in the way that a network filesystem might. | 
|  | // | 
|  | // Go programs may use ModeIrregular to filter out unusual files (such as | 
|  | // raw device files on Linux, POSIX FIFO special files, and so on), so | 
|  | // to avoid files changing unpredictably from regular to irregular we will | 
|  | // consider DEDUP files to be close enough to regular to treat as such. | 
|  | default: | 
|  | m |= ModeIrregular | 
|  | } | 
|  | } | 
|  | return | 
|  | } | 
|  |  | 
|  | // modePreGo1_23 returns the FileMode for the fileStat, using the pre-Go 1.23 | 
|  | // logic for determining the file mode. | 
|  | // The logic is subtle and not well-documented, so it is better to keep it | 
|  | // separate from the new logic. | 
|  | func (fs *fileStat) modePreGo1_23() (m FileMode) { | 
|  | if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 { | 
|  | m |= 0444 | 
|  | } else { | 
|  | m |= 0666 | 
|  | } | 
|  | if fs.ReparseTag == syscall.IO_REPARSE_TAG_SYMLINK || | 
|  | fs.ReparseTag == windows.IO_REPARSE_TAG_MOUNT_POINT { | 
|  | return m | ModeSymlink | 
|  | } | 
|  | if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { | 
|  | m |= ModeDir | 0111 | 
|  | } | 
|  | switch fs.filetype { | 
|  | case syscall.FILE_TYPE_PIPE: | 
|  | m |= ModeNamedPipe | 
|  | case syscall.FILE_TYPE_CHAR: | 
|  | m |= ModeDevice | ModeCharDevice | 
|  | } | 
|  | if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 { | 
|  | if fs.ReparseTag == windows.IO_REPARSE_TAG_AF_UNIX { | 
|  | m |= ModeSocket | 
|  | } | 
|  | if m&ModeType == 0 { | 
|  | if fs.ReparseTag == windows.IO_REPARSE_TAG_DEDUP { | 
|  | // See comment in fs.Mode. | 
|  | } else { | 
|  | m |= ModeIrregular | 
|  | } | 
|  | } | 
|  | } | 
|  | return m | 
|  | } | 
|  |  | 
|  | func (fs *fileStat) ModTime() time.Time { | 
|  | return time.Unix(0, fs.LastWriteTime.Nanoseconds()) | 
|  | } | 
|  |  | 
|  | // Sys returns syscall.Win32FileAttributeData for file fs. | 
|  | func (fs *fileStat) Sys() any { | 
|  | return &syscall.Win32FileAttributeData{ | 
|  | FileAttributes: fs.FileAttributes, | 
|  | CreationTime:   fs.CreationTime, | 
|  | LastAccessTime: fs.LastAccessTime, | 
|  | LastWriteTime:  fs.LastWriteTime, | 
|  | FileSizeHigh:   fs.FileSizeHigh, | 
|  | FileSizeLow:    fs.FileSizeLow, | 
|  | } | 
|  | } | 
|  |  | 
|  | func (fs *fileStat) loadFileId() error { | 
|  | fs.Lock() | 
|  | defer fs.Unlock() | 
|  | if fs.path == "" { | 
|  | // already done | 
|  | return nil | 
|  | } | 
|  | var path string | 
|  | if fs.appendNameToPath { | 
|  | path = fixLongPath(fs.path + `\` + fs.name) | 
|  | } else { | 
|  | path = fs.path | 
|  | } | 
|  | pathp, err := syscall.UTF16PtrFromString(path) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | // Per https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-points-and-file-operations, | 
|  | // “Applications that use the CreateFile function should specify the | 
|  | // FILE_FLAG_OPEN_REPARSE_POINT flag when opening the file if it is a reparse | 
|  | // point.” | 
|  | // | 
|  | // And per https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew, | 
|  | // “If the file is not a reparse point, then this flag is ignored.” | 
|  | // | 
|  | // So we set FILE_FLAG_OPEN_REPARSE_POINT unconditionally, since we want | 
|  | // information about the reparse point itself. | 
|  | // | 
|  | // If the file is a symlink, the symlink target should have already been | 
|  | // resolved when the fileStat was created, so we don't need to worry about | 
|  | // resolving symlink reparse points again here. | 
|  | attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT) | 
|  |  | 
|  | h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | defer syscall.CloseHandle(h) | 
|  | var i syscall.ByHandleFileInformation | 
|  | err = syscall.GetFileInformationByHandle(h, &i) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | fs.path = "" | 
|  | fs.vol = i.VolumeSerialNumber | 
|  | fs.idxhi = i.FileIndexHigh | 
|  | fs.idxlo = i.FileIndexLow | 
|  | return nil | 
|  | } | 
|  |  | 
|  | // saveInfoFromPath saves full path of the file to be used by os.SameFile later, | 
|  | // and set name from path. | 
|  | func (fs *fileStat) saveInfoFromPath(path string) error { | 
|  | fs.path = path | 
|  | if !filepathlite.IsAbs(fs.path) { | 
|  | var err error | 
|  | fs.path, err = syscall.FullPath(fs.path) | 
|  | if err != nil { | 
|  | return &PathError{Op: "FullPath", Path: path, Err: err} | 
|  | } | 
|  | } | 
|  | fs.name = filepathlite.Base(path) | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func sameFile(fs1, fs2 *fileStat) bool { | 
|  | e := fs1.loadFileId() | 
|  | if e != nil { | 
|  | return false | 
|  | } | 
|  | e = fs2.loadFileId() | 
|  | if e != nil { | 
|  | return false | 
|  | } | 
|  | return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo | 
|  | } | 
|  |  | 
|  | // For testing. | 
|  | func atime(fi FileInfo) time.Time { | 
|  | return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds()) | 
|  | } |