1package hcs
2
3import (
4	"context"
5	"encoding/json"
6	"errors"
7	"fmt"
8	"net"
9	"syscall"
10
11	"github.com/Microsoft/hcsshim/internal/log"
12)
13
14var (
15	// ErrComputeSystemDoesNotExist is an error encountered when the container being operated on no longer exists
16	ErrComputeSystemDoesNotExist = syscall.Errno(0xc037010e)
17
18	// ErrElementNotFound is an error encountered when the object being referenced does not exist
19	ErrElementNotFound = syscall.Errno(0x490)
20
21	// ErrElementNotFound is an error encountered when the object being referenced does not exist
22	ErrNotSupported = syscall.Errno(0x32)
23
24	// ErrInvalidData is an error encountered when the request being sent to hcs is invalid/unsupported
25	// decimal -2147024883 / hex 0x8007000d
26	ErrInvalidData = syscall.Errno(0xd)
27
28	// ErrHandleClose is an error encountered when the handle generating the notification being waited on has been closed
29	ErrHandleClose = errors.New("hcsshim: the handle generating this notification has been closed")
30
31	// ErrAlreadyClosed is an error encountered when using a handle that has been closed by the Close method
32	ErrAlreadyClosed = errors.New("hcsshim: the handle has already been closed")
33
34	// ErrInvalidNotificationType is an error encountered when an invalid notification type is used
35	ErrInvalidNotificationType = errors.New("hcsshim: invalid notification type")
36
37	// ErrInvalidProcessState is an error encountered when the process is not in a valid state for the requested operation
38	ErrInvalidProcessState = errors.New("the process is in an invalid state for the attempted operation")
39
40	// ErrTimeout is an error encountered when waiting on a notification times out
41	ErrTimeout = errors.New("hcsshim: timeout waiting for notification")
42
43	// ErrUnexpectedContainerExit is the error encountered when a container exits while waiting for
44	// a different expected notification
45	ErrUnexpectedContainerExit = errors.New("unexpected container exit")
46
47	// ErrUnexpectedProcessAbort is the error encountered when communication with the compute service
48	// is lost while waiting for a notification
49	ErrUnexpectedProcessAbort = errors.New("lost communication with compute service")
50
51	// ErrUnexpectedValue is an error encountered when hcs returns an invalid value
52	ErrUnexpectedValue = errors.New("unexpected value returned from hcs")
53
54	// ErrVmcomputeAlreadyStopped is an error encountered when a shutdown or terminate request is made on a stopped container
55	ErrVmcomputeAlreadyStopped = syscall.Errno(0xc0370110)
56
57	// ErrVmcomputeOperationPending is an error encountered when the operation is being completed asynchronously
58	ErrVmcomputeOperationPending = syscall.Errno(0xC0370103)
59
60	// ErrVmcomputeOperationInvalidState is an error encountered when the compute system is not in a valid state for the requested operation
61	ErrVmcomputeOperationInvalidState = syscall.Errno(0xc0370105)
62
63	// ErrProcNotFound is an error encountered when the the process cannot be found
64	ErrProcNotFound = syscall.Errno(0x7f)
65
66	// ErrVmcomputeOperationAccessIsDenied is an error which can be encountered when enumerating compute systems in RS1/RS2
67	// builds when the underlying silo might be in the process of terminating. HCS was fixed in RS3.
68	ErrVmcomputeOperationAccessIsDenied = syscall.Errno(0x5)
69
70	// ErrVmcomputeInvalidJSON is an error encountered when the compute system does not support/understand the messages sent by management
71	ErrVmcomputeInvalidJSON = syscall.Errno(0xc037010d)
72
73	// ErrVmcomputeUnknownMessage is an error encountered guest compute system doesn't support the message
74	ErrVmcomputeUnknownMessage = syscall.Errno(0xc037010b)
75
76	// ErrVmcomputeUnexpectedExit is an error encountered when the compute system terminates unexpectedly
77	ErrVmcomputeUnexpectedExit = syscall.Errno(0xC0370106)
78
79	// ErrNotSupported is an error encountered when hcs doesn't support the request
80	ErrPlatformNotSupported = errors.New("unsupported platform request")
81)
82
83type ErrorEvent struct {
84	Message    string `json:"Message,omitempty"`    // Fully formated error message
85	StackTrace string `json:"StackTrace,omitempty"` // Stack trace in string form
86	Provider   string `json:"Provider,omitempty"`
87	EventID    uint16 `json:"EventId,omitempty"`
88	Flags      uint32 `json:"Flags,omitempty"`
89	Source     string `json:"Source,omitempty"`
90	//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)
91}
92
93type hcsResult struct {
94	Error        int32
95	ErrorMessage string
96	ErrorEvents  []ErrorEvent `json:"ErrorEvents,omitempty"`
97}
98
99func (ev *ErrorEvent) String() string {
100	evs := "[Event Detail: " + ev.Message
101	if ev.StackTrace != "" {
102		evs += " Stack Trace: " + ev.StackTrace
103	}
104	if ev.Provider != "" {
105		evs += " Provider: " + ev.Provider
106	}
107	if ev.EventID != 0 {
108		evs = fmt.Sprintf("%s EventID: %d", evs, ev.EventID)
109	}
110	if ev.Flags != 0 {
111		evs = fmt.Sprintf("%s flags: %d", evs, ev.Flags)
112	}
113	if ev.Source != "" {
114		evs += " Source: " + ev.Source
115	}
116	evs += "]"
117	return evs
118}
119
120func processHcsResult(ctx context.Context, resultJSON string) []ErrorEvent {
121	if resultJSON != "" {
122		result := &hcsResult{}
123		if err := json.Unmarshal([]byte(resultJSON), result); err != nil {
124			log.G(ctx).WithError(err).Warning("Could not unmarshal HCS result")
125			return nil
126		}
127		return result.ErrorEvents
128	}
129	return nil
130}
131
132type HcsError struct {
133	Op     string
134	Err    error
135	Events []ErrorEvent
136}
137
138var _ net.Error = &HcsError{}
139
140func (e *HcsError) Error() string {
141	s := e.Op + ": " + e.Err.Error()
142	for _, ev := range e.Events {
143		s += "\n" + ev.String()
144	}
145	return s
146}
147
148func (e *HcsError) Temporary() bool {
149	err, ok := e.Err.(net.Error)
150	return ok && err.Temporary()
151}
152
153func (e *HcsError) Timeout() bool {
154	err, ok := e.Err.(net.Error)
155	return ok && err.Timeout()
156}
157
158// ProcessError is an error encountered in HCS during an operation on a Process object
159type ProcessError struct {
160	SystemID string
161	Pid      int
162	Op       string
163	Err      error
164	Events   []ErrorEvent
165}
166
167var _ net.Error = &ProcessError{}
168
169// SystemError is an error encountered in HCS during an operation on a Container object
170type SystemError struct {
171	ID     string
172	Op     string
173	Err    error
174	Events []ErrorEvent
175}
176
177var _ net.Error = &SystemError{}
178
179func (e *SystemError) Error() string {
180	s := e.Op + " " + e.ID + ": " + e.Err.Error()
181	for _, ev := range e.Events {
182		s += "\n" + ev.String()
183	}
184	return s
185}
186
187func (e *SystemError) Temporary() bool {
188	err, ok := e.Err.(net.Error)
189	return ok && err.Temporary()
190}
191
192func (e *SystemError) Timeout() bool {
193	err, ok := e.Err.(net.Error)
194	return ok && err.Timeout()
195}
196
197func makeSystemError(system *System, op string, err error, events []ErrorEvent) error {
198	// Don't double wrap errors
199	if _, ok := err.(*SystemError); ok {
200		return err
201	}
202	return &SystemError{
203		ID:     system.ID(),
204		Op:     op,
205		Err:    err,
206		Events: events,
207	}
208}
209
210func (e *ProcessError) Error() string {
211	s := fmt.Sprintf("%s %s:%d: %s", e.Op, e.SystemID, e.Pid, e.Err.Error())
212	for _, ev := range e.Events {
213		s += "\n" + ev.String()
214	}
215	return s
216}
217
218func (e *ProcessError) Temporary() bool {
219	err, ok := e.Err.(net.Error)
220	return ok && err.Temporary()
221}
222
223func (e *ProcessError) Timeout() bool {
224	err, ok := e.Err.(net.Error)
225	return ok && err.Timeout()
226}
227
228func makeProcessError(process *Process, op string, err error, events []ErrorEvent) error {
229	// Don't double wrap errors
230	if _, ok := err.(*ProcessError); ok {
231		return err
232	}
233	return &ProcessError{
234		Pid:      process.Pid(),
235		SystemID: process.SystemID(),
236		Op:       op,
237		Err:      err,
238		Events:   events,
239	}
240}
241
242// IsNotExist checks if an error is caused by the Container or Process not existing.
243// Note: Currently, ErrElementNotFound can mean that a Process has either
244// already exited, or does not exist. Both IsAlreadyStopped and IsNotExist
245// will currently return true when the error is ErrElementNotFound or ErrProcNotFound.
246func IsNotExist(err error) bool {
247	err = getInnerError(err)
248	return err == ErrComputeSystemDoesNotExist ||
249		err == ErrElementNotFound ||
250		err == ErrProcNotFound
251}
252
253// IsAlreadyClosed checks if an error is caused by the Container or Process having been
254// already closed by a call to the Close() method.
255func IsAlreadyClosed(err error) bool {
256	err = getInnerError(err)
257	return err == ErrAlreadyClosed
258}
259
260// IsPending returns a boolean indicating whether the error is that
261// the requested operation is being completed in the background.
262func IsPending(err error) bool {
263	err = getInnerError(err)
264	return err == ErrVmcomputeOperationPending
265}
266
267// IsTimeout returns a boolean indicating whether the error is caused by
268// a timeout waiting for the operation to complete.
269func IsTimeout(err error) bool {
270	if err, ok := err.(net.Error); ok && err.Timeout() {
271		return true
272	}
273	err = getInnerError(err)
274	return err == ErrTimeout
275}
276
277// IsAlreadyStopped returns a boolean indicating whether the error is caused by
278// a Container or Process being already stopped.
279// Note: Currently, ErrElementNotFound can mean that a Process has either
280// already exited, or does not exist. Both IsAlreadyStopped and IsNotExist
281// will currently return true when the error is ErrElementNotFound or ErrProcNotFound.
282func IsAlreadyStopped(err error) bool {
283	err = getInnerError(err)
284	return err == ErrVmcomputeAlreadyStopped ||
285		err == ErrElementNotFound ||
286		err == ErrProcNotFound
287}
288
289// IsNotSupported returns a boolean indicating whether the error is caused by
290// unsupported platform requests
291// Note: Currently Unsupported platform requests can be mean either
292// ErrVmcomputeInvalidJSON, ErrInvalidData, ErrNotSupported or ErrVmcomputeUnknownMessage
293// is thrown from the Platform
294func IsNotSupported(err error) bool {
295	err = getInnerError(err)
296	// If Platform doesn't recognize or support the request sent, below errors are seen
297	return err == ErrVmcomputeInvalidJSON ||
298		err == ErrInvalidData ||
299		err == ErrNotSupported ||
300		err == ErrVmcomputeUnknownMessage
301}
302
303// IsOperationInvalidState returns true when err is caused by
304// `ErrVmcomputeOperationInvalidState`.
305func IsOperationInvalidState(err error) bool {
306	err = getInnerError(err)
307	return err == ErrVmcomputeOperationInvalidState
308}
309
310// IsAccessIsDenied returns true when err is caused by
311// `ErrVmcomputeOperationAccessIsDenied`.
312func IsAccessIsDenied(err error) bool {
313	err = getInnerError(err)
314	return err == ErrVmcomputeOperationAccessIsDenied
315}
316
317func getInnerError(err error) error {
318	switch pe := err.(type) {
319	case nil:
320		return nil
321	case *HcsError:
322		err = pe.Err
323	case *SystemError:
324		err = pe.Err
325	case *ProcessError:
326		err = pe.Err
327	}
328	return err
329}
330