summaryrefslogtreecommitdiffstats
path: root/src/os/stat_windows.go
blob: 033c3b9353290cb4f5d1fcc67a8a3ab989dd3a54 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// 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/syscall/windows"
	"syscall"
	"unsafe"
)

// Stat returns the FileInfo structure describing file.
// If there is an error, it will be of type *PathError.
func (file *File) Stat() (FileInfo, error) {
	if file == nil {
		return nil, ErrInvalid
	}
	return statHandle(file.name, file.pfd.Sysfd)
}

// stat implements both Stat and Lstat of a file.
func stat(funcname, name string, followSymlinks bool) (FileInfo, error) {
	if len(name) == 0 {
		return nil, &PathError{Op: funcname, Path: name, Err: syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
	}
	namep, err := syscall.UTF16PtrFromString(fixLongPath(name))
	if err != nil {
		return nil, &PathError{Op: funcname, Path: name, Err: err}
	}

	// Try GetFileAttributesEx first, because it is faster than CreateFile.
	// See https://golang.org/issues/19922#issuecomment-300031421 for details.
	var fa syscall.Win32FileAttributeData
	err = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))

	// GetFileAttributesEx fails with ERROR_SHARING_VIOLATION error for
	// files like c:\pagefile.sys. Use FindFirstFile for such files.
	if err == windows.ERROR_SHARING_VIOLATION {
		var fd syscall.Win32finddata
		sh, err := syscall.FindFirstFile(namep, &fd)
		if err != nil {
			return nil, &PathError{Op: "FindFirstFile", Path: name, Err: err}
		}
		syscall.FindClose(sh)
		if fd.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
			// Not a symlink or mount point. FindFirstFile is good enough.
			fs := newFileStatFromWin32finddata(&fd)
			if err := fs.saveInfoFromPath(name); err != nil {
				return nil, err
			}
			return fs, nil
		}
	}

	if err == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
		// The file is definitely not a symlink, because it isn't any kind of reparse point.
		// The information we got from GetFileAttributesEx is good enough for now.
		fs := &fileStat{
			FileAttributes: fa.FileAttributes,
			CreationTime:   fa.CreationTime,
			LastAccessTime: fa.LastAccessTime,
			LastWriteTime:  fa.LastWriteTime,
			FileSizeHigh:   fa.FileSizeHigh,
			FileSizeLow:    fa.FileSizeLow,
		}
		if err := fs.saveInfoFromPath(name); err != nil {
			return nil, err
		}
		return fs, nil
	}

	// Use CreateFile to determine whether the file is a symlink and, if so,
	// save information about the link target.
	// Set FILE_FLAG_BACKUP_SEMANTICS so that CreateFile will create the handle
	// even if name refers to a directory.
	h, err := syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, 0)
	if err != nil {
		// Since CreateFile failed, we can't determine whether name refers to a
		// symlink, or some other kind of reparse point. Since we can't return a
		// FileInfo with a known-accurate Mode, we must return an error.
		return nil, &PathError{Op: "CreateFile", Path: name, Err: err}
	}

	fi, err := statHandle(name, h)
	syscall.CloseHandle(h)
	if err == nil && followSymlinks && fi.(*fileStat).isSymlink() {
		// To obtain information about the link target, we reopen the file without
		// FILE_FLAG_OPEN_REPARSE_POINT and examine the resulting handle.
		// (See https://devblogs.microsoft.com/oldnewthing/20100212-00/?p=14963.)
		h, err = syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
		if err != nil {
			// name refers to a symlink, but we couldn't resolve the symlink target.
			return nil, &PathError{Op: "CreateFile", Path: name, Err: err}
		}
		defer syscall.CloseHandle(h)
		return statHandle(name, h)
	}
	return fi, err
}

func statHandle(name string, h syscall.Handle) (FileInfo, error) {
	ft, err := syscall.GetFileType(h)
	if err != nil {
		return nil, &PathError{Op: "GetFileType", Path: name, Err: err}
	}
	switch ft {
	case syscall.FILE_TYPE_PIPE, syscall.FILE_TYPE_CHAR:
		return &fileStat{name: basename(name), filetype: ft}, nil
	}
	fs, err := newFileStatFromGetFileInformationByHandle(name, h)
	if err != nil {
		return nil, err
	}
	fs.filetype = ft
	return fs, err
}

// statNolog implements Stat for Windows.
func statNolog(name string) (FileInfo, error) {
	return stat("Stat", name, true)
}

// lstatNolog implements Lstat for Windows.
func lstatNolog(name string) (FileInfo, error) {
	followSymlinks := false
	if name != "" && IsPathSeparator(name[len(name)-1]) {
		// We try to implement POSIX semantics for Lstat path resolution
		// (per https://pubs.opengroup.org/onlinepubs/9699919799.2013edition/basedefs/V1_chap04.html#tag_04_12):
		// symlinks before the last separator in the path must be resolved. Since
		// the last separator in this case follows the last path element, we should
		// follow symlinks in the last path element.
		followSymlinks = true
	}
	return stat("Lstat", name, followSymlinks)
}