Files
2024-11-01 17:43:06 +00:00

330 lines
9.1 KiB
Go

// 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
}
}