-
Notifications
You must be signed in to change notification settings - Fork 285
Expand file tree
/
Copy pathcallback.go
More file actions
244 lines (210 loc) · 7.6 KB
/
callback.go
File metadata and controls
244 lines (210 loc) · 7.6 KB
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
//go:build windows
package hcs
import (
"context"
"encoding/json"
"fmt"
"sync"
"sync/atomic"
"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"
)
var (
// TODO: don't delete notification contexts on close, so callback can handle delayed notifications
// used to lock [callbackMap].
callbackMapLock = sync.RWMutex{}
callbackMap = map[callbackNumber]*notificationWatcherContext{}
notificationWatcherCallback = syscall.NewCallback(notificationWatcher)
// Notifications for HCS_SYSTEM handles
hcsNotificationSystemExited hcsNotification = 0x00000001
hcsNotificationSystemCreateCompleted hcsNotification = 0x00000002
hcsNotificationSystemStartCompleted hcsNotification = 0x00000003
hcsNotificationSystemPauseCompleted hcsNotification = 0x00000004
hcsNotificationSystemResumeCompleted hcsNotification = 0x00000005
hcsNotificationSystemCrashReport hcsNotification = 0x00000006
hcsNotificationSystemSiloJobCreated hcsNotification = 0x00000007
hcsNotificationSystemSaveCompleted hcsNotification = 0x00000008
hcsNotificationSystemRdpEnhancedModeStateChanged hcsNotification = 0x00000009
hcsNotificationSystemShutdownFailed hcsNotification = 0x0000000A
hcsNotificationSystemGetPropertiesCompleted hcsNotification = 0x0000000B
hcsNotificationSystemModifyCompleted hcsNotification = 0x0000000C
hcsNotificationSystemCrashInitiated hcsNotification = 0x0000000D
hcsNotificationSystemGuestConnectionClosed hcsNotification = 0x0000000E
// Notifications for HCS_PROCESS handles
hcsNotificationProcessExited hcsNotification = 0x00010000
// Common notifications
hcsNotificationInvalid hcsNotification = 0x00000000
hcsNotificationServiceDisconnect hcsNotification = 0x01000000
)
type hcsNotification uint32
func (hn hcsNotification) String() string {
switch hn {
case hcsNotificationSystemExited:
return "SystemExited"
case hcsNotificationSystemCreateCompleted:
return "SystemCreateCompleted"
case hcsNotificationSystemStartCompleted:
return "SystemStartCompleted"
case hcsNotificationSystemPauseCompleted:
return "SystemPauseCompleted"
case hcsNotificationSystemResumeCompleted:
return "SystemResumeCompleted"
case hcsNotificationSystemCrashReport:
return "SystemCrashReport"
case hcsNotificationSystemSiloJobCreated:
return "SystemSiloJobCreated"
case hcsNotificationSystemSaveCompleted:
return "SystemSaveCompleted"
case hcsNotificationSystemRdpEnhancedModeStateChanged:
return "SystemRdpEnhancedModeStateChanged"
case hcsNotificationSystemShutdownFailed:
return "SystemShutdownFailed"
case hcsNotificationSystemGetPropertiesCompleted:
return "SystemGetPropertiesCompleted"
case hcsNotificationSystemModifyCompleted:
return "SystemModifyCompleted"
case hcsNotificationSystemCrashInitiated:
return "SystemCrashInitiated"
case hcsNotificationSystemGuestConnectionClosed:
return "SystemGuestConnectionClosed"
case hcsNotificationProcessExited:
return "ProcessExited"
case hcsNotificationInvalid:
return "Invalid"
case hcsNotificationServiceDisconnect:
return "ServiceDisconnect"
default:
return fmt.Sprintf("Unknown: %d", hn)
}
}
// HCS callbacks take the form:
//
// typedef void (CALLBACK *HCS_NOTIFICATION_CALLBACK)(
// _In_ DWORD notificationType,
// _In_opt_ void* context,
// _In_ HRESULT notificationStatus,
// _In_opt_ PCWSTR notificationData
// );
//
// where the context is a pointer to the data that is associated with a particular notification.
//
// However, since Golang can freely move structs, pointer values are not stable.
// Therefore, interpret the pointer as the unique ID of a [notificationWatcherContext]
// stored in [callbackMap].
//
// Note: Pointer stability via converting to [unsafe.Pointer] for syscalls is only guaranteed
// until the syscall returns, and the same pointer value is therefore invalid across different
// syscall invocations.
// See point (4) of the [unsafe.Pointer] documentation.
type callbackNumber uintptr
var callbackCounter atomic.Uintptr
func nextCallback() callbackNumber { return callbackNumber(callbackCounter.Add(1)) }
type notificationChannel chan error
type notificationWatcherContext struct {
channels notificationChannels
handle vmcompute.HcsCallback
systemID string
processID int
}
type notificationChannels map[hcsNotification]notificationChannel
func newSystemChannels() notificationChannels {
channels := make(notificationChannels)
for _, notif := range []hcsNotification{
hcsNotificationServiceDisconnect,
hcsNotificationSystemExited,
hcsNotificationSystemCreateCompleted,
hcsNotificationSystemStartCompleted,
hcsNotificationSystemPauseCompleted,
hcsNotificationSystemResumeCompleted,
hcsNotificationSystemSaveCompleted,
} {
channels[notif] = make(notificationChannel, 1)
}
return channels
}
func newProcessChannels() notificationChannels {
channels := make(notificationChannels)
for _, notif := range []hcsNotification{
hcsNotificationServiceDisconnect,
hcsNotificationProcessExited,
} {
channels[notif] = make(notificationChannel, 1)
}
return channels
}
func closeChannels(channels notificationChannels) {
for _, c := range channels {
close(c)
}
}
func notificationWatcher(
notificationType hcsNotification,
callbackNum callbackNumber,
notificationStatus uintptr,
notificationData *uint16,
) uintptr {
ctx, entry := log.SetEntry(context.Background(), logrus.Fields{
logfields.CallbackNumber: callbackNum,
"notification-type": notificationType.String(),
})
result := processNotification(ctx, notificationStatus, notificationData)
if result != nil {
entry.Data[logrus.ErrorKey] = result
}
callbackMapLock.RLock()
callbackCtx := callbackMap[callbackNum]
callbackMapLock.RUnlock()
if callbackCtx == nil {
entry.Warn("received HCS notification for unknown callback number")
return 0
}
entry.Data[logfields.SystemID] = callbackCtx.systemID
if callbackCtx.processID != 0 {
entry.Data[logfields.ProcessID] = callbackCtx.processID
}
entry.Debug("received HCS notification")
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
}
// don't call CoTaskMemFree since HCS_NOTIFICATION_CALLBACK's notificationData is PCWSTR.
resultJSON := interop.ConvertString(notificationData)
result := &hcsResult{}
if jsonErr := json.Unmarshal([]byte(resultJSON), result); jsonErr != nil {
log.G(ctx).WithFields(logrus.Fields{
logfields.JSON: resultJSON,
logrus.ErrorKey: jsonErr,
}).Warn("could not unmarshal HCS result")
return err
}
log.G(ctx).WithField("result", result).Trace("parsed notification data")
// 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 notification status and data HResult values")
}
if len(result.ErrorEvents) > 0 {
return &resultError{
Err: err,
Events: result.ErrorEvents,
}
}
return err
}