-
-
Notifications
You must be signed in to change notification settings - Fork 48
/
virtualization.go
306 lines (271 loc) · 9.71 KB
/
virtualization.go
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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
package vz
/*
#cgo darwin CFLAGS: -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization -framework Cocoa
# include "virtualization.h"
*/
import "C"
import (
"runtime"
"sync"
"unsafe"
"github.com/rs/xid"
)
// VirtualMachineState represents execution state of the virtual machine.
type VirtualMachineState int
const (
// VirtualMachineStateStopped Initial state before the virtual machine is started.
VirtualMachineStateStopped VirtualMachineState = iota
// VirtualMachineStateRunning Running virtual machine.
VirtualMachineStateRunning
// VirtualMachineStatePaused A started virtual machine is paused.
// This state can only be transitioned from VirtualMachineStatePausing.
VirtualMachineStatePaused
// VirtualMachineStateError The virtual machine has encountered an internal error.
VirtualMachineStateError
// VirtualMachineStateStarting The virtual machine is configuring the hardware and starting.
VirtualMachineStateStarting
// VirtualMachineStatePausing The virtual machine is being paused.
// This is the intermediate state between VirtualMachineStateRunning and VirtualMachineStatePaused.
VirtualMachineStatePausing
// VirtualMachineStateResuming The virtual machine is being resumed.
// This is the intermediate state between VirtualMachineStatePaused and VirtualMachineStateRunning.
VirtualMachineStateResuming
)
// VirtualMachine represents the entire state of a single virtual machine.
//
// A Virtual Machine is the emulation of a complete hardware machine of the same architecture as the real hardware machine.
// When executing the Virtual Machine, the Virtualization framework uses certain hardware resources and emulates others to provide isolation
// and great performance.
//
// The definition of a virtual machine starts with its configuration. This is done by setting up a VirtualMachineConfiguration struct.
// Once configured, the virtual machine can be started with (*VirtualMachine).Start() method.
//
// Creating a virtual machine using the Virtualization framework requires the app to have the "com.apple.security.virtualization" entitlement.
// see: https://developer.apple.com/documentation/virtualization/vzvirtualmachine?language=objc
type VirtualMachine struct {
// id for this struct.
id string
// Indicate whether or not virtualization is available.
//
// If virtualization is unavailable, no VirtualMachineConfiguration will validate.
// The validation error of the VirtualMachineConfiguration provides more information about why virtualization is unavailable.
supported bool
pointer
dispatchQueue unsafe.Pointer
mu sync.Mutex
}
type (
machineStatus struct {
state VirtualMachineState
stateNotify chan VirtualMachineState
mu sync.RWMutex
}
machineHandlers struct {
start func(error)
pause func(error)
resume func(error)
}
)
var (
handlers = map[string]*machineHandlers{}
statuses = map[string]*machineStatus{}
)
// NewVirtualMachine creates a new VirtualMachine with VirtualMachineConfiguration.
//
// The configuration must be valid. Validation can be performed at runtime with (*VirtualMachineConfiguration).Validate() method.
// The configuration is copied by the initializer.
//
// A new dispatch queue will create when called this function.
// Every operation on the virtual machine must be done on that queue. The callbacks and delegate methods are invoked on that queue.
func NewVirtualMachine(config *VirtualMachineConfiguration) *VirtualMachine {
id := xid.New().String()
cs := charWithGoString(id)
defer cs.Free()
statuses[id] = &machineStatus{
state: VirtualMachineState(0),
stateNotify: make(chan VirtualMachineState),
}
handlers[id] = &machineHandlers{
start: func(error) {},
pause: func(error) {},
resume: func(error) {},
}
dispatchQueue := C.makeDispatchQueue(cs.CString())
v := &VirtualMachine{
id: id,
pointer: pointer{
ptr: C.newVZVirtualMachineWithDispatchQueue(
config.Ptr(),
dispatchQueue,
cs.CString(),
),
},
dispatchQueue: dispatchQueue,
}
runtime.SetFinalizer(v, func(self *VirtualMachine) {
releaseDispatch(self.dispatchQueue)
self.Release()
})
return v
}
// SocketDevices return the list of socket devices configured on this virtual machine.
// Return an empty array if no socket device is configured.
//
// Since only NewVirtioSocketDeviceConfiguration is available in vz package,
// it will always return VirtioSocketDevice.
// see: https://developer.apple.com/documentation/virtualization/vzvirtualmachine/3656702-socketdevices?language=objc
func (v *VirtualMachine) SocketDevices() []*VirtioSocketDevice {
nsArray := &NSArray{
pointer: pointer{
ptr: C.VZVirtualMachine_socketDevices(v.Ptr()),
},
}
ptrs := nsArray.ToPointerSlice()
socketDevices := make([]*VirtioSocketDevice, len(ptrs))
for i, ptr := range ptrs {
socketDevices[i] = newVirtioSocketDevice(ptr, v.dispatchQueue)
}
return socketDevices
}
//export changeStateOnObserver
func changeStateOnObserver(state C.int, cID *C.char) {
id := (*char)(cID)
// I expected it will not cause panic.
// if caused panic, that's unexpected behavior.
v, _ := statuses[id.String()]
v.mu.Lock()
newState := VirtualMachineState(state)
v.state = newState
// for non-blocking
go func() { v.stateNotify <- newState }()
statuses[id.String()] = v
v.mu.Unlock()
}
// State represents execution state of the virtual machine.
func (v *VirtualMachine) State() VirtualMachineState {
// I expected it will not cause panic.
// if caused panic, that's unexpected behavior.
val, _ := statuses[v.id]
val.mu.RLock()
defer val.mu.RUnlock()
return val.state
}
// StateChangedNotify gets notification is changed execution state of the virtual machine.
func (v *VirtualMachine) StateChangedNotify() <-chan VirtualMachineState {
// I expected it will not cause panic.
// if caused panic, that's unexpected behavior.
val, _ := statuses[v.id]
val.mu.RLock()
defer val.mu.RUnlock()
return val.stateNotify
}
// CanStart returns true if the machine is in a state that can be started.
func (v *VirtualMachine) CanStart() bool {
return bool(C.vmCanStart(v.Ptr(), v.dispatchQueue))
}
// CanPause returns true if the machine is in a state that can be paused.
func (v *VirtualMachine) CanPause() bool {
return bool(C.vmCanPause(v.Ptr(), v.dispatchQueue))
}
// CanResume returns true if the machine is in a state that can be resumed.
func (v *VirtualMachine) CanResume() bool {
return (bool)(C.vmCanResume(v.Ptr(), v.dispatchQueue))
}
// CanRequestStop returns whether the machine is in a state where the guest can be asked to stop.
func (v *VirtualMachine) CanRequestStop() bool {
return (bool)(C.vmCanRequestStop(v.Ptr(), v.dispatchQueue))
}
//export startHandler
func startHandler(errPtr unsafe.Pointer, cid *C.char) {
id := (*char)(cid).String()
// If returns nil in the cgo world, the nil will not be treated as nil in the Go world
// so this is temporarily handled (Go 1.17)
if err := newNSError(errPtr); err != nil {
handlers[id].start(err)
} else {
handlers[id].start(nil)
}
}
//export pauseHandler
func pauseHandler(errPtr unsafe.Pointer, cid *C.char) {
id := (*char)(cid).String()
// see: startHandler
if err := newNSError(errPtr); err != nil {
handlers[id].pause(err)
} else {
handlers[id].pause(nil)
}
}
//export resumeHandler
func resumeHandler(errPtr unsafe.Pointer, cid *C.char) {
id := (*char)(cid).String()
// see: startHandler
if err := newNSError(errPtr); err != nil {
handlers[id].resume(err)
} else {
handlers[id].resume(nil)
}
}
func makeHandler(fn func(error)) (func(error), chan struct{}) {
done := make(chan struct{})
return func(err error) {
fn(err)
close(done)
}, done
}
// Start a virtual machine that is in either Stopped or Error state.
//
// - fn parameter called after the virtual machine has been successfully started or on error.
// The error parameter passed to the block is null if the start was successful.
func (v *VirtualMachine) Start(fn func(error)) {
h, done := makeHandler(fn)
handlers[v.id].start = h
cid := charWithGoString(v.id)
defer cid.Free()
C.startWithCompletionHandler(v.Ptr(), v.dispatchQueue, cid.CString())
<-done
}
// Pause a virtual machine that is in Running state.
//
// - fn parameter called after the virtual machine has been successfully paused or on error.
// The error parameter passed to the block is null if the start was successful.
func (v *VirtualMachine) Pause(fn func(error)) {
h, done := makeHandler(fn)
handlers[v.id].pause = h
cid := charWithGoString(v.id)
defer cid.Free()
C.pauseWithCompletionHandler(v.Ptr(), v.dispatchQueue, cid.CString())
<-done
}
// Resume a virtual machine that is in the Paused state.
//
// - fn parameter called after the virtual machine has been successfully resumed or on error.
// The error parameter passed to the block is null if the resumption was successful.
func (v *VirtualMachine) Resume(fn func(error)) {
h, done := makeHandler(fn)
handlers[v.id].resume = h
cid := charWithGoString(v.id)
defer cid.Free()
C.resumeWithCompletionHandler(v.Ptr(), v.dispatchQueue, cid.CString())
<-done
}
// RequestStop requests that the guest turns itself off.
//
// If returned error is not nil, assigned with the error if the request failed.
// Returens true if the request was made successfully.
func (v *VirtualMachine) RequestStop() (bool, error) {
nserr := newNSErrorAsNil()
nserrPtr := nserr.Ptr()
ret := (bool)(C.requestStopVirtualMachine(v.Ptr(), v.dispatchQueue, &nserrPtr))
if err := newNSError(nserrPtr); err != nil {
return ret, err
}
return ret, nil
}
// StartGraphicApplication starts an application to display graphics of the VM.
//
// You must to call runtime.LockOSThread before calling this method.
func (v *VirtualMachine) StartGraphicApplication(width, height float64) {
C.startVirtualMachineWindow(v.Ptr(), C.double(width), C.double(height))
}