diff --git a/internal/hcs/callback.go b/internal/hcs/callback.go index 7b27173c3a..5cb7ad077e 100644 --- a/internal/hcs/callback.go +++ b/internal/hcs/callback.go @@ -3,18 +3,38 @@ package hcs import ( + "context" + "encoding/json" "fmt" "sync" "syscall" + "github.com/sirupsen/logrus" + "github.com/Microsoft/hcsshim/internal/interop" + "github.com/Microsoft/hcsshim/internal/log" "github.com/Microsoft/hcsshim/internal/logfields" "github.com/Microsoft/hcsshim/internal/vmcompute" - "github.com/sirupsen/logrus" ) var ( - nextCallback uintptr + // TODO: refactor callback number to be a uint and not a uintptr + + nextCallback callbackCounter + + // `callbackMapLock` is used to protect the map itself, and not the values in the map. + // This causes a race condition with `(*notificationWatcherContext).handle`, + // where, if a computeSystem or process is closed immediately after it is opened, then + // the handle may not be updated with the result from + // `vmcompute.HcsRegister[ComputeSystem|Process]Callback` in `registerCallback` when + // `unregisterCallback` is called. + // Similarly with `(*notificationWatcherContext).channels`, if a `unregisterCallback` + // is called while `waitForNotification` or `notificationWatcher` are processing a notification, + // then the former may close the notification channels after the latter two have read + // (and retain a pointer to) the `notificationWatcherContext`, resulting in a send on a closed channel. + // TODO: use a per-context [RW]Mutex to fix the above scenarios. + + // protected by [callbackMapLock]. callbackMap = map[uintptr]*notificationWatcherContext{} callbackMapLock = sync.RWMutex{} @@ -132,32 +152,78 @@ func closeChannels(channels notificationChannels) { } } -func notificationWatcher(notificationType hcsNotification, callbackNumber uintptr, notificationStatus uintptr, notificationData *uint16) uintptr { - var result error - if int32(notificationStatus) < 0 { - result = interop.Win32FromHresult(notificationStatus) +func notificationWatcher( + notificationType hcsNotification, + callbackNumber uintptr, + notificationStatus uintptr, + notificationData *uint16, +) uintptr { + ctx, entry := log.SetEntry(context.Background(), logrus.Fields{ + "notification-type": notificationType.String(), + }) + + result := processNotification(ctx, notificationStatus, notificationData) + if result != nil { + entry.Data[logrus.ErrorKey] = result } callbackMapLock.RLock() - context := callbackMap[callbackNumber] + callbackCtx := callbackMap[callbackNumber] callbackMapLock.RUnlock() - if context == nil { + if callbackCtx == nil { + entry.WithField("callbackNumber", callbackNumber).Warn("received notification for unknown callback number") return 0 } - log := logrus.WithFields(logrus.Fields{ - "notification-type": notificationType.String(), - "system-id": context.systemID, - }) - if context.processID != 0 { - log.Data[logfields.ProcessID] = context.processID + entry.Data["system-id"] = callbackCtx.systemID + if callbackCtx.processID != 0 { + entry.Data[logfields.ProcessID] = callbackCtx.processID } - log.Debug("HCS notification") + entry.Debug("HCS notification") - if channel, ok := context.channels[notificationType]; ok { + if channel, ok := callbackCtx.channels[notificationType]; ok { channel <- result } return 0 } + +// processNotification parses and validates HCS notifications and returns the result as an error. +func processNotification(ctx context.Context, notificationStatus uintptr, notificationData *uint16) (err error) { + // TODO: merge/unify with [processHcsResult] + status := int32(notificationStatus) + if status < 0 { + err = interop.Win32FromHresult(notificationStatus) + } + + if notificationData == nil { + return err + } + + resultJSON := interop.ConvertAndFreeCoTaskMemString(notificationData) + result := &hcsResult{} + if jsonErr := json.Unmarshal([]byte(resultJSON), result); jsonErr != nil { + log.G(ctx).WithFields(logrus.Fields{ + logfields.JSON: resultJSON, + logrus.ErrorKey: err, + }).Warn("Could not unmarshal HCS result") + return err + } + + // the HResult and data payload should have the same error value + if result.Error < 0 && status < 0 && status != result.Error { + log.G(ctx).WithFields(logrus.Fields{ + "status": status, + "data": result.Error, + }).Warn("Mismatched status and data HResult values") + } + + if len(result.ErrorEvents) > 0 { + return &resultError{ + Err: err, + Events: result.ErrorEvents, + } + } + return err +} diff --git a/internal/hcs/errors.go b/internal/hcs/errors.go index 3e10f5c7e0..b6e1828628 100644 --- a/internal/hcs/errors.go +++ b/internal/hcs/errors.go @@ -8,9 +8,13 @@ import ( "errors" "fmt" "net" + "strings" "syscall" + "github.com/sirupsen/logrus" + "github.com/Microsoft/hcsshim/internal/log" + "github.com/Microsoft/hcsshim/internal/logfields" ) var ( @@ -99,41 +103,57 @@ type ErrorEvent struct { EventID uint16 `json:"EventId,omitempty"` Flags uint32 `json:"Flags,omitempty"` Source string `json:"Source,omitempty"` - //Data []EventData `json:"Data,omitempty"` // Omit this as HCS doesn't encode this well. It's more confusing to include. It is however logged in debug mode (see processHcsResult function) -} -type hcsResult struct { - Error int32 - ErrorMessage string - ErrorEvents []ErrorEvent `json:"ErrorEvents,omitempty"` + // Omit this as HCS doesn't encode this well. It's more confusing to include. + // It is however logged in during [processHcsResult] errors. + //Data []EventData `json:"Data,omitempty"` } func (ev *ErrorEvent) String() string { - evs := "[Event Detail: " + ev.Message + sb := strings.Builder{} + ev.writeTo(&sb) + return sb.String() +} + +func (ev *ErrorEvent) writeTo(b *strings.Builder) { + // rough wag at needed length + b.Grow(64 + len(ev.Message) + len(ev.StackTrace) + len(ev.Provider) + len(ev.Source)) + + // [strings.Builder] Write* functions always return nil errors + _, _ = b.WriteString("[Event Detail: " + ev.Message) if ev.StackTrace != "" { - evs += " Stack Trace: " + ev.StackTrace + _, _ = b.WriteString(" Stack Trace: " + ev.StackTrace) } if ev.Provider != "" { - evs += " Provider: " + ev.Provider + _, _ = b.WriteString(" Provider: " + ev.Provider) } if ev.EventID != 0 { - evs = fmt.Sprintf("%s EventID: %d", evs, ev.EventID) + fmt.Fprintf(b, " EventID: %d", ev.EventID) } if ev.Flags != 0 { - evs = fmt.Sprintf("%s flags: %d", evs, ev.Flags) + fmt.Fprintf(b, " flags: %d", ev.Flags) } if ev.Source != "" { - evs += " Source: " + ev.Source + _, _ = b.WriteString(" Source: " + ev.Source) } - evs += "]" - return evs + _ = b.WriteByte(']') +} + +type hcsResult struct { + Error int32 + ErrorMessage string + ErrorEvents []ErrorEvent `json:"ErrorEvents,omitempty"` + // TODO: AttributionRecords } func processHcsResult(ctx context.Context, resultJSON string) []ErrorEvent { if resultJSON != "" { result := &hcsResult{} if err := json.Unmarshal([]byte(resultJSON), result); err != nil { - log.G(ctx).WithError(err).Warning("Could not unmarshal HCS result") + log.G(ctx).WithFields(logrus.Fields{ + logfields.JSON: resultJSON, + logrus.ErrorKey: err, + }).Warn("Could not unmarshal HCS result") return nil } return result.ErrorEvents @@ -141,6 +161,27 @@ func processHcsResult(ctx context.Context, resultJSON string) []ErrorEvent { return nil } +// resultError allows passing the events along with the error from [notificationWatcher] +// so they can both be including in the resulting [makeSystemError]/[makeProcessError] call. +// +// errorMessage **should** be the same as err.Error(), so it can safely be ignored. +type resultError struct { + Err error + Events []ErrorEvent +} + +func (e *resultError) Error() string { + return appendErrorEvents(e.Err.Error(), e.Events) +} + +func (e *resultError) Is(target error) bool { + return errors.Is(e.Err, target) +} + +func (e *resultError) Unwrap() error { + return e.Err +} + type HcsError struct { Op string Err error @@ -150,11 +191,7 @@ type HcsError struct { var _ net.Error = &HcsError{} func (e *HcsError) Error() string { - s := e.Op + ": " + e.Err.Error() - for _, ev := range e.Events { - s += "\n" + ev.String() - } - return s + return appendErrorEvents(e.Op+": "+e.Err.Error(), e.Events) } func (e *HcsError) Is(target error) bool { @@ -194,11 +231,7 @@ type SystemError struct { var _ net.Error = &SystemError{} func (e *SystemError) Error() string { - s := e.Op + " " + e.ID + ": " + e.Err.Error() - for _, ev := range e.Events { - s += "\n" + ev.String() - } - return s + return appendErrorEvents(e.Op+" "+e.ID+": "+e.Err.Error(), e.Events) } func makeSystemError(system *System, op string, err error, events []ErrorEvent) error { @@ -208,6 +241,7 @@ func makeSystemError(system *System, op string, err error, events []ErrorEvent) return err } + events, err = getEvents(events, err) return &SystemError{ ID: system.ID(), HcsError: HcsError{ @@ -228,11 +262,7 @@ type ProcessError struct { var _ net.Error = &ProcessError{} func (e *ProcessError) Error() string { - s := fmt.Sprintf("%s %s:%d: %s", e.Op, e.SystemID, e.Pid, e.Err.Error()) - for _, ev := range e.Events { - s += "\n" + ev.String() - } - return s + return appendErrorEvents(fmt.Sprintf("%s %s:%d: %s", e.Op, e.SystemID, e.Pid, e.Err.Error()), e.Events) } func makeProcessError(process *Process, op string, err error, events []ErrorEvent) error { @@ -241,6 +271,8 @@ func makeProcessError(process *Process, op string, err error, events []ErrorEven if errors.As(err, &e) { return err } + + events, err = getEvents(events, err) return &ProcessError{ Pid: process.Pid(), SystemID: process.SystemID(), @@ -252,6 +284,34 @@ func makeProcessError(process *Process, op string, err error, events []ErrorEven } } +// getEvents checks to see if err is a [resultError] that has events associated with it, and if so, +// returns the unwrapped error and events. +// This is used to flatten an [SystemError] or [ProcessError] that wraps a [resultError] +func getEvents(events []ErrorEvent, err error) ([]ErrorEvent, error) { + // only return nested events if the original events is empty. + // don't use [errors.As], since that will unwrap the error chain and drop the parents. + if resErr, ok := err.(*resultError); err != nil && ok && len(events) == 0 { //nolint:errorlint + return resErr.Events, resErr.Err + } + return events, err +} + +// common formatting for error strings followed by event data, +func appendErrorEvents(s string, events []ErrorEvent) string { + if len(events) == 0 { + return s + } + + sb := &strings.Builder{} + _, _ = sb.WriteString(s) + for _, ev := range events { + // don't join with newlines since those are ... awkward within error strings + _, _ = sb.WriteString(": ") + ev.writeTo(sb) + } + return sb.String() +} + // IsNotExist checks if an error is caused by the Container or Process not existing. // Note: Currently, ErrElementNotFound can mean that a Process has either // already exited, or does not exist. Both IsAlreadyStopped and IsNotExist diff --git a/internal/hcs/process.go b/internal/hcs/process.go index fef2bf546c..90f9fc2b91 100644 --- a/internal/hcs/process.go +++ b/internal/hcs/process.go @@ -12,6 +12,7 @@ import ( "syscall" "time" + "github.com/sirupsen/logrus" "go.opencensus.io/trace" "github.com/Microsoft/hcsshim/internal/cow" @@ -20,6 +21,7 @@ import ( "github.com/Microsoft/hcsshim/internal/oc" "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" "github.com/Microsoft/hcsshim/internal/vmcompute" + "github.com/Microsoft/hcsshim/internal/winapi" ) type Process struct { @@ -97,7 +99,7 @@ func (process *Process) Signal(ctx context.Context, options interface{}) (bool, operation := "hcs::Process::Signal" - if process.handle == 0 { + if winapi.IsInvalidHandle(process.handle) { return false, makeProcessError(process, operation, ErrAlreadyClosed, nil) } @@ -122,7 +124,7 @@ func (process *Process) Kill(ctx context.Context) (bool, error) { operation := "hcs::Process::Kill" - if process.handle == 0 { + if winapi.IsInvalidHandle(process.handle) { return false, makeProcessError(process, operation, ErrAlreadyClosed, nil) } @@ -287,7 +289,7 @@ func (process *Process) ResizeConsole(ctx context.Context, width, height uint16) operation := "hcs::Process::ResizeConsole" - if process.handle == 0 { + if winapi.IsInvalidHandle(process.handle) { return makeProcessError(process, operation, ErrAlreadyClosed, nil) } modifyRequest := hcsschema.ProcessModifyRequest{ @@ -339,7 +341,7 @@ func (process *Process) StdioLegacy() (_ io.WriteCloser, _ io.ReadCloser, _ io.R process.handleLock.RLock() defer process.handleLock.RUnlock() - if process.handle == 0 { + if winapi.IsInvalidHandle(process.handle) { return nil, nil, nil, makeProcessError(process, operation, ErrAlreadyClosed, nil) } @@ -388,7 +390,7 @@ func (process *Process) CloseStdin(ctx context.Context) (err error) { process.handleLock.RLock() defer process.handleLock.RUnlock() - if process.handle == 0 { + if winapi.IsInvalidHandle(process.handle) { return makeProcessError(process, operation, ErrAlreadyClosed, nil) } @@ -434,7 +436,7 @@ func (process *Process) CloseStdout(ctx context.Context) (err error) { process.handleLock.Lock() defer process.handleLock.Unlock() - if process.handle == 0 { + if winapi.IsInvalidHandle(process.handle) { return nil } @@ -458,7 +460,7 @@ func (process *Process) CloseStderr(ctx context.Context) (err error) { process.handleLock.Lock() defer process.handleLock.Unlock() - if process.handle == 0 { + if winapi.IsInvalidHandle(process.handle) { return nil } @@ -486,7 +488,7 @@ func (process *Process) Close() (err error) { defer process.handleLock.Unlock() // Don't double free this - if process.handle == 0 { + if winapi.IsInvalidHandle(process.handle) { return nil } @@ -524,6 +526,14 @@ func (process *Process) Close() (err error) { } func (process *Process) registerCallback(ctx context.Context) error { + callbackNumber := nextCallback.Inc() + + log.G(ctx).WithFields(logrus.Fields{ + "cid": process.SystemID(), + "pid": process.processID, + "callbackNumber": callbackNumber, + }).Debug("register process callback") + callbackContext := ¬ificationWatcherContext{ channels: newProcessChannels(), systemID: process.SystemID(), @@ -531,8 +541,6 @@ func (process *Process) registerCallback(ctx context.Context) error { } callbackMapLock.Lock() - callbackNumber := nextCallback - nextCallback++ callbackMap[callbackNumber] = callbackContext callbackMapLock.Unlock() @@ -549,6 +557,12 @@ func (process *Process) registerCallback(ctx context.Context) error { func (process *Process) unregisterCallback(ctx context.Context) error { callbackNumber := process.callbackNumber + log.G(ctx).WithFields(logrus.Fields{ + "cid": process.SystemID(), + "pid": process.processID, + "callbackNumber": callbackNumber, + }).Debug("unregister process callback") + callbackMapLock.RLock() callbackContext := callbackMap[callbackNumber] callbackMapLock.RUnlock() @@ -559,7 +573,7 @@ func (process *Process) unregisterCallback(ctx context.Context) error { handle := callbackContext.handle - if handle == 0 { + if winapi.IsInvalidHandle(handle) { return nil } diff --git a/internal/hcs/system.go b/internal/hcs/system.go index b1597466f6..1f2d0c699a 100644 --- a/internal/hcs/system.go +++ b/internal/hcs/system.go @@ -21,6 +21,7 @@ import ( "github.com/Microsoft/hcsshim/internal/oc" "github.com/Microsoft/hcsshim/internal/timeout" "github.com/Microsoft/hcsshim/internal/vmcompute" + "github.com/Microsoft/hcsshim/internal/winapi" "github.com/sirupsen/logrus" "go.opencensus.io/trace" ) @@ -206,7 +207,7 @@ func (computeSystem *System) Start(ctx context.Context) (err error) { // prevent starting an exited system because waitblock we do not recreate waitBlock // or rerun waitBackground, so we have no way to be notified of it closing again - if computeSystem.handle == 0 { + if winapi.IsInvalidHandle(computeSystem.handle) { return makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil) } @@ -232,7 +233,7 @@ func (computeSystem *System) Shutdown(ctx context.Context) error { operation := "hcs::System::Shutdown" - if computeSystem.handle == 0 || computeSystem.stopped() { + if winapi.IsInvalidHandle(computeSystem.handle) || computeSystem.stopped() { return nil } @@ -254,7 +255,7 @@ func (computeSystem *System) Terminate(ctx context.Context) error { operation := "hcs::System::Terminate" - if computeSystem.handle == 0 || computeSystem.stopped() { + if winapi.IsInvalidHandle(computeSystem.handle) || computeSystem.stopped() { return nil } @@ -351,7 +352,7 @@ func (computeSystem *System) Properties(ctx context.Context, types ...schema1.Pr operation := "hcs::System::Properties" - if computeSystem.handle == 0 { + if winapi.IsInvalidHandle(computeSystem.handle) { return nil, makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil) } @@ -492,7 +493,7 @@ func (computeSystem *System) statisticsInProc(job *jobobject.JobObject) (*hcssch func (computeSystem *System) hcsPropertiesV2Query(ctx context.Context, types []hcsschema.PropertyType) (*hcsschema.Properties, error) { operation := "hcs::System::PropertiesV2" - if computeSystem.handle == 0 { + if winapi.IsInvalidHandle(computeSystem.handle) { return nil, makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil) } @@ -586,7 +587,7 @@ func (computeSystem *System) Pause(ctx context.Context) (err error) { computeSystem.handleLock.RLock() defer computeSystem.handleLock.RUnlock() - if computeSystem.handle == 0 { + if winapi.IsInvalidHandle(computeSystem.handle) { return makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil) } @@ -614,7 +615,7 @@ func (computeSystem *System) Resume(ctx context.Context) (err error) { computeSystem.handleLock.RLock() defer computeSystem.handleLock.RUnlock() - if computeSystem.handle == 0 { + if winapi.IsInvalidHandle(computeSystem.handle) { return makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil) } @@ -647,7 +648,7 @@ func (computeSystem *System) Save(ctx context.Context, options interface{}) (err computeSystem.handleLock.RLock() defer computeSystem.handleLock.RUnlock() - if computeSystem.handle == 0 { + if winapi.IsInvalidHandle(computeSystem.handle) { return makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil) } @@ -665,7 +666,7 @@ func (computeSystem *System) createProcess(ctx context.Context, operation string computeSystem.handleLock.RLock() defer computeSystem.handleLock.RUnlock() - if computeSystem.handle == 0 { + if winapi.IsInvalidHandle(computeSystem.handle) { return nil, nil, makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil) } @@ -727,7 +728,7 @@ func (computeSystem *System) OpenProcess(ctx context.Context, pid int) (*Process operation := "hcs::System::OpenProcess" - if computeSystem.handle == 0 { + if winapi.IsInvalidHandle(computeSystem.handle) { return nil, makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil) } @@ -766,7 +767,7 @@ func (computeSystem *System) CloseCtx(ctx context.Context) (err error) { defer computeSystem.handleLock.Unlock() // Don't double free this - if computeSystem.handle == 0 { + if winapi.IsInvalidHandle(computeSystem.handle) { return nil } @@ -789,14 +790,19 @@ func (computeSystem *System) CloseCtx(ctx context.Context) (err error) { } func (computeSystem *System) registerCallback(ctx context.Context) error { + callbackNumber := nextCallback.Inc() + + log.G(ctx).WithFields(logrus.Fields{ + "cid": computeSystem.id, + "callbackNumber": callbackNumber, + }).Debug("register computer system callback") + callbackContext := ¬ificationWatcherContext{ channels: newSystemChannels(), systemID: computeSystem.id, } callbackMapLock.Lock() - callbackNumber := nextCallback - nextCallback++ callbackMap[callbackNumber] = callbackContext callbackMapLock.Unlock() @@ -814,6 +820,11 @@ func (computeSystem *System) registerCallback(ctx context.Context) error { func (computeSystem *System) unregisterCallback(ctx context.Context) error { callbackNumber := computeSystem.callbackNumber + log.G(ctx).WithFields(logrus.Fields{ + "cid": computeSystem.id, + "callbackNumber": callbackNumber, + }).Debug("unregister computer system callback") + callbackMapLock.RLock() callbackContext := callbackMap[callbackNumber] callbackMapLock.RUnlock() @@ -824,7 +835,7 @@ func (computeSystem *System) unregisterCallback(ctx context.Context) error { handle := callbackContext.handle - if handle == 0 { + if winapi.IsInvalidHandle(handle) { return nil } @@ -853,7 +864,7 @@ func (computeSystem *System) Modify(ctx context.Context, config interface{}) err operation := "hcs::System::Modify" - if computeSystem.handle == 0 { + if winapi.IsInvalidHandle(computeSystem.handle) { return makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil) } diff --git a/internal/hcs/utils.go b/internal/hcs/utils.go index 76eb2be7cf..65d961706f 100644 --- a/internal/hcs/utils.go +++ b/internal/hcs/utils.go @@ -5,13 +5,17 @@ package hcs import ( "context" "io" + "sync/atomic" "syscall" + "github.com/pkg/errors" + "golang.org/x/sys/windows" + "github.com/Microsoft/go-winio" diskutil "github.com/Microsoft/go-winio/vhd" + "github.com/Microsoft/hcsshim/computestorage" - "github.com/pkg/errors" - "golang.org/x/sys/windows" + "github.com/Microsoft/hcsshim/internal/winapi" ) // makeOpenFiles calls winio.NewOpenFile for each handle in a slice but closes all the handles @@ -19,7 +23,7 @@ import ( func makeOpenFiles(hs []syscall.Handle) (_ []io.ReadWriteCloser, err error) { fs := make([]io.ReadWriteCloser, len(hs)) for i, h := range hs { - if h != syscall.Handle(0) { + if !winapi.IsInvalidHandle(h) { if err == nil { fs[i], err = winio.NewOpenFile(windows.Handle(h)) } @@ -62,3 +66,18 @@ func CreateNTFSVHD(ctx context.Context, vhdPath string, sizeGB uint32) (err erro return nil } + +// TODO: move these to a dedicated `internal/sync` package + +type callbackCounter struct { + v atomic.Uintptr +} + +func (c *callbackCounter) Inc() uintptr { return (&c.v).Add(1) } + +// noCopy prevents structs from being copied. +// See: https://golang.org/issues/8005#issuecomment-190753527 +type noCopy struct{} + +func (*noCopy) Lock() {} +func (*noCopy) Unlock() {} diff --git a/internal/hcs/waithelper.go b/internal/hcs/waithelper.go index 3a51ed1955..d4c420759f 100644 --- a/internal/hcs/waithelper.go +++ b/internal/hcs/waithelper.go @@ -32,13 +32,14 @@ func waitForNotification( timeout *time.Duration, ) error { callbackMapLock.RLock() - if _, ok := callbackMap[callbackNumber]; !ok { - callbackMapLock.RUnlock() + callbackCtx, ok := callbackMap[callbackNumber] + callbackMapLock.RUnlock() + + if !ok { log.G(ctx).WithField("callbackNumber", callbackNumber).Error("failed to waitForNotification: callbackNumber does not exist in callbackMap") return ErrHandleClose } - channels := callbackMap[callbackNumber].channels - callbackMapLock.RUnlock() + channels := callbackCtx.channels expectedChannel := channels[expectedNotification] if expectedChannel == nil { diff --git a/internal/winapi/doc.go b/internal/winapi/doc.go index 9acc0bfc17..0bfaf47a33 100644 --- a/internal/winapi/doc.go +++ b/internal/winapi/doc.go @@ -1,3 +1,5 @@ // Package winapi contains various low-level bindings to Windows APIs. It can // be thought of as an extension to golang.org/x/sys/windows. package winapi + +//go:generate go tool github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go ./*.go diff --git a/internal/winapi/utils.go b/internal/winapi/utils.go index de16750d76..8372560900 100644 --- a/internal/winapi/utils.go +++ b/internal/winapi/utils.go @@ -10,6 +10,11 @@ import ( "golang.org/x/sys/windows" ) +// IsInvalidHandle returns true if the Handle is zero or [windows.InvalidHandle]. +func IsInvalidHandle[H ~uintptr](h H) bool { + return h == 0 || uintptr(h) == uintptr(windows.InvalidHandle) +} + // Uint16BufferToSlice wraps a uint16 pointer-and-length into a slice // for easier interop with Go APIs func Uint16BufferToSlice(buffer *uint16, bufferLength int) (result []uint16) { diff --git a/internal/winapi/winapi.go b/internal/winapi/winapi.go deleted file mode 100644 index 009e70ab19..0000000000 --- a/internal/winapi/winapi.go +++ /dev/null @@ -1,3 +0,0 @@ -package winapi - -//go:generate go tool github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go ./*.go