File: ctime_windows.go

package info (click to toggle)
golang-github-djherbis-times 1.6.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 184 kB
  • sloc: sh: 10; makefile: 3
file content (149 lines) | stat: -rw-r--r-- 3,412 bytes parent folder | download | duplicates (2)
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
137
138
139
140
141
142
143
144
145
146
147
148
149
package times

import (
	"os"
	"syscall"
	"time"
	"unsafe"
)

// Stat returns the Timespec for the given filename.
func Stat(name string) (Timespec, error) {
	ts, err := platformSpecficStat(name)
	if err == nil {
		return ts, err
	}

	return stat(name, os.Stat)
}

// Lstat returns the Timespec for the given filename, and does not follow Symlinks.
func Lstat(name string) (Timespec, error) {
	ts, err := platformSpecficLstat(name)
	if err == nil {
		return ts, err
	}

	return stat(name, os.Lstat)
}

type timespecEx struct {
	atime
	mtime
	ctime
	btime
}

// StatFile finds a Windows Timespec with ChangeTime.
func StatFile(file *os.File) (Timespec, error) {
	return statFile(syscall.Handle(file.Fd()))
}

func statFile(h syscall.Handle) (Timespec, error) {
	var fileInfo fileBasicInfo
	if err := getFileInformationByHandleEx(h, &fileInfo); err != nil {
		return nil, err
	}

	var t timespecEx
	t.atime.v = time.Unix(0, fileInfo.LastAccessTime.Nanoseconds())
	t.mtime.v = time.Unix(0, fileInfo.LastWriteTime.Nanoseconds())
	t.ctime.v = time.Unix(0, fileInfo.ChangeTime.Nanoseconds())
	t.btime.v = time.Unix(0, fileInfo.CreationTime.Nanoseconds())
	return t, nil
}

func platformSpecficLstat(name string) (Timespec, error) {
	if findProcErr != nil {
		return nil, findProcErr
	}

	isSym, err := isSymlink(name)
	if err != nil {
		return nil, err
	}

	var attrs = uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
	if isSym {
		attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT
	}

	return openHandleAndStat(name, attrs)
}

func isSymlink(name string) (bool, error) {
	fi, err := os.Lstat(name)
	if err != nil {
		return false, err
	}
	return fi.Mode()&os.ModeSymlink != 0, nil
}

func platformSpecficStat(name string) (Timespec, error) {
	if findProcErr != nil {
		return nil, findProcErr
	}

	return openHandleAndStat(name, syscall.FILE_FLAG_BACKUP_SEMANTICS)
}

func openHandleAndStat(name string, attrs uint32) (Timespec, error) {
	pathp, e := syscall.UTF16PtrFromString(name)
	if e != nil {
		return nil, e
	}
	h, e := syscall.CreateFile(pathp,
		syscall.FILE_WRITE_ATTRIBUTES, syscall.FILE_SHARE_WRITE, nil,
		syscall.OPEN_EXISTING, attrs, 0)
	if e != nil {
		return nil, e
	}
	defer syscall.Close(h)

	return statFile(h)
}

var (
	findProcErr                      error
	procGetFileInformationByHandleEx *syscall.Proc
)

func init() {
	var modkernel32 *syscall.DLL
	if modkernel32, findProcErr = syscall.LoadDLL("kernel32.dll"); findProcErr == nil {
		procGetFileInformationByHandleEx, findProcErr = modkernel32.FindProc("GetFileInformationByHandleEx")
	}
}

// fileBasicInfo holds the C++ data for FileTimes.
//
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa364217(v=vs.85).aspx
type fileBasicInfo struct {
	CreationTime   syscall.Filetime
	LastAccessTime syscall.Filetime
	LastWriteTime  syscall.Filetime
	ChangeTime     syscall.Filetime
	FileAttributes uint32
	_              uint32 // padding
}

type fileInformationClass int

const (
	fileBasicInfoClass fileInformationClass = iota
)

func getFileInformationByHandleEx(handle syscall.Handle, data *fileBasicInfo) (err error) {
	if findProcErr != nil {
		return findProcErr
	}

	r1, _, e1 := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), 4, uintptr(handle), uintptr(fileBasicInfoClass), uintptr(unsafe.Pointer(data)), unsafe.Sizeof(*data), 0, 0)
	if r1 == 0 {
		err = syscall.EINVAL
		if e1 != 0 {
			err = error(e1)
		}
	}
	return
}