//go:build windows package hcs import ( "context" "encoding/json" "errors" "fmt" "net" "syscall" "github.com/Microsoft/hcsshim/internal/log" ) var ( // ErrComputeSystemDoesNotExist is an error encountered when the container being operated on no longer exists ErrComputeSystemDoesNotExist = syscall.Errno(0xc037010e) // ErrElementNotFound is an error encountered when the object being referenced does not exist ErrElementNotFound = syscall.Errno(0x490) // ErrElementNotFound is an error encountered when the object being referenced does not exist ErrNotSupported = syscall.Errno(0x32) // ErrInvalidData is an error encountered when the request being sent to hcs is invalid/unsupported // decimal -2147024883 / hex 0x8007000d ErrInvalidData = syscall.Errno(0xd) // ErrHandleClose is an error encountered when the handle generating the notification being waited on has been closed ErrHandleClose = errors.New("hcsshim: the handle generating this notification has been closed") // ErrAlreadyClosed is an error encountered when using a handle that has been closed by the Close method ErrAlreadyClosed = errors.New("hcsshim: the handle has already been closed") // ErrInvalidNotificationType is an error encountered when an invalid notification type is used ErrInvalidNotificationType = errors.New("hcsshim: invalid notification type") // ErrInvalidProcessState is an error encountered when the process is not in a valid state for the requested operation ErrInvalidProcessState = errors.New("the process is in an invalid state for the attempted operation") // ErrTimeout is an error encountered when waiting on a notification times out ErrTimeout = errors.New("hcsshim: timeout waiting for notification") // ErrUnexpectedContainerExit is the error encountered when a container exits while waiting for // a different expected notification ErrUnexpectedContainerExit = errors.New("unexpected container exit") // ErrUnexpectedProcessAbort is the error encountered when communication with the compute service // is lost while waiting for a notification ErrUnexpectedProcessAbort = errors.New("lost communication with compute service") // ErrUnexpectedValue is an error encountered when hcs returns an invalid value ErrUnexpectedValue = errors.New("unexpected value returned from hcs") // ErrOperationDenied is an error when hcs attempts an operation that is explicitly denied ErrOperationDenied = errors.New("operation denied") // ErrVmcomputeAlreadyStopped is an error encountered when a shutdown or terminate request is made on a stopped container ErrVmcomputeAlreadyStopped = syscall.Errno(0xc0370110) // ErrVmcomputeOperationPending is an error encountered when the operation is being completed asynchronously ErrVmcomputeOperationPending = syscall.Errno(0xC0370103) // ErrVmcomputeOperationInvalidState is an error encountered when the compute system is not in a valid state for the requested operation ErrVmcomputeOperationInvalidState = syscall.Errno(0xc0370105) // ErrProcNotFound is an error encountered when a procedure look up fails. ErrProcNotFound = syscall.Errno(0x7f) // ErrVmcomputeOperationAccessIsDenied is an error which can be encountered when enumerating compute systems in RS1/RS2 // builds when the underlying silo might be in the process of terminating. HCS was fixed in RS3. ErrVmcomputeOperationAccessIsDenied = syscall.Errno(0x5) // ErrVmcomputeInvalidJSON is an error encountered when the compute system does not support/understand the messages sent by management ErrVmcomputeInvalidJSON = syscall.Errno(0xc037010d) // ErrVmcomputeUnknownMessage is an error encountered guest compute system doesn't support the message ErrVmcomputeUnknownMessage = syscall.Errno(0xc037010b) // ErrVmcomputeUnexpectedExit is an error encountered when the compute system terminates unexpectedly ErrVmcomputeUnexpectedExit = syscall.Errno(0xC0370106) // ErrNotSupported is an error encountered when hcs doesn't support the request ErrPlatformNotSupported = errors.New("unsupported platform request") // ErrProcessAlreadyStopped is returned by hcs if the process we're trying to kill has already been stopped. ErrProcessAlreadyStopped = syscall.Errno(0x8037011f) // ErrInvalidHandle is an error that can be encountered when querying the properties of a compute system when the handle to that // compute system has already been closed. ErrInvalidHandle = syscall.Errno(0x6) ) type ErrorEvent struct { Message string `json:"Message,omitempty"` // Fully formated error message StackTrace string `json:"StackTrace,omitempty"` // Stack trace in string form Provider string `json:"Provider,omitempty"` 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"` } func (ev *ErrorEvent) String() string { evs := "[Event Detail: " + ev.Message if ev.StackTrace != "" { evs += " Stack Trace: " + ev.StackTrace } if ev.Provider != "" { evs += " Provider: " + ev.Provider } if ev.EventID != 0 { evs = fmt.Sprintf("%s EventID: %d", evs, ev.EventID) } if ev.Flags != 0 { evs = fmt.Sprintf("%s flags: %d", evs, ev.Flags) } if ev.Source != "" { evs += " Source: " + ev.Source } evs += "]" return evs } 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") return nil } return result.ErrorEvents } return nil } type HcsError struct { Op string Err error Events []ErrorEvent } 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 } func (e *HcsError) Is(target error) bool { return errors.Is(e.Err, target) } // unwrap isnt really needed, but helpful convince function func (e *HcsError) Unwrap() error { return e.Err } // Deprecated: net.Error.Temporary is deprecated. func (e *HcsError) Temporary() bool { err := e.netError() return (err != nil) && err.Temporary() } func (e *HcsError) Timeout() bool { err := e.netError() return (err != nil) && err.Timeout() } func (e *HcsError) netError() (err net.Error) { if errors.As(e.Unwrap(), &err) { return err } return nil } // SystemError is an error encountered in HCS during an operation on a Container object type SystemError struct { HcsError ID string } 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 } func makeSystemError(system *System, op string, err error, events []ErrorEvent) error { // Don't double wrap errors var e *SystemError if errors.As(err, &e) { return err } return &SystemError{ ID: system.ID(), HcsError: HcsError{ Op: op, Err: err, Events: events, }, } } // ProcessError is an error encountered in HCS during an operation on a Process object type ProcessError struct { HcsError SystemID string Pid int } 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 } func makeProcessError(process *Process, op string, err error, events []ErrorEvent) error { // Don't double wrap errors var e *ProcessError if errors.As(err, &e) { return err } return &ProcessError{ Pid: process.Pid(), SystemID: process.SystemID(), HcsError: HcsError{ Op: op, Err: err, Events: events, }, } } // 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 // will currently return true when the error is ErrElementNotFound. func IsNotExist(err error) bool { return IsAny(err, ErrComputeSystemDoesNotExist, ErrElementNotFound) } // IsErrorInvalidHandle checks whether the error is the result of an operation carried // out on a handle that is invalid/closed. This error popped up while trying to query // stats on a container in the process of being stopped. func IsErrorInvalidHandle(err error) bool { return errors.Is(err, ErrInvalidHandle) } // IsAlreadyClosed checks if an error is caused by the Container or Process having been // already closed by a call to the Close() method. func IsAlreadyClosed(err error) bool { return errors.Is(err, ErrAlreadyClosed) } // IsPending returns a boolean indicating whether the error is that // the requested operation is being completed in the background. func IsPending(err error) bool { return errors.Is(err, ErrVmcomputeOperationPending) } // IsTimeout returns a boolean indicating whether the error is caused by // a timeout waiting for the operation to complete. func IsTimeout(err error) bool { // HcsError and co. implement Timeout regardless of whether the errors they wrap do, // so `errors.As(err, net.Error)`` will always be true. // Using `errors.As(err.Unwrap(), net.Err)` wont work for general errors. // So first check if there an `ErrTimeout` in the chain, then convert to a net error. if errors.Is(err, ErrTimeout) { return true } var nerr net.Error return errors.As(err, &nerr) && nerr.Timeout() } // IsAlreadyStopped returns a boolean indicating whether the error is caused by // a Container or Process being already stopped. // Note: Currently, ErrElementNotFound can mean that a Process has either // already exited, or does not exist. Both IsAlreadyStopped and IsNotExist // will currently return true when the error is ErrElementNotFound. func IsAlreadyStopped(err error) bool { return IsAny(err, ErrVmcomputeAlreadyStopped, ErrProcessAlreadyStopped, ErrElementNotFound) } // IsNotSupported returns a boolean indicating whether the error is caused by // unsupported platform requests // Note: Currently Unsupported platform requests can be mean either // ErrVmcomputeInvalidJSON, ErrInvalidData, ErrNotSupported or ErrVmcomputeUnknownMessage // is thrown from the Platform func IsNotSupported(err error) bool { // If Platform doesn't recognize or support the request sent, below errors are seen return IsAny(err, ErrVmcomputeInvalidJSON, ErrInvalidData, ErrNotSupported, ErrVmcomputeUnknownMessage) } // IsOperationInvalidState returns true when err is caused by // `ErrVmcomputeOperationInvalidState`. func IsOperationInvalidState(err error) bool { return errors.Is(err, ErrVmcomputeOperationInvalidState) } // IsAccessIsDenied returns true when err is caused by // `ErrVmcomputeOperationAccessIsDenied`. func IsAccessIsDenied(err error) bool { return errors.Is(err, ErrVmcomputeOperationAccessIsDenied) } // IsAny is a vectorized version of [errors.Is], it returns true if err is one of targets. func IsAny(err error, targets ...error) bool { for _, e := range targets { if errors.Is(err, e) { return true } } return false }