-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy patherror.go
329 lines (271 loc) · 9.12 KB
/
error.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
// Copyright (c) 2022 Tailscale Inc & AUTHORS. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build windows
package wingoes
import (
"fmt"
"golang.org/x/sys/windows"
)
// Error represents various error codes that may be encountered when coding
// against Windows APIs, including HRESULTs, windows.NTStatus, and windows.Errno.
type Error HRESULT
// Errors are HRESULTs under the hood because the HRESULT encoding allows for
// all the other common types of Windows errors to be encoded within them.
const (
hrS_OK = HRESULT(0)
hrE_ABORT = HRESULT(-((0x80004004 ^ 0xFFFFFFFF) + 1))
hrE_FAIL = HRESULT(-((0x80004005 ^ 0xFFFFFFFF) + 1))
hrE_NOINTERFACE = HRESULT(-((0x80004002 ^ 0xFFFFFFFF) + 1))
hrE_NOTIMPL = HRESULT(-((0x80004001 ^ 0xFFFFFFFF) + 1))
hrE_POINTER = HRESULT(-((0x80004003 ^ 0xFFFFFFFF) + 1))
hrE_UNEXPECTED = HRESULT(-((0x8000FFFF ^ 0xFFFFFFFF) + 1))
hrTYPE_E_WRONGTYPEKIND = HRESULT(-((0x8002802A ^ 0xFFFFFFFF) + 1))
)
// S_FALSE is a peculiar HRESULT value which means that the call executed
// successfully, but returned false as its result.
const S_FALSE = HRESULT(1)
var (
// genericError encodes an Error whose message string is very generic.
genericError = Error(hresultFromFacilityAndCode(hrFail, facilityWin32, hrCode(windows.ERROR_UNIDENTIFIED_ERROR)))
)
// Common HRESULT codes that don't use Win32 facilities, but have meanings that
// we can manually translate to Win32 error codes.
var commonHRESULTToErrno = map[HRESULT]windows.Errno{
hrE_ABORT: windows.ERROR_REQUEST_ABORTED,
hrE_FAIL: windows.ERROR_UNIDENTIFIED_ERROR,
hrE_NOINTERFACE: windows.ERROR_NOINTERFACE,
hrE_NOTIMPL: windows.ERROR_CALL_NOT_IMPLEMENTED,
hrE_UNEXPECTED: windows.ERROR_INTERNAL_ERROR,
}
type hrCode uint16
type hrFacility uint16
type failBit bool
const (
hrFlagBitsMask = 0xF8000000
hrFacilityMax = 0x00001FFF
hrFacilityMask = hrFacilityMax << 16
hrCodeMax = 0x0000FFFF
hrCodeMask = hrCodeMax
hrFailBit = 0x80000000
hrCustomerBit = 0x20000000 // Also defined as syscall.APPLICATION_ERROR
hrFacilityNTBit = 0x10000000
)
const (
facilityWin32 = hrFacility(7)
)
// Succeeded returns true when hr is successful, but its actual error code
// may include additional status information.
func (hr HRESULT) Succeeded() bool {
return hr >= 0
}
// Failed returns true when hr contains a failure code.
func (hr HRESULT) Failed() bool {
return hr < 0
}
func (hr HRESULT) String() string {
return fmt.Sprintf("0x%08X", uint32(hr))
}
func (hr HRESULT) isNT() bool {
return (hr & (hrCustomerBit | hrFacilityNTBit)) == hrFacilityNTBit
}
func (hr HRESULT) isCustomer() bool {
return (hr & hrCustomerBit) != 0
}
// isNormal returns true when the customer and NT bits are cleared, ie hr's
// encoding contains valid facility and code fields.
func (hr HRESULT) isNormal() bool {
return (hr & (hrCustomerBit | hrFacilityNTBit)) == 0
}
// facility returns the facility bits of hr. Only valid when isNormal is true.
func (hr HRESULT) facility() hrFacility {
return hrFacility((uint32(hr) >> 16) & hrFacilityMax)
}
// facility returns the code bits of hr. Only valid when isNormal is true.
func (hr HRESULT) code() hrCode {
return hrCode(uint32(hr) & hrCodeMask)
}
const (
hrFail = failBit(true)
hrSuccess = failBit(false)
)
func hresultFromFacilityAndCode(isFail failBit, f hrFacility, c hrCode) HRESULT {
var r uint32
if isFail {
r |= hrFailBit
}
r |= (uint32(f) << 16) & hrFacilityMask
r |= uint32(c) & hrCodeMask
return HRESULT(r)
}
// ErrorFromErrno creates an Error from e.
func ErrorFromErrno(e windows.Errno) Error {
if e == windows.ERROR_SUCCESS {
return Error(hrS_OK)
}
if ue := uint32(e); (ue & hrFlagBitsMask) == hrCustomerBit {
// syscall.APPLICATION_ERROR == hrCustomerBit, so the only other thing
// we need to do to transform this into an HRESULT is add the fail flag
return Error(HRESULT(ue | hrFailBit))
}
if uint32(e) > hrCodeMax {
// Can't be encoded in HRESULT, return generic error instead
return genericError
}
return Error(hresultFromFacilityAndCode(hrFail, facilityWin32, hrCode(e)))
}
// ErrorFromNTStatus creates an Error from s.
func ErrorFromNTStatus(s windows.NTStatus) Error {
if s == windows.STATUS_SUCCESS {
return Error(hrS_OK)
}
return Error(HRESULT(s) | hrFacilityNTBit)
}
// ErrorFromHRESULT creates an Error from hr.
func ErrorFromHRESULT(hr HRESULT) Error {
return Error(hr)
}
// NewError converts e into an Error if e's type is supported. It returns
// both the Error and a bool indicating whether the conversion was successful.
func NewError(e any) (Error, bool) {
switch v := e.(type) {
case Error:
return v, true
case windows.NTStatus:
return ErrorFromNTStatus(v), true
case windows.Errno:
return ErrorFromErrno(v), true
case HRESULT:
return ErrorFromHRESULT(v), true
default:
return ErrorFromHRESULT(hrTYPE_E_WRONGTYPEKIND), false
}
}
// IsOK returns true when the Error is unconditionally successful.
func (e Error) IsOK() bool {
return HRESULT(e) == hrS_OK
}
// Succeeded returns true when the Error is successful, but its error code
// may include additional status information.
func (e Error) Succeeded() bool {
return HRESULT(e).Succeeded()
}
// Failed returns true when the Error contains a failure code.
func (e Error) Failed() bool {
return HRESULT(e).Failed()
}
// AsHRESULT converts the Error to a HRESULT.
func (e Error) AsHRESULT() HRESULT {
return HRESULT(e)
}
type errnoFailHandler func(hr HRESULT) windows.Errno
func (e Error) toErrno(f errnoFailHandler) windows.Errno {
hr := HRESULT(e)
if hr == hrS_OK {
return windows.ERROR_SUCCESS
}
if hr.isCustomer() {
return windows.Errno(uint32(e) ^ hrFailBit)
}
if hr.isNT() {
return e.AsNTStatus().Errno()
}
if hr.facility() == facilityWin32 {
return windows.Errno(hr.code())
}
if errno, ok := commonHRESULTToErrno[hr]; ok {
return errno
}
return f(hr)
}
// AsError converts the Error to a windows.Errno, but panics if not possible.
func (e Error) AsErrno() windows.Errno {
handler := func(hr HRESULT) windows.Errno {
panic(fmt.Sprintf("wingoes.Error: Called AsErrno on a non-convertable HRESULT 0x%08X", uint32(hr)))
return windows.ERROR_UNIDENTIFIED_ERROR
}
return e.toErrno(handler)
}
type ntStatusFailHandler func(hr HRESULT) windows.NTStatus
func (e Error) toNTStatus(f ntStatusFailHandler) windows.NTStatus {
hr := HRESULT(e)
if hr == hrS_OK {
return windows.STATUS_SUCCESS
}
if hr.isNT() {
return windows.NTStatus(hr ^ hrFacilityNTBit)
}
return f(hr)
}
// AsNTStatus converts the Error to a windows.NTStatus, but panics if not possible.
func (e Error) AsNTStatus() windows.NTStatus {
handler := func(hr HRESULT) windows.NTStatus {
panic(fmt.Sprintf("windows.Error: Called AsNTStatus on a non-NTSTATUS HRESULT 0x%08X", uint32(hr)))
return windows.STATUS_UNSUCCESSFUL
}
return e.toNTStatus(handler)
}
// TryAsErrno converts the Error to a windows.Errno, or returns defval if
// such a conversion is not possible.
func (e Error) TryAsErrno(defval windows.Errno) windows.Errno {
handler := func(hr HRESULT) windows.Errno {
return defval
}
return e.toErrno(handler)
}
// TryAsNTStatus converts the Error to a windows.NTStatus, or returns defval if
// such a conversion is not possible.
func (e Error) TryAsNTStatus(defval windows.NTStatus) windows.NTStatus {
handler := func(hr HRESULT) windows.NTStatus {
return defval
}
return e.toNTStatus(handler)
}
// IsAvailableAsHRESULT returns true if e may be converted to an HRESULT.
func (e Error) IsAvailableAsHRESULT() bool {
return true
}
// IsAvailableAsErrno returns true if e may be converted to a windows.Errno.
func (e Error) IsAvailableAsErrno() bool {
hr := HRESULT(e)
if hr.isCustomer() || e.IsAvailableAsNTStatus() || (hr.facility() == facilityWin32) {
return true
}
_, convertable := commonHRESULTToErrno[hr]
return convertable
}
// IsAvailableAsNTStatus returns true if e may be converted to a windows.NTStatus.
func (e Error) IsAvailableAsNTStatus() bool {
return HRESULT(e) == hrS_OK || HRESULT(e).isNT()
}
// Error produces a human-readable message describing Error e.
func (e Error) Error() string {
if HRESULT(e).isCustomer() {
return windows.Errno(uint32(e) ^ hrFailBit).Error()
}
buf := make([]uint16, 300)
const flags = windows.FORMAT_MESSAGE_FROM_SYSTEM | windows.FORMAT_MESSAGE_IGNORE_INSERTS
lenExclNul, err := windows.FormatMessage(flags, 0, uint32(e), 0, buf, nil)
if err != nil {
return fmt.Sprintf("wingoes.Error 0x%08X", uint32(e))
}
for ; lenExclNul > 0 && (buf[lenExclNul-1] == '\n' || buf[lenExclNul-1] == '\r'); lenExclNul-- {
}
return windows.UTF16ToString(buf[:lenExclNul])
}
// Unwrap permits extraction of underlying windows.NTStatus or windows.Errno
// errors that are encoded within e.
func (e Error) Unwrap() error {
// Order is important! We need earlier checks to exclude certain things that
// would otherwise be (in this case) false positives in later checks!
switch {
case e.IsOK():
return nil
case e.IsAvailableAsNTStatus():
return e.AsNTStatus()
case e.IsAvailableAsErrno():
return e.AsErrno()
default:
return nil
}
}