-
Notifications
You must be signed in to change notification settings - Fork 387
/
dir_windows.go
154 lines (125 loc) · 3.75 KB
/
dir_windows.go
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
150
151
152
153
154
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
//go:build windows
// +build windows
package filestore
import (
"errors"
"fmt"
"os"
"path/filepath"
"unsafe"
"golang.org/x/sys/windows"
)
var errSharingViolation = windows.Errno(32)
func isBusy(err error) bool {
err = underlyingError(err)
return errors.Is(err, errSharingViolation)
}
func diskInfoFromPath(path string) (info DiskInfo, err error) {
absPath, err := filepath.Abs(path)
if err != nil {
absPath = path
}
var filesystemID string
var availableSpace int64
availableSpace, err = getDiskFreeSpace(absPath)
if err != nil {
return DiskInfo{"", -1}, err
}
filesystemID, err = getVolumeSerialNumber(absPath)
if err != nil {
return DiskInfo{"", availableSpace}, err
}
return DiskInfo{filesystemID, availableSpace}, nil
}
var (
kernel32 = windows.MustLoadDLL("kernel32.dll")
procGetDiskFreeSpace = kernel32.MustFindProc("GetDiskFreeSpaceExW")
)
func getDiskFreeSpace(path string) (int64, error) {
path16, err := windows.UTF16PtrFromString(path)
if err != nil {
return -1, err
}
var bytes int64
_, _, err = procGetDiskFreeSpace.Call(uintptr(unsafe.Pointer(path16)), uintptr(unsafe.Pointer(&bytes)), 0, 0)
err = ignoreSuccess(err)
return bytes, err
}
func getVolumeSerialNumber(path string) (string, error) {
path16, err := windows.UTF16PtrFromString(path)
if err != nil {
return "", err
}
var volumePath [1024]uint16
err = windows.GetVolumePathName(path16, &volumePath[0], uint32(len(volumePath)))
if err != nil {
return "", err
}
var volumeSerial uint32
err = windows.GetVolumeInformation(
&volumePath[0],
nil, 0, // volume name buffer
&volumeSerial,
nil, // maximum component length
nil, // filesystem flags
nil, 0, // filesystem name buffer
)
err = ignoreSuccess(err)
if err != nil {
return "", err
}
return fmt.Sprintf("%08x", volumeSerial), nil
}
// windows api occasionally returns.
func ignoreSuccess(err error) error {
if errors.Is(err, windows.Errno(0)) {
return nil
}
return err
}
// Adds `\\?` prefix to ensure that API recognizes it as a long path.
// see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
func tryFixLongPath(path string) string {
abspath, err := filepath.Abs(path)
if err != nil {
return path
}
return `\\?\` + abspath
}
// rename implements atomic file rename on windows.
func rename(oldpath, newpath string) error {
oldpathp, err := windows.UTF16PtrFromString(tryFixLongPath(oldpath))
if err != nil {
return &os.LinkError{Op: "replace", Old: oldpath, New: newpath, Err: err}
}
newpathp, err := windows.UTF16PtrFromString(tryFixLongPath(newpath))
if err != nil {
return &os.LinkError{Op: "replace", Old: oldpath, New: newpath, Err: err}
}
err = windows.MoveFileEx(oldpathp, newpathp, windows.MOVEFILE_REPLACE_EXISTING|windows.MOVEFILE_WRITE_THROUGH)
if err != nil {
return &os.LinkError{Op: "replace", Old: oldpath, New: newpath, Err: err}
}
return nil
}
// openFileReadOnly opens the file with read only.
// Custom implementation, because os.Open doesn't support specifying FILE_SHARE_DELETE.
func openFileReadOnly(path string, perm os.FileMode) (*os.File, error) {
pathp, err := windows.UTF16PtrFromString(tryFixLongPath(path))
if err != nil {
return nil, err
}
access := uint32(windows.GENERIC_READ)
sharemode := uint32(windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_DELETE)
var sa windows.SecurityAttributes
sa.Length = uint32(unsafe.Sizeof(sa))
sa.InheritHandle = 1
createmode := uint32(windows.OPEN_EXISTING)
handle, err := windows.CreateFile(pathp, access, sharemode, &sa, createmode, windows.FILE_ATTRIBUTE_NORMAL, 0)
if err != nil {
return nil, err
}
return os.NewFile(uintptr(handle), path), nil
}