forked from Code-Hex/vz
-
Notifications
You must be signed in to change notification settings - Fork 0
/
virtualization.go
342 lines (296 loc) · 11 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
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization -framework Cocoa
# include "virtualization_11.h"
# include "virtualization_12.h"
# include "virtualization_13.h"
*/
import "C"
import (
"runtime/cgo"
"sync"
"unsafe"
infinity "github.com/Code-Hex/go-infinity-channel"
"github.com/Code-Hex/vz/v3/internal/objc"
)
// VirtualMachineState represents execution state of the virtual machine.
//
//go:generate stringer -type=VirtualMachineState
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
// VZVirtualMachineStateStopping The virtual machine is being stopped.
// This is the intermediate state between VZVirtualMachineStateRunning and VZVirtualMachineStateStop.
VirtualMachineStateStopping
)
// 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
machineState *machineState
finalizeOnce sync.Once
}
type machineState struct {
state VirtualMachineState
stateNotify *infinity.Channel[VirtualMachineState]
mu sync.RWMutex
}
// 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.
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewVirtualMachine(config *VirtualMachineConfiguration) (*VirtualMachine, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
// should not call Free function for this string.
cs := (*char)(objc.GetUUID())
dispatchQueue := C.makeDispatchQueue(cs.CString())
machineState := &machineState{
state: VirtualMachineState(0),
stateNotify: infinity.NewChannel[VirtualMachineState](),
}
stateHandle := cgo.NewHandle(machineState)
v := &VirtualMachine{
id: cs.String(),
pointer: objc.NewPointer(
C.newVZVirtualMachineWithDispatchQueue(
objc.Ptr(config),
dispatchQueue,
C.uintptr_t(stateHandle),
),
),
dispatchQueue: dispatchQueue,
machineState: machineState,
}
objc.SetFinalizer(v, func(self *VirtualMachine) {
self.finalize()
stateHandle.Delete()
})
return v, nil
}
func (v *VirtualMachine) finalize() {
v.finalizeOnce.Do(func() {
objc.ReleaseDispatch(v.dispatchQueue)
objc.Release(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 := objc.NewNSArray(
C.VZVirtualMachine_socketDevices(objc.Ptr(v)),
)
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(newStateRaw C.int, cgoHandleUintptr C.uintptr_t) {
stateHandle := cgo.Handle(cgoHandleUintptr)
// I expected it will not cause panic.
// if caused panic, that's unexpected behavior.
v, _ := stateHandle.Value().(*machineState)
v.mu.Lock()
newState := VirtualMachineState(newStateRaw)
v.state = newState
v.stateNotify.In() <- newState
v.mu.Unlock()
}
// State represents execution state of the virtual machine.
func (v *VirtualMachine) State() VirtualMachineState {
v.machineState.mu.RLock()
defer v.machineState.mu.RUnlock()
return v.machineState.state
}
// StateChangedNotify gets notification is changed execution state of the virtual machine.
func (v *VirtualMachine) StateChangedNotify() <-chan VirtualMachineState {
v.machineState.mu.RLock()
defer v.machineState.mu.RUnlock()
return v.machineState.stateNotify.Out()
}
// CanStart returns true if the machine is in a state that can be started.
func (v *VirtualMachine) CanStart() bool {
return bool(C.vmCanStart(objc.Ptr(v), 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(objc.Ptr(v), 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(objc.Ptr(v), 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(objc.Ptr(v), v.dispatchQueue))
}
// CanStop returns whether the machine is in a state that can be stopped.
//
// This is only supported on macOS 12 and newer, false will always be returned
// on older versions.
func (v *VirtualMachine) CanStop() bool {
if err := macOSAvailable(12); err != nil {
return false
}
return (bool)(C.vmCanStop(objc.Ptr(v), v.dispatchQueue))
}
//export virtualMachineCompletionHandler
func virtualMachineCompletionHandler(cgoHandleUintptr C.uintptr_t, errPtr unsafe.Pointer) {
cgoHandle := cgo.Handle(cgoHandleUintptr)
handler := cgoHandle.Value().(func(error))
if err := newNSError(errPtr); err != nil {
handler(err)
} else {
handler(nil)
}
}
func makeHandler() (func(error), chan error) {
ch := make(chan error, 1)
return func(err error) {
ch <- err
close(ch)
}, ch
}
type virtualMachineStartOptions struct {
macOSVirtualMachineStartOptionsPtr unsafe.Pointer
}
// VirtualMachineStartOption is an option for virtual machine start.
type VirtualMachineStartOption func(*virtualMachineStartOptions) error
// Start a virtual machine that is in either Stopped or Error state.
//
// If you want to listen status change events, use the "StateChangedNotify" method.
//
// If options are specified, also checks whether these options are
// available in use your macOS version available.
func (v *VirtualMachine) Start(opts ...VirtualMachineStartOption) error {
o := &virtualMachineStartOptions{}
for _, optFunc := range opts {
if err := optFunc(o); err != nil {
return err
}
}
h, errCh := makeHandler()
handle := cgo.NewHandle(h)
defer handle.Delete()
if o.macOSVirtualMachineStartOptionsPtr != nil {
C.startWithOptionsCompletionHandler(
objc.Ptr(v),
v.dispatchQueue,
o.macOSVirtualMachineStartOptionsPtr,
C.uintptr_t(handle),
)
} else {
C.startWithCompletionHandler(objc.Ptr(v), v.dispatchQueue, C.uintptr_t(handle))
}
return <-errCh
}
// Pause a virtual machine that is in Running state.
//
// If you want to listen status change events, use the "StateChangedNotify" method.
func (v *VirtualMachine) Pause() error {
h, errCh := makeHandler()
handle := cgo.NewHandle(h)
defer handle.Delete()
C.pauseWithCompletionHandler(objc.Ptr(v), v.dispatchQueue, C.uintptr_t(handle))
return <-errCh
}
// Resume a virtual machine that is in the Paused state.
//
// If you want to listen status change events, use the "StateChangedNotify" method.
func (v *VirtualMachine) Resume() error {
h, errCh := makeHandler()
handle := cgo.NewHandle(h)
defer handle.Delete()
C.resumeWithCompletionHandler(objc.Ptr(v), v.dispatchQueue, C.uintptr_t(handle))
return <-errCh
}
// RequestStop requests that the guest turns itself off.
//
// If returned error is not nil, assigned with the error if the request failed.
// Returns true if the request was made successfully.
func (v *VirtualMachine) RequestStop() (bool, error) {
nserrPtr := newNSErrorAsNil()
ret := (bool)(C.requestStopVirtualMachine(objc.Ptr(v), v.dispatchQueue, &nserrPtr))
if err := newNSError(nserrPtr); err != nil {
return ret, err
}
return ret, nil
}
// Stop stops a VM that’s in either a running or paused state.
//
// The completion handler returns an error object when the VM fails to stop,
// or nil if the stop was successful.
//
// If you want to listen status change events, use the "StateChangedNotify" method.
//
// Warning: This is a destructive operation. It stops the VM without
// giving the guest a chance to stop cleanly.
//
// This is only supported on macOS 12 and newer, error will be returned on older versions.
func (v *VirtualMachine) Stop() error {
if err := macOSAvailable(12); err != nil {
return err
}
h, errCh := makeHandler()
handle := cgo.NewHandle(h)
defer handle.Delete()
C.stopWithCompletionHandler(objc.Ptr(v), v.dispatchQueue, C.uintptr_t(handle))
return <-errCh
}
// StartGraphicApplication starts an application to display graphics of the VM.
//
// You must to call runtime.LockOSThread before calling this method.
//
// This is only supported on macOS 12 and newer, error will be returned on older versions.
func (v *VirtualMachine) StartGraphicApplication(width, height float64) error {
if err := macOSAvailable(12); err != nil {
return err
}
C.startVirtualMachineWindow(objc.Ptr(v), C.double(width), C.double(height))
return nil
}