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
5package os
6
7import (
8	"internal/filepathlite"
9	"internal/syscall/windows"
10	"syscall"
11	"unsafe"
12)
13
14// Stat returns the [FileInfo] structure describing file.
15// If there is an error, it will be of type [*PathError].
16func (file *File) Stat() (FileInfo, error) {
17	if file == nil {
18		return nil, ErrInvalid
19	}
20	return statHandle(file.name, file.pfd.Sysfd)
21}
22
23// stat implements both Stat and Lstat of a file.
24func stat(funcname, name string, followSurrogates bool) (FileInfo, error) {
25	if len(name) == 0 {
26		return nil, &PathError{Op: funcname, Path: name, Err: syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
27	}
28	namep, err := syscall.UTF16PtrFromString(fixLongPath(name))
29	if err != nil {
30		return nil, &PathError{Op: funcname, Path: name, Err: err}
31	}
32
33	// Try GetFileAttributesEx first, because it is faster than CreateFile.
34	// See https://golang.org/issues/19922#issuecomment-300031421 for details.
35	var fa syscall.Win32FileAttributeData
36	err = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
37	if err == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
38		// Not a surrogate for another named entity, because it isn't any kind of reparse point.
39		// The information we got from GetFileAttributesEx is good enough for now.
40		fs := newFileStatFromWin32FileAttributeData(&fa)
41		if err := fs.saveInfoFromPath(name); err != nil {
42			return nil, err
43		}
44		return fs, nil
45	}
46
47	// GetFileAttributesEx fails with ERROR_SHARING_VIOLATION error for
48	// files like c:\pagefile.sys. Use FindFirstFile for such files.
49	if err == windows.ERROR_SHARING_VIOLATION {
50		var fd syscall.Win32finddata
51		sh, err := syscall.FindFirstFile(namep, &fd)
52		if err != nil {
53			return nil, &PathError{Op: "FindFirstFile", Path: name, Err: err}
54		}
55		syscall.FindClose(sh)
56		if fd.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
57			// Not a surrogate for another named entity. FindFirstFile is good enough.
58			fs := newFileStatFromWin32finddata(&fd)
59			if err := fs.saveInfoFromPath(name); err != nil {
60				return nil, err
61			}
62			return fs, nil
63		}
64	}
65
66	// Use CreateFile to determine whether the file is a name surrogate and, if so,
67	// save information about the link target.
68	// Set FILE_FLAG_BACKUP_SEMANTICS so that CreateFile will create the handle
69	// even if name refers to a directory.
70	var flags uint32 = syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT
71	h, err := syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, flags, 0)
72
73	if err == windows.ERROR_INVALID_PARAMETER {
74		// Console handles, like "\\.\con", require generic read access. See
75		// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew#consoles.
76		// We haven't set it previously because it is normally not required
77		// to read attributes and some files may not allow it.
78		h, err = syscall.CreateFile(namep, syscall.GENERIC_READ, 0, nil, syscall.OPEN_EXISTING, flags, 0)
79	}
80	if err != nil {
81		// Since CreateFile failed, we can't determine whether name refers to a
82		// name surrogate, or some other kind of reparse point. Since we can't return a
83		// FileInfo with a known-accurate Mode, we must return an error.
84		return nil, &PathError{Op: "CreateFile", Path: name, Err: err}
85	}
86
87	fi, err := statHandle(name, h)
88	syscall.CloseHandle(h)
89	if err == nil && followSurrogates && fi.(*fileStat).isReparseTagNameSurrogate() {
90		// To obtain information about the link target, we reopen the file without
91		// FILE_FLAG_OPEN_REPARSE_POINT and examine the resulting handle.
92		// (See https://devblogs.microsoft.com/oldnewthing/20100212-00/?p=14963.)
93		h, err = syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
94		if err != nil {
95			// name refers to a symlink, but we couldn't resolve the symlink target.
96			return nil, &PathError{Op: "CreateFile", Path: name, Err: err}
97		}
98		defer syscall.CloseHandle(h)
99		return statHandle(name, h)
100	}
101	return fi, err
102}
103
104func statHandle(name string, h syscall.Handle) (FileInfo, error) {
105	ft, err := syscall.GetFileType(h)
106	if err != nil {
107		return nil, &PathError{Op: "GetFileType", Path: name, Err: err}
108	}
109	switch ft {
110	case syscall.FILE_TYPE_PIPE, syscall.FILE_TYPE_CHAR:
111		return &fileStat{name: filepathlite.Base(name), filetype: ft}, nil
112	}
113	fs, err := newFileStatFromGetFileInformationByHandle(name, h)
114	if err != nil {
115		return nil, err
116	}
117	fs.filetype = ft
118	return fs, err
119}
120
121// statNolog implements Stat for Windows.
122func statNolog(name string) (FileInfo, error) {
123	return stat("Stat", name, true)
124}
125
126// lstatNolog implements Lstat for Windows.
127func lstatNolog(name string) (FileInfo, error) {
128	followSurrogates := false
129	if name != "" && IsPathSeparator(name[len(name)-1]) {
130		// We try to implement POSIX semantics for Lstat path resolution
131		// (per https://pubs.opengroup.org/onlinepubs/9699919799.2013edition/basedefs/V1_chap04.html#tag_04_12):
132		// symlinks before the last separator in the path must be resolved. Since
133		// the last separator in this case follows the last path element, we should
134		// follow symlinks in the last path element.
135		followSurrogates = true
136	}
137	return stat("Lstat", name, followSurrogates)
138}
139