Merge pull request #3865 from deitch/bump-vz

bump virtualization-framework library to v3.0.0
This commit is contained in:
Avi Deitcher
2022-11-07 09:54:08 +02:00
committed by GitHub
100 changed files with 8200 additions and 2327 deletions

View File

@@ -8,7 +8,6 @@ require (
github.com/Azure/go-autorest/autorest v0.11.24
github.com/Azure/go-autorest/autorest/adal v0.9.18
github.com/Azure/go-autorest/autorest/to v0.4.0
github.com/Code-Hex/vz v0.0.4
github.com/Microsoft/go-winio v0.5.2
github.com/ScaleFT/sshkeys v0.0.0-20181112160850-82451a803681
github.com/aws/aws-sdk-go v1.44.82
@@ -43,7 +42,7 @@ require (
github.com/surma/gocpio v1.0.2-0.20160926205914-fcb68777e7dc
github.com/vmware/govmomi v0.20.3
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
golang.org/x/crypto v0.1.0
golang.org/x/net v0.1.0
golang.org/x/oauth2 v0.1.0
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
@@ -60,6 +59,7 @@ require (
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/Code-Hex/vz/v3 v3.0.0 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
@@ -130,6 +130,7 @@ require (
go.opentelemetry.io/otel/sdk v1.4.1 // indirect
go.opentelemetry.io/otel/trace v1.4.1 // indirect
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
golang.org/x/mod v0.6.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
google.golang.org/appengine v1.6.7 // indirect

View File

@@ -149,6 +149,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Code-Hex/vz v0.0.4 h1:1rM8ijE+znlrOeYIer441cSwpYq2rk57Fd3dRpxUDUA=
github.com/Code-Hex/vz v0.0.4/go.mod h1:UeHKXSv3hP7BzU6IaVE/a7VHSHUHpqbS3oVko4O5UYI=
github.com/Code-Hex/vz/v3 v3.0.0 h1:UuYAZz8NF8n+R4MLfn/lpUlepgzZ8BlhKtioQ+q9LXk=
github.com/Code-Hex/vz/v3 v3.0.0/go.mod h1:+xPQOXVzNaZ4OeIIhlJFumtbFhsvRfqKwX7wuZS4dFA=
github.com/Djarvur/go-err113 v0.0.0-20200410182137-af658d038157/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
github.com/Djarvur/go-err113 v0.1.0/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo=
@@ -1590,6 +1592,8 @@ golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1629,6 +1633,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

View File

@@ -16,7 +16,7 @@ import (
"strings"
"syscall"
"github.com/Code-Hex/vz"
vz "github.com/Code-Hex/vz/v3"
"github.com/pkg/term/termios"
log "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
@@ -147,22 +147,34 @@ func runVirtualizationFramework(args []string) {
}
vmlinuzFile = vmlinuzUncompressed
}
bootLoader := vz.NewLinuxBootLoader(
bootLoader, err := vz.NewLinuxBootLoader(
vmlinuzFile,
vz.WithCommandLine(strings.Join(kernelCommandLineArguments, " ")),
vz.WithInitrd(initrd),
)
if err != nil {
log.Fatalf("unable to create bootloader: %v", err)
}
config := vz.NewVirtualMachineConfiguration(
config, err := vz.NewVirtualMachineConfiguration(
bootLoader,
*cpus,
memBytes,
)
if err != nil {
log.Fatalf("unable to create VM config: %v", err)
}
// console
stdin, stdout := os.Stdin, os.Stdout
serialPortAttachment := vz.NewFileHandleSerialPortAttachment(stdin, stdout)
consoleConfig := vz.NewVirtioConsoleDeviceSerialPortConfiguration(serialPortAttachment)
serialPortAttachment, err := vz.NewFileHandleSerialPortAttachment(stdin, stdout)
if err != nil {
log.Fatalf("unable to create serial port attachment: %v", err)
}
consoleConfig, err := vz.NewVirtioConsoleDeviceSerialPortConfiguration(serialPortAttachment)
if err != nil {
log.Fatalf("unable to create console config: %v", err)
}
config.SetSerialPortsVirtualMachineConfiguration([]*vz.VirtioConsoleDeviceSerialPortConfiguration{
consoleConfig,
})
@@ -179,19 +191,33 @@ func runVirtualizationFramework(args []string) {
switch netMode[0] {
case virtualizationNetworkingVMNet:
natAttachment := vz.NewNATNetworkDeviceAttachment()
networkConfig := vz.NewVirtioNetworkDeviceConfiguration(natAttachment)
natAttachment, err := vz.NewNATNetworkDeviceAttachment()
if err != nil {
log.Fatalf("Could not create NAT network device attachment: %v", err)
}
networkConfig, err := vz.NewVirtioNetworkDeviceConfiguration(natAttachment)
if err != nil {
log.Fatalf("Could not create virtio network device configuration: %v", err)
}
config.SetNetworkDevicesVirtualMachineConfiguration([]*vz.VirtioNetworkDeviceConfiguration{
networkConfig,
})
networkConfig.SetMacAddress(vz.NewRandomLocallyAdministeredMACAddress())
macAddress, err := vz.NewRandomLocallyAdministeredMACAddress()
if err != nil {
log.Fatalf("Could not create random MAC address: %v", err)
}
networkConfig.SetMACAddress(macAddress)
case virtualizationNetworkingNone:
default:
log.Fatalf("Invalid networking mode: %s", netMode[0])
}
// entropy
entropyConfig := vz.NewVirtioEntropyDeviceConfiguration()
entropyConfig, err := vz.NewVirtioEntropyDeviceConfiguration()
if err != nil {
log.Fatalf("Could not create virtio entropy device configuration: %v", err)
}
config.SetEntropyDevicesVirtualMachineConfiguration([]*vz.VirtioEntropyDeviceConfiguration{
entropyConfig,
})
@@ -215,7 +241,10 @@ func runVirtualizationFramework(args []string) {
if err != nil {
log.Fatal(err)
}
storageDeviceConfig := vz.NewVirtioBlockDeviceConfiguration(diskImageAttachment)
storageDeviceConfig, err := vz.NewVirtioBlockDeviceConfiguration(diskImageAttachment)
if err != nil {
log.Fatalf("Could not create virtio block device configuration: %v", err)
}
storageDevices = append(storageDevices, storageDeviceConfig)
}
for _, iso := range isoPaths {
@@ -226,38 +255,50 @@ func runVirtualizationFramework(args []string) {
if err != nil {
log.Fatal(err)
}
storageDeviceConfig := vz.NewVirtioBlockDeviceConfiguration(diskImageAttachment)
storageDeviceConfig, err := vz.NewVirtioBlockDeviceConfiguration(diskImageAttachment)
if err != nil {
log.Fatalf("Could not create virtio block device configuration: %v", err)
}
storageDevices = append(storageDevices, storageDeviceConfig)
}
config.SetStorageDevicesVirtualMachineConfiguration(storageDevices)
// traditional memory balloon device which allows for managing guest memory. (optional)
memoryBalloonDeviceConfiguration, err := vz.NewVirtioTraditionalMemoryBalloonDeviceConfiguration()
if err != nil {
log.Fatalf("Could not create virtio traditional memory balloon device configuration: %v", err)
}
config.SetMemoryBalloonDevicesVirtualMachineConfiguration([]vz.MemoryBalloonDeviceConfiguration{
vz.NewVirtioTraditionalMemoryBalloonDeviceConfiguration(),
memoryBalloonDeviceConfiguration,
})
// socket device (optional)
socketDeviceConfiguration, err := vz.NewVirtioSocketDeviceConfiguration()
if err != nil {
log.Fatalf("Could not create virtio socket device configuration: %v", err)
}
config.SetSocketDevicesVirtualMachineConfiguration([]vz.SocketDeviceConfiguration{
vz.NewVirtioSocketDeviceConfiguration(),
socketDeviceConfiguration,
})
validated, err := config.Validate()
if !validated || err != nil {
log.Fatal("validation failed", err)
}
vm := vz.NewVirtualMachine(config)
vm, err := vz.NewVirtualMachine(config)
if err != nil {
log.Fatalf("Could not create virtual machine: %v", err)
}
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, syscall.SIGTERM)
errCh := make(chan error, 1)
vm.Start(func(err error) {
if err != nil {
errCh <- err
}
})
if err := vm.Start(); err != nil {
errCh <- err
}
for {
select {

View File

@@ -1,2 +0,0 @@
virtualization
*.log

View File

@@ -1,52 +0,0 @@
vz - Go binding with Apple [Virtualization.framework](https://developer.apple.com/documentation/virtualization?language=objc)
=======
vz provides the power of the Apple Virtualization.framework in Go. Put here is block quote of overreview which is written what is Virtualization.framework from the document.
> The Virtualization framework provides high-level APIs for creating and managing virtual machines on Apple silicon and Intel-based Mac computers. Use this framework to boot and run a Linux-based operating system in a custom environment that you define. The framework supports the Virtio specification, which defines standard interfaces for many device types, including network, socket, serial port, storage, entropy, and memory-balloon devices.
## USAGE
Please see the example directory.
## REQUIREMENTS
- Higher or equal to macOS Big Sur (11.0.0)
- If you're M1 Mac User need higher or equal to Go 1.16
## IMPORTANT
For binaries used in this package, you need to create an entitlements file like the one below and apply the following command.
<details>
<summary>vz.entitlements</summary>
```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.virtualization</key>
<true/>
</dict>
</plist>
```
</details>
```sh
$ codesign --entitlements vz.entitlements -s - <YOUR BINARY PATH>
```
> A process must have the com.apple.security.virtualization entitlement to use the Virtualization APIs.
If you want to use [`VZBridgedNetworkDeviceAttachment`](https://developer.apple.com/documentation/virtualization/vzbridgednetworkdeviceattachment?language=objc), you need to add also `com.apple.vm.networking` entitlement.
## TODO
- [x] [VZMACAddress](https://developer.apple.com/documentation/virtualization/vzmacaddress?language=objc)
- [ ] [VZVirtioSocketDeviceConfiguration](https://developer.apple.com/documentation/virtualization/sockets?language=objc)
## LICENSE
MIT License

View File

@@ -1,89 +0,0 @@
package vz
/*
#cgo darwin CFLAGS: -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization.h"
*/
import "C"
import (
"fmt"
"runtime"
)
// BootLoader is the interface of boot loader definitions.
// see: LinuxBootLoader
type BootLoader interface {
NSObject
bootLoader()
}
type baseBootLoader struct{}
func (*baseBootLoader) bootLoader() {}
var _ BootLoader = (*LinuxBootLoader)(nil)
// LinuxBootLoader Boot loader configuration for a Linux kernel.
type LinuxBootLoader struct {
vmlinuzPath string
initrdPath string
cmdLine string
pointer
*baseBootLoader
}
func (b *LinuxBootLoader) String() string {
return fmt.Sprintf(
"vmlinuz: %q, initrd: %q, command-line: %q",
b.vmlinuzPath,
b.initrdPath,
b.cmdLine,
)
}
type LinuxBootLoaderOption func(b *LinuxBootLoader)
// WithCommandLine sets the command-line parameters.
// see: https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html
func WithCommandLine(cmdLine string) LinuxBootLoaderOption {
return func(b *LinuxBootLoader) {
b.cmdLine = cmdLine
cs := charWithGoString(cmdLine)
defer cs.Free()
C.setCommandLineVZLinuxBootLoader(b.Ptr(), cs.CString())
}
}
// WithInitrd sets the optional initial RAM disk.
func WithInitrd(initrdPath string) LinuxBootLoaderOption {
return func(b *LinuxBootLoader) {
b.initrdPath = initrdPath
cs := charWithGoString(initrdPath)
defer cs.Free()
C.setInitialRamdiskURLVZLinuxBootLoader(b.Ptr(), cs.CString())
}
}
// NewLinuxBootLoader creates a LinuxBootLoader with the Linux kernel passed as Path.
func NewLinuxBootLoader(vmlinuz string, opts ...LinuxBootLoaderOption) *LinuxBootLoader {
vmlinuzPath := charWithGoString(vmlinuz)
defer vmlinuzPath.Free()
bootLoader := &LinuxBootLoader{
vmlinuzPath: vmlinuz,
pointer: pointer{
ptr: C.newVZLinuxBootLoader(
vmlinuzPath.CString(),
),
},
}
runtime.SetFinalizer(bootLoader, func(self *LinuxBootLoader) {
self.Release()
})
for _, opt := range opts {
opt(bootLoader)
}
return bootLoader
}

View File

@@ -1,137 +0,0 @@
package vz
/*
#cgo darwin CFLAGS: -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization.h"
*/
import "C"
import "runtime"
// VirtualMachineConfiguration defines the configuration of a VirtualMachine.
//
// The following properties must be configured before creating a virtual machine:
// - bootLoader
//
// The configuration of devices is often done in two parts:
// - Device configuration
// - Device attachment
//
// The device configuration defines the characteristics of the emulated hardware device.
// For example, for a network device, the device configuration defines the type of network adapter present
// in the virtual machine and its MAC address.
//
// The device attachment defines the host machine's resources that are exposed by the virtual device.
// For example, for a network device, the device attachment can be virtual network interface with a NAT
// to the real network.
//
// Creating a virtual machine using the Virtualization framework requires the app to have the "com.apple.security.virtualization" entitlement.
// A VirtualMachineConfiguration is considered invalid if the application does not have the entitlement.
//
// see: https://developer.apple.com/documentation/virtualization/vzvirtualmachineconfiguration?language=objc
type VirtualMachineConfiguration struct {
cpuCount uint
memorySize uint64
pointer
}
// NewVirtualMachineConfiguration creates a new configuration.
//
// - bootLoader parameter is used when the virtual machine starts.
// - cpu parameter is The number of CPUs must be a value between
// VZVirtualMachineConfiguration.minimumAllowedCPUCount and VZVirtualMachineConfiguration.maximumAllowedCPUCount.
// - memorySize parameter represents memory size in bytes.
// The memory size must be a multiple of a 1 megabyte (1024 * 1024 bytes) between
// VZVirtualMachineConfiguration.minimumAllowedMemorySize and VZVirtualMachineConfiguration.maximumAllowedMemorySize.
func NewVirtualMachineConfiguration(bootLoader BootLoader, cpu uint, memorySize uint64) *VirtualMachineConfiguration {
config := &VirtualMachineConfiguration{
cpuCount: cpu,
memorySize: memorySize,
pointer: pointer{
ptr: C.newVZVirtualMachineConfiguration(
bootLoader.Ptr(),
C.uint(cpu),
C.ulonglong(memorySize),
),
},
}
runtime.SetFinalizer(config, func(self *VirtualMachineConfiguration) {
self.Release()
})
return config
}
// Validate the configuration.
//
// Return true if the configuration is valid.
// If error is not nil, assigned with the validation error if the validation failed.
func (v *VirtualMachineConfiguration) Validate() (bool, error) {
nserr := newNSErrorAsNil()
nserrPtr := nserr.Ptr()
ret := C.validateVZVirtualMachineConfiguration(v.Ptr(), &nserrPtr)
err := newNSError(nserrPtr)
if err != nil {
return false, err
}
return (bool)(ret), nil
}
// SetEntropyDevicesVirtualMachineConfiguration sets list of entropy devices. Empty by default.
func (v *VirtualMachineConfiguration) SetEntropyDevicesVirtualMachineConfiguration(cs []*VirtioEntropyDeviceConfiguration) {
ptrs := make([]NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := convertToNSMutableArray(ptrs)
C.setEntropyDevicesVZVirtualMachineConfiguration(v.Ptr(), array.Ptr())
}
// SetMemoryBalloonDevicesVirtualMachineConfiguration sets list of memory balloon devices. Empty by default.
func (v *VirtualMachineConfiguration) SetMemoryBalloonDevicesVirtualMachineConfiguration(cs []MemoryBalloonDeviceConfiguration) {
ptrs := make([]NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := convertToNSMutableArray(ptrs)
C.setMemoryBalloonDevicesVZVirtualMachineConfiguration(v.Ptr(), array.Ptr())
}
// SetNetworkDevicesVirtualMachineConfiguration sets list of network adapters. Empty by default.
func (v *VirtualMachineConfiguration) SetNetworkDevicesVirtualMachineConfiguration(cs []*VirtioNetworkDeviceConfiguration) {
ptrs := make([]NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := convertToNSMutableArray(ptrs)
C.setNetworkDevicesVZVirtualMachineConfiguration(v.Ptr(), array.Ptr())
}
// SetSerialPortsVirtualMachineConfiguration sets list of serial ports. Empty by default.
func (v *VirtualMachineConfiguration) SetSerialPortsVirtualMachineConfiguration(cs []*VirtioConsoleDeviceSerialPortConfiguration) {
ptrs := make([]NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := convertToNSMutableArray(ptrs)
C.setSerialPortsVZVirtualMachineConfiguration(v.Ptr(), array.Ptr())
}
// SetSocketDevicesVirtualMachineConfiguration sets list of socket devices. Empty by default.
func (v *VirtualMachineConfiguration) SetSocketDevicesVirtualMachineConfiguration(cs []SocketDeviceConfiguration) {
ptrs := make([]NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := convertToNSMutableArray(ptrs)
C.setSocketDevicesVZVirtualMachineConfiguration(v.Ptr(), array.Ptr())
}
// SetStorageDevicesVirtualMachineConfiguration sets list of disk devices. Empty by default.
func (v *VirtualMachineConfiguration) SetStorageDevicesVirtualMachineConfiguration(cs []StorageDeviceConfiguration) {
ptrs := make([]NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := convertToNSMutableArray(ptrs)
C.setStorageDevicesVZVirtualMachineConfiguration(v.Ptr(), array.Ptr())
}

View File

@@ -1,206 +0,0 @@
package vz
/*
#cgo darwin CFLAGS: -x objective-c
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization.h"
const char *getNSErrorLocalizedDescription(void *err)
{
NSString *ld = (NSString *)[(NSError *)err localizedDescription];
return [ld UTF8String];
}
const char *getNSErrorDomain(void *err)
{
const char *ret;
@autoreleasepool {
NSString *domain = (NSString *)[(NSError *)err domain];
ret = [domain UTF8String];
}
return ret;
}
const char *getNSErrorUserInfo(void *err)
{
NSDictionary<NSErrorUserInfoKey, id> *ui = [(NSError *)err userInfo];
NSString *uis = [NSString stringWithFormat:@"%@", ui];
return [uis UTF8String];
}
NSInteger getNSErrorCode(void *err)
{
return (NSInteger)[(NSError *)err code];
}
void *makeNSMutableArray(unsigned long cap)
{
return [[NSMutableArray alloc] initWithCapacity:(NSUInteger)cap];
}
void addNSMutableArrayVal(void *ary, void *val)
{
[(NSMutableArray *)ary addObject:(NSObject *)val];
}
void *newNSError()
{
NSError *err = nil;
return err;
}
bool hasError(void *err)
{
return (NSError *)err != nil;
}
void *minimumAlloc()
{
return [[NSMutableData dataWithLength:1] mutableBytes];
}
void releaseNSObject(void* o)
{
@autoreleasepool {
[(NSObject*)o release];
}
}
static inline void startNSThread()
{
[[NSThread new] start]; // put the runtime into multi-threaded mode
}
static inline void releaseDispatch(void *queue)
{
dispatch_release((dispatch_queue_t)queue);
}
*/
import "C"
import (
"fmt"
"runtime"
"unsafe"
)
// startNSThread starts NSThread.
func startNSThread() {
C.startNSThread()
}
// releaseDispatch releases allocated dispatch_queue_t
func releaseDispatch(p unsafe.Pointer) {
C.releaseDispatch(p)
}
// CharWithGoString makes *Char which is *C.Char wrapper from Go string.
func charWithGoString(s string) *char {
return (*char)(unsafe.Pointer(C.CString(s)))
}
// Char is a wrapper of C.char
type char C.char
// CString converts *C.char from *Char
func (c *char) CString() *C.char {
return (*C.char)(c)
}
// String converts Go string from *Char
func (c *char) String() string {
return C.GoString((*C.char)(c))
}
// Free frees allocated *C.char in Go code
func (c *char) Free() {
C.free(unsafe.Pointer(c))
}
// pointer indicates any pointers which are allocated in objective-c world.
type pointer struct {
ptr unsafe.Pointer
}
// Release releases allocated resources in objective-c world.
func (p *pointer) Release() {
C.releaseNSObject(p.Ptr())
runtime.KeepAlive(p)
}
// Ptr returns raw pointer.
func (o *pointer) Ptr() unsafe.Pointer {
if o == nil {
return nil
}
return o.ptr
}
// NSObject indicates NSObject
type NSObject interface {
Ptr() unsafe.Pointer
}
// NSError indicates NSError.
type NSError struct {
Domain string
Code int
LocalizedDescription string
UserInfo string
pointer
}
// newNSErrorAsNil makes nil NSError in objective-c world.
func newNSErrorAsNil() *pointer {
p := &pointer{
ptr: unsafe.Pointer(C.newNSError()),
}
return p
}
// hasNSError checks passed pointer is NSError or not.
func hasNSError(nserrPtr unsafe.Pointer) bool {
return (bool)(C.hasError(nserrPtr))
}
func (n *NSError) Error() string {
if n == nil {
return "<nil>"
}
return fmt.Sprintf(
"Error Domain=%s Code=%d Description=%q UserInfo=%s",
n.Domain,
n.Code,
n.LocalizedDescription,
n.UserInfo,
)
}
// TODO(codehex): improvement (3 times called C functions now)
func newNSError(p unsafe.Pointer) *NSError {
if !hasNSError(p) {
return nil
}
domain := (*char)(C.getNSErrorDomain(p))
description := (*char)(C.getNSErrorLocalizedDescription(p))
userInfo := (*char)(C.getNSErrorUserInfo(p))
return &NSError{
Domain: domain.String(),
Code: int(C.getNSErrorCode(p)),
LocalizedDescription: description.String(),
UserInfo: userInfo.String(), // NOTE(codehex): maybe we can convert to map[string]interface{}
}
}
// convertToNSMutableArray converts to NSMutableArray from NSObject slice in Go world.
func convertToNSMutableArray(s []NSObject) *pointer {
ln := len(s)
ary := C.makeNSMutableArray(C.ulong(ln))
for _, v := range s {
C.addNSMutableArrayVal(ary, v.Ptr())
}
p := &pointer{ptr: ary}
runtime.SetFinalizer(p, func(self *pointer) {
self.Release()
})
return p
}

View File

@@ -1,46 +0,0 @@
package vz
/*
#cgo darwin CFLAGS: -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization.h"
*/
import "C"
import "runtime"
// SocketDeviceConfiguration for a socket device configuration.
type SocketDeviceConfiguration interface {
NSObject
socketDeviceConfiguration()
}
type baseSocketDeviceConfiguration struct{}
func (*baseSocketDeviceConfiguration) socketDeviceConfiguration() {}
var _ SocketDeviceConfiguration = (*VirtioSocketDeviceConfiguration)(nil)
// VirtioSocketDeviceConfiguration is a configuration of the Virtio socket device.
//
// This configuration creates a Virtio socket device for the guest which communicates with the host through the Virtio interface.
// Only one Virtio socket device can be used per virtual machine.
// see: https://developer.apple.com/documentation/virtualization/vzvirtiosocketdeviceconfiguration?language=objc
type VirtioSocketDeviceConfiguration struct {
pointer
*baseSocketDeviceConfiguration
}
// NewVirtioSocketDeviceConfiguration creates a new VirtioSocketDeviceConfiguration.
func NewVirtioSocketDeviceConfiguration() *VirtioSocketDeviceConfiguration {
config := &VirtioSocketDeviceConfiguration{
pointer: pointer{
ptr: C.newVZVirtioSocketDeviceConfiguration(),
},
}
runtime.SetFinalizer(config, func(self *VirtioSocketDeviceConfiguration) {
self.Release()
})
return config
}

View File

@@ -1,109 +0,0 @@
package vz
/*
#cgo darwin CFLAGS: -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization.h"
*/
import "C"
import "runtime"
type baseStorageDeviceAttachment struct{}
func (*baseStorageDeviceAttachment) storageDeviceAttachment() {}
// StorageDeviceAttachment for a storage device attachment.
//
// A storage device attachment defines how a virtual machine storage device interfaces with the host system.
// see: https://developer.apple.com/documentation/virtualization/vzstoragedeviceattachment?language=objc
type StorageDeviceAttachment interface {
NSObject
storageDeviceAttachment()
}
var _ StorageDeviceAttachment = (*DiskImageStorageDeviceAttachment)(nil)
// DiskImageStorageDeviceAttachment is a storage device attachment using a disk image to implement the storage.
//
// This storage device attachment uses a disk image on the host file system as the drive of the storage device.
// Only raw data disk images are supported.
// see: https://developer.apple.com/documentation/virtualization/vzdiskimagestoragedeviceattachment?language=objc
type DiskImageStorageDeviceAttachment struct {
pointer
*baseStorageDeviceAttachment
}
// NewDiskImageStorageDeviceAttachment initialize the attachment from a local file path.
// Returns error is not nil, assigned with the error if the initialization failed.
//
// - diskPath is local file URL to the disk image in RAW format.
// - readOnly if YES, the device attachment is read-only, otherwise the device can write data to the disk image.
func NewDiskImageStorageDeviceAttachment(diskPath string, readOnly bool) (*DiskImageStorageDeviceAttachment, error) {
nserr := newNSErrorAsNil()
nserrPtr := nserr.Ptr()
diskPathChar := charWithGoString(diskPath)
defer diskPathChar.Free()
attachment := &DiskImageStorageDeviceAttachment{
pointer: pointer{
ptr: C.newVZDiskImageStorageDeviceAttachment(
diskPathChar.CString(),
C.bool(readOnly),
&nserrPtr,
),
},
}
if err := newNSError(nserrPtr); err != nil {
return nil, err
}
runtime.SetFinalizer(attachment, func(self *DiskImageStorageDeviceAttachment) {
self.Release()
})
return attachment, nil
}
// StorageDeviceConfiguration for a storage device configuration.
type StorageDeviceConfiguration interface {
NSObject
storageDeviceConfiguration()
}
type baseStorageDeviceConfiguration struct{}
func (*baseStorageDeviceConfiguration) storageDeviceConfiguration() {}
var _ StorageDeviceConfiguration = (*VirtioBlockDeviceConfiguration)(nil)
// VirtioBlockDeviceConfiguration is a configuration of a paravirtualized storage device of type Virtio Block Device.
//
// This device configuration creates a storage device using paravirtualization.
// The emulated device follows the Virtio Block Device specification.
//
// The host implementation of the device is done through an attachment subclassing VZStorageDeviceAttachment
// like VZDiskImageStorageDeviceAttachment.
// see: https://developer.apple.com/documentation/virtualization/vzvirtioblockdeviceconfiguration?language=objc
type VirtioBlockDeviceConfiguration struct {
pointer
*baseStorageDeviceConfiguration
}
// NewVirtioBlockDeviceConfiguration initialize a VZVirtioBlockDeviceConfiguration with a device attachment.
//
// - attachment The storage device attachment. This defines how the virtualized device operates on the host side.
func NewVirtioBlockDeviceConfiguration(attachment StorageDeviceAttachment) *VirtioBlockDeviceConfiguration {
config := &VirtioBlockDeviceConfiguration{
pointer: pointer{
ptr: C.newVZVirtioBlockDeviceConfiguration(
attachment.Ptr(),
),
},
}
runtime.SetFinalizer(config, func(self *VirtioBlockDeviceConfiguration) {
self.Release()
})
return config
}

View File

@@ -0,0 +1,4 @@
BasedOnStyle: WebKit
Language: ObjC
TabWidth: 4
PointerAlignment: Right

View File

@@ -0,0 +1,7 @@
virtualization
*.log
.envrc
.env
RestoreImage.ipsw
testdata/*
!testdata/.gitkeep

View File

@@ -0,0 +1,23 @@
## How to contribute to github.com/Code-Hex/vz
#### **Did you find a bug?**
* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/Code-Hex/vz/issues).
* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/Code-Hex/vz/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring.
#### **Did you write a patch that fixes a bug?**
* Open a new GitHub pull request with the patch.
* Ensure the PR description clearly describes the problem and solution. **Must** be included the relevant issue number.
> **Note**
> Basically, this can be done after a policy has been decided like how to write the code or how to implement, etc in an issue.
> **Warning**
> We generally do not accept some pull requests like either did not follow the owner's opinion or harassing.
#### **Did you fix whitespace, format code, or make a purely cosmetic patch?**
Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of github.com/Code-Hex/vz will generally not be accepted.

View File

@@ -0,0 +1,31 @@
PUIPUI_LINUX_VERSION := 0.0.1
ARCH := $(shell uname -m)
KERNEL_ARCH := $(shell echo $(ARCH) | sed -e s/arm64/aarch64/)
KERNEL_TAR := puipui_linux_v$(PUIPUI_LINUX_VERSION)_$(KERNEL_ARCH).tar.gz
KERNEL_DOWNLOAD_URL := https://github.com/Code-Hex/puipui-linux/releases/download/v$(PUIPUI_LINUX_VERSION)/$(KERNEL_TAR)
.PHONY: fmt
fmt:
@ls | grep -E '\.(h|m)$$' | xargs clang-format -i --verbose
.PHONY: test
test:
go test -exec "go run $(PWD)/cmd/codesign" ./... -timeout 60s -v
.PHONY: download_kernel
download_kernel:
curl --output-dir testdata -LO $(KERNEL_DOWNLOAD_URL)
@tar xvf testdata/$(KERNEL_TAR) -C testdata
ifeq ($(ARCH),arm64)
@gunzip -f testdata/Image.gz
else
@mv testdata/bzImage testdata/Image
endif
.PHONY: install/stringer
install/stringer:
@go install golang.org/x/tools/cmd/stringer@latest
.PHONY: clean
clean:
@rm testdata/{Image,initramfs.cpio.gz,*.tar.gz}

View File

@@ -0,0 +1,137 @@
vz - Go binding with Apple [Virtualization.framework](https://developer.apple.com/documentation/virtualization?language=objc)
=======
[![Build](https://github.com/Code-Hex/vz/actions/workflows/compile.yml/badge.svg)](https://github.com/Code-Hex/vz/actions/workflows/compile.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/Code-Hex/vz/v3.svg)](https://pkg.go.dev/github.com/Code-Hex/vz/v3)
vz provides the power of the Apple Virtualization.framework in Go. Put here is block quote of overreview which is written what is Virtualization.framework from the document.
> The Virtualization framework provides high-level APIs for creating and managing virtual machines (VM) on Apple silicon and Intel-based Mac computers. Use this framework to boot and run macOS or Linux-based operating systems in custom environments that you define. The framework supports the [Virtual I/O Device (VIRTIO)](https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html) specification, which defines standard interfaces for many device types, including network, socket, serial port, storage, entropy, and memory-balloon devices.
## Usage
Please see the [example](https://github.com/Code-Hex/vz/tree/main/example) directory.
## Requirements
- Higher or equal to macOS Big Sur (11.0.0).
- Latest version of vz supports last two Go major [releases](https://go.dev/doc/devel/release) and might work with older versions.
## Installation
Initialize your project by creating a folder and then running `go mod init github.com/your/repo` ([learn more](https://go.dev/blog/using-go-modules)) inside the folder. Then install vz with the go get command:
```
$ go get github.com/Code-Hex/vz/v3
```
Deprecated older versions (v1, v2).
## Feature Overview
- ✅ Virtualize Linux on a Mac **(x86_64, arm64)**
- GUI Support
- Boot Extensible Firmware Interface (EFI) ROM
- Clipboard sharing through the SPICE agent
- ✅ Virtualize macOS on Apple Silicon Macs **(arm64)**
- Fetches the latest restore image supported by this host from the network
- Start in recovery mode
- ✅ Running Intel Binaries in Linux VMs with Rosetta **(arm64)**
- ✅ [Shared Directories](https://github.com/Code-Hex/vz/wiki/Shared-Directories)
- ✅ [Virtio Sockets](https://github.com/Code-Hex/vz/wiki/Sockets)
- ✅ Less dependent (only under golang.org/x/*)
## Important
For binaries used in this package, you need to create an entitlements file like the one below and apply the following command.
<details>
<summary>vz.entitlements</summary>
```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.virtualization</key>
<true/>
</dict>
</plist>
```
</details>
```sh
$ codesign --entitlements vz.entitlements -s - <YOUR BINARY PATH>
```
> A process must have the com.apple.security.virtualization entitlement to use the Virtualization APIs.
If you want to use [`VZBridgedNetworkDeviceAttachment`](https://developer.apple.com/documentation/virtualization/vzbridgednetworkdeviceattachment?language=objc), you need to add also `com.apple.vm.networking` entitlement.
## Known compile-time warnings
If you compile using an older Xcode SDK, you will get the following warnings.
This example warns that macOS 12.3 API and macOS 13 API are not available in the binary build. Running this binary on a modern OS (macOS 12.3 or macOS 13) means that these APIs are not available. This means these APIs are not available even if you are running this binary on a modern OS (macOS 12.3 or macOS 13).
```
$ go build .
# github.com/Code-Hex/vz/v3
In file included from _cgo_export.c:4:
In file included from socket.go:6:
In file included from ./virtualization_11.h:9:
./virtualization_helper.h:25:9: warning: macOS 12.3 API has been disabled [-W#pragma-messages]
./virtualization_helper.h:32:9: warning: macOS 13 API has been disabled [-W#pragma-messages]
```
If you want to build a binary that can use the API on all operating systems, make sure the Xcode SDK is up-to-date.
You can check the version of the Xcode SDK available for each macOS on this site.
https://xcodereleases.com/
## Version compatibility check
The package provides a mechanism for checking the availability of the respective API through error handling:
```go
bootLoader, err := vz.NewEFIBootLoader()
if errors.Is(err, vz.ErrUnsupportedOSVersion) || errors.Is(err, ErrBuildTargetOSVersion) {
return fallbackBootLoader()
}
if err != nil {
return nil, err
}
return bootLoader, nil
```
There are two items to check.
1. API is compatible with the version of macOS
2. The binary was built with the API enabled
## Knowledge for the Apple Virtualization.framework
There is a lot of knowledge required to use this Apple Virtualization.framework, but the information is too scattered and very difficult to understand. In most cases, this can be found in [the official documentation](https://developer.apple.com/documentation/virtualization?language=objc). However, the Linux kernel knowledge required to use the feature provided by this framework is not documented. Therefore, I have compiled the knowledge I have gathered so far into this wiki.
https://github.com/Code-Hex/vz/wiki
Anyone is free to edit this wiki. It would help someone if you could add information not listed here. Let's make a good wiki together!
## Testing
If you want to contribute some code, you will need to add tests.
[PUI PUI Linux](https://github.com/Code-Hex/puipui-linux) is used to test this library. This Linux is designed to provide only the minimum functionality required for the Apple Virtualization.framework (Virtio), so the kernel file size is very small.
The test code uses the `Makefile` in the project root.
```
$ # Download PUI PUI Linux, Only required the first time.
$ make download_kernel
$ make test
```
## LICENSE
MIT License

View File

@@ -0,0 +1,141 @@
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_11.h"
# include "virtualization_12.h"
*/
import "C"
import (
"github.com/Code-Hex/vz/v3/internal/objc"
)
// AudioDeviceConfiguration interface for an audio device configuration.
type AudioDeviceConfiguration interface {
objc.NSObject
audioDeviceConfiguration()
}
type baseAudioDeviceConfiguration struct{}
func (*baseAudioDeviceConfiguration) audioDeviceConfiguration() {}
// VirtioSoundDeviceConfiguration is a struct that defines a Virtio sound device configuration.
//
// Use a VirtioSoundDeviceConfiguration to configure an audio device for your VM. After creating
// this struct, assign appropriate values via the SetStreams method which defines the behaviors of
// the underlying audio streams for this audio device.
//
// After creating and configuring a VirtioSoundDeviceConfiguration struct, assign it to the
// SetAudioDevicesVirtualMachineConfiguration method of your VMs configuration.
type VirtioSoundDeviceConfiguration struct {
*pointer
*baseAudioDeviceConfiguration
}
var _ AudioDeviceConfiguration = (*VirtioSoundDeviceConfiguration)(nil)
// NewVirtioSoundDeviceConfiguration creates a new sound device configuration.
//
// This is only supported on macOS 12 and newer, error will be returned
// on older versions.
func NewVirtioSoundDeviceConfiguration() (*VirtioSoundDeviceConfiguration, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
config := &VirtioSoundDeviceConfiguration{
pointer: objc.NewPointer(
C.newVZVirtioSoundDeviceConfiguration(),
),
}
objc.SetFinalizer(config, func(self *VirtioSoundDeviceConfiguration) {
objc.Release(self)
})
return config, nil
}
// SetStreams sets the list of audio streams exposed by this device.
func (v *VirtioSoundDeviceConfiguration) SetStreams(streams ...VirtioSoundDeviceStreamConfiguration) {
ptrs := make([]objc.NSObject, len(streams))
for i, val := range streams {
ptrs[i] = val
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setStreamsVZVirtioSoundDeviceConfiguration(
objc.Ptr(v), objc.Ptr(array),
)
}
// VirtioSoundDeviceStreamConfiguration interface for Virtio Sound Device Stream Configuration.
type VirtioSoundDeviceStreamConfiguration interface {
objc.NSObject
virtioSoundDeviceStreamConfiguration()
}
type baseVirtioSoundDeviceStreamConfiguration struct{}
func (*baseVirtioSoundDeviceStreamConfiguration) virtioSoundDeviceStreamConfiguration() {}
// VirtioSoundDeviceHostInputStreamConfiguration is a PCM stream of input audio data,
// such as from a microphone via host.
type VirtioSoundDeviceHostInputStreamConfiguration struct {
*pointer
*baseVirtioSoundDeviceStreamConfiguration
}
var _ VirtioSoundDeviceStreamConfiguration = (*VirtioSoundDeviceHostInputStreamConfiguration)(nil)
// NewVirtioSoundDeviceHostInputStreamConfiguration creates a new PCM stream configuration of input audio data from host.
//
// This is only supported on macOS 12 and newer, error will be returned
// on older versions.
func NewVirtioSoundDeviceHostInputStreamConfiguration() (*VirtioSoundDeviceHostInputStreamConfiguration, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
config := &VirtioSoundDeviceHostInputStreamConfiguration{
pointer: objc.NewPointer(
C.newVZVirtioSoundDeviceHostInputStreamConfiguration(),
),
}
objc.SetFinalizer(config, func(self *VirtioSoundDeviceHostInputStreamConfiguration) {
objc.Release(self)
})
return config, nil
}
// VirtioSoundDeviceHostOutputStreamConfiguration is a struct that
// defines a Virtio host sound device output stream configuration.
//
// A PCM stream of output audio data, such as to a speaker from host.
type VirtioSoundDeviceHostOutputStreamConfiguration struct {
*pointer
*baseVirtioSoundDeviceStreamConfiguration
}
var _ VirtioSoundDeviceStreamConfiguration = (*VirtioSoundDeviceHostOutputStreamConfiguration)(nil)
// NewVirtioSoundDeviceHostOutputStreamConfiguration creates a new sounds device output stream configuration.
//
// This is only supported on macOS 12 and newer, error will be returned
// on older versions.
func NewVirtioSoundDeviceHostOutputStreamConfiguration() (*VirtioSoundDeviceHostOutputStreamConfiguration, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
config := &VirtioSoundDeviceHostOutputStreamConfiguration{
pointer: objc.NewPointer(
C.newVZVirtioSoundDeviceHostOutputStreamConfiguration(),
),
}
objc.SetFinalizer(config, func(self *VirtioSoundDeviceHostOutputStreamConfiguration) {
objc.Release(self)
})
return config, nil
}

View File

@@ -0,0 +1,226 @@
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_11.h"
# include "virtualization_13.h"
*/
import "C"
import (
"fmt"
"os"
"github.com/Code-Hex/vz/v3/internal/objc"
)
// BootLoader is the interface of boot loader definitions.
type BootLoader interface {
objc.NSObject
bootLoader()
}
type baseBootLoader struct{}
func (*baseBootLoader) bootLoader() {}
var _ BootLoader = (*LinuxBootLoader)(nil)
// LinuxBootLoader Boot loader configuration for a Linux kernel.
// see: https://developer.apple.com/documentation/virtualization/vzlinuxbootloader?language=objc
type LinuxBootLoader struct {
vmlinuzPath string
initrdPath string
cmdLine string
*pointer
*baseBootLoader
}
func (b *LinuxBootLoader) String() string {
return fmt.Sprintf(
"vmlinuz: %q, initrd: %q, command-line: %q",
b.vmlinuzPath,
b.initrdPath,
b.cmdLine,
)
}
// LinuxBootLoaderOption is an option for LinuxBootLoader.
type LinuxBootLoaderOption func(b *LinuxBootLoader) error
// WithCommandLine sets the command-line parameters.
// see: https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html
func WithCommandLine(cmdLine string) LinuxBootLoaderOption {
return func(b *LinuxBootLoader) error {
b.cmdLine = cmdLine
cs := charWithGoString(cmdLine)
defer cs.Free()
C.setCommandLineVZLinuxBootLoader(objc.Ptr(b), cs.CString())
return nil
}
}
// WithInitrd sets the optional initial RAM disk.
func WithInitrd(initrdPath string) LinuxBootLoaderOption {
return func(b *LinuxBootLoader) error {
if _, err := os.Stat(initrdPath); err != nil {
return fmt.Errorf("invalid initial RAM disk path: %w", err)
}
b.initrdPath = initrdPath
cs := charWithGoString(initrdPath)
defer cs.Free()
C.setInitialRamdiskURLVZLinuxBootLoader(objc.Ptr(b), cs.CString())
return nil
}
}
// NewLinuxBootLoader creates a LinuxBootLoader with the Linux kernel passed as Path.
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewLinuxBootLoader(vmlinuz string, opts ...LinuxBootLoaderOption) (*LinuxBootLoader, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
if _, err := os.Stat(vmlinuz); err != nil {
return nil, fmt.Errorf("invalid linux kernel path: %w", err)
}
vmlinuzPath := charWithGoString(vmlinuz)
defer vmlinuzPath.Free()
bootLoader := &LinuxBootLoader{
vmlinuzPath: vmlinuz,
pointer: objc.NewPointer(
C.newVZLinuxBootLoader(vmlinuzPath.CString()),
),
}
objc.SetFinalizer(bootLoader, func(self *LinuxBootLoader) {
objc.Release(self)
})
for _, opt := range opts {
if err := opt(bootLoader); err != nil {
return nil, err
}
}
return bootLoader, nil
}
var _ BootLoader = (*LinuxBootLoader)(nil)
// EFIBootLoader Boot loader configuration for booting guest operating systems expecting an EFI ROM.
// see: https://developer.apple.com/documentation/virtualization/vzefibootloader?language=objc
type EFIBootLoader struct {
*pointer
*baseBootLoader
variableStore *EFIVariableStore
}
// NewEFIBootLoaderOption is an option type to initialize a new EFIBootLoader.
type NewEFIBootLoaderOption func(b *EFIBootLoader)
// WithEFIVariableStore sets the optional EFI variable store.
func WithEFIVariableStore(variableStore *EFIVariableStore) NewEFIBootLoaderOption {
return func(e *EFIBootLoader) {
C.setVariableStoreVZEFIBootLoader(objc.Ptr(e), objc.Ptr(variableStore))
e.variableStore = variableStore
}
}
// NewEFIBootLoader creates a new EFI boot loader.
//
// This is only supported on macOS 13 and newer, error will
// be returned on older versions.
func NewEFIBootLoader(opts ...NewEFIBootLoaderOption) (*EFIBootLoader, error) {
if err := macOSAvailable(13); err != nil {
return nil, err
}
bootLoader := &EFIBootLoader{
pointer: objc.NewPointer(
C.newVZEFIBootLoader(),
),
}
for _, optFunc := range opts {
optFunc(bootLoader)
}
objc.SetFinalizer(bootLoader, func(self *EFIBootLoader) {
objc.Release(self)
})
return bootLoader, nil
}
// VariableStore returns EFI variable store.
func (e *EFIBootLoader) VariableStore() *EFIVariableStore {
return e.variableStore
}
// EFIVariableStore is EFI variable store.
// The EFI variable store contains NVRAM variables exposed by the EFI ROM.
//
// see: https://developer.apple.com/documentation/virtualization/vzefivariablestore?language=objc
type EFIVariableStore struct {
*pointer
path string
}
// NewEFIVariableStoreOption is an option type to initialize a new EFIVariableStore.
type NewEFIVariableStoreOption func(*EFIVariableStore) error
// WithCreatingEFIVariableStore is an option to initialized VZEFIVariableStore to a path on a file system.
// If the variable store already exists in path, it is overwritten.
func WithCreatingEFIVariableStore() NewEFIVariableStoreOption {
return func(es *EFIVariableStore) error {
cpath := charWithGoString(es.path)
defer cpath.Free()
nserrPtr := newNSErrorAsNil()
es.pointer = objc.NewPointer(
C.newCreatingVZEFIVariableStoreAtPath(
cpath.CString(),
&nserrPtr,
),
)
if err := newNSError(nserrPtr); err != nil {
return err
}
return nil
}
}
// NewEFIVariableStore Initialize the variable store. If no options are specified,
// it initialises from the paths that exist.
//
// This is only supported on macOS 13 and newer, error will
// be returned on older versions.
func NewEFIVariableStore(path string, opts ...NewEFIVariableStoreOption) (*EFIVariableStore, error) {
if err := macOSAvailable(13); err != nil {
return nil, err
}
variableStore := &EFIVariableStore{path: path}
for _, optFunc := range opts {
if err := optFunc(variableStore); err != nil {
return nil, err
}
}
if objc.Ptr(variableStore) == nil {
if _, err := os.Stat(path); err != nil {
return nil, err
}
cpath := charWithGoString(path)
defer cpath.Free()
variableStore.pointer = objc.NewPointer(
C.newVZEFIVariableStorePath(cpath.CString()),
)
}
objc.SetFinalizer(variableStore, func(self *EFIVariableStore) {
objc.Release(self)
})
return variableStore, nil
}
// Path returns the path of the variable store on the local file system.
func (e *EFIVariableStore) Path() string { return e.path }

View File

@@ -0,0 +1,43 @@
//go:build darwin && arm64
// +build darwin,arm64
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_12_arm64.h"
*/
import "C"
import (
"github.com/Code-Hex/vz/v3/internal/objc"
)
// MacOSBootLoader is a boot loader configuration for booting macOS on Apple Silicon.
type MacOSBootLoader struct {
*pointer
*baseBootLoader
}
var _ BootLoader = (*MacOSBootLoader)(nil)
// NewMacOSBootLoader creates a new MacOSBootLoader struct.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewMacOSBootLoader() (*MacOSBootLoader, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
bootLoader := &MacOSBootLoader{
pointer: objc.NewPointer(
C.newVZMacOSBootLoader(),
),
}
objc.SetFinalizer(bootLoader, func(self *MacOSBootLoader) {
objc.Release(self)
})
return bootLoader, nil
}

View File

@@ -0,0 +1,139 @@
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c
#cgo darwin LDFLAGS: -lobjc -framework Foundation
#import <Foundation/Foundation.h>
const char *getNSErrorLocalizedDescription(void *err)
{
NSString *ld = (NSString *)[(NSError *)err localizedDescription];
return [ld UTF8String];
}
const char *getNSErrorDomain(void *err)
{
NSString *domain = (NSString *)[(NSError *)err domain];
return [domain UTF8String];
}
const char *getNSErrorUserInfo(void *err)
{
NSDictionary<NSErrorUserInfoKey, id> *ui = [(NSError *)err userInfo];
NSString *uis = [NSString stringWithFormat:@"%@", ui];
return [uis UTF8String];
}
NSInteger getNSErrorCode(void *err)
{
return (NSInteger)[(NSError *)err code];
}
typedef struct NSErrorFlat {
const char *domain;
const char *localizedDescription;
const char *userinfo;
int code;
} NSErrorFlat;
NSErrorFlat convertNSError2Flat(void *err)
{
NSErrorFlat ret;
ret.domain = getNSErrorDomain(err);
ret.localizedDescription = getNSErrorLocalizedDescription(err);
ret.userinfo = getNSErrorUserInfo(err);
ret.code = (int)getNSErrorCode(err);
return ret;
}
void *newNSError()
{
NSError *err = nil;
return err;
}
bool hasError(void *err)
{
return (NSError *)err != nil;
}
*/
import "C"
import (
"fmt"
"unsafe"
"github.com/Code-Hex/vz/v3/internal/objc"
)
// pointer is a type alias which is able to use as embedded type and
// makes as unexported it.
type pointer = objc.Pointer
// NSError indicates NSError.
type NSError struct {
Domain string
Code int
LocalizedDescription string
UserInfo string
}
// newNSErrorAsNil makes nil NSError in objective-c world.
func newNSErrorAsNil() unsafe.Pointer {
return unsafe.Pointer(C.newNSError())
}
// hasNSError checks passed pointer is NSError or not.
func hasNSError(nserrPtr unsafe.Pointer) bool {
return (bool)(C.hasError(nserrPtr))
}
func (n *NSError) Error() string {
if n == nil {
return "<nil>"
}
return fmt.Sprintf(
"Error Domain=%s Code=%d Description=%q UserInfo=%s",
n.Domain,
n.Code,
n.LocalizedDescription,
n.UserInfo,
)
}
func newNSError(p unsafe.Pointer) *NSError {
if !hasNSError(p) {
return nil
}
nsError := C.convertNSError2Flat(p)
return &NSError{
Domain: (*char)(nsError.domain).String(),
Code: int((nsError.code)),
LocalizedDescription: (*char)(nsError.localizedDescription).String(),
UserInfo: (*char)(nsError.userinfo).String(), // NOTE(codehex): maybe we can convert to map[string]interface{}
}
}
// CharWithGoString makes *Char which is *C.Char wrapper from Go string.
func charWithGoString(s string) *char {
return (*char)(unsafe.Pointer(C.CString(s)))
}
// Char is a wrapper of C.char
type char C.char
// CString converts *C.char from *Char
func (c *char) CString() *C.char {
return (*C.char)(c)
}
// String converts Go string from *Char
func (c *char) String() string {
return C.GoString((*C.char)(c))
}
// Free frees allocated *C.char in Go code
func (c *char) Free() {
C.free(unsafe.Pointer(c))
}

View File

@@ -0,0 +1,66 @@
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_13.h"
*/
import "C"
import (
"github.com/Code-Hex/vz/v3/internal/objc"
)
// SpiceAgentPortAttachment is an attachment point that enables
// the Spice clipboard sharing capability.
//
// see: https://developer.apple.com/documentation/virtualization/vzspiceagentportattachment?language=objc
type SpiceAgentPortAttachment struct {
*pointer
*baseSerialPortAttachment
enabledSharesClipboard bool
}
var _ SerialPortAttachment = (*SpiceAgentPortAttachment)(nil)
// NewSpiceAgentPortAttachment creates a new Spice agent port attachment.
//
// This is only supported on macOS 13 and newer, error will
// be returned on older versions.
func NewSpiceAgentPortAttachment() (*SpiceAgentPortAttachment, error) {
if err := macOSAvailable(13); err != nil {
return nil, err
}
spiceAgent := &SpiceAgentPortAttachment{
pointer: objc.NewPointer(
C.newVZSpiceAgentPortAttachment(),
),
enabledSharesClipboard: true,
}
objc.SetFinalizer(spiceAgent, func(self *SpiceAgentPortAttachment) {
objc.Release(self)
})
return spiceAgent, nil
}
// SetSharesClipboard sets enable the Spice agent clipboard sharing capability.
func (s *SpiceAgentPortAttachment) SetSharesClipboard(enable bool) {
C.setSharesClipboardVZSpiceAgentPortAttachment(
objc.Ptr(s),
C.bool(enable),
)
s.enabledSharesClipboard = enable
}
// SharesClipboard returns enable the Spice agent clipboard sharing capability.
func (s *SpiceAgentPortAttachment) SharesClipboard() bool { return s.enabledSharesClipboard }
// SpiceAgentPortAttachmentName returns the Spice agent port name.
func SpiceAgentPortAttachmentName() (string, error) {
if err := macOSAvailable(13); err != nil {
return "", err
}
cstring := (*char)(C.getSpiceAgentPortName())
return cstring.String(), nil
}

View File

@@ -0,0 +1,299 @@
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_11.h"
# include "virtualization_12.h"
# include "virtualization_13.h"
*/
import "C"
import (
"github.com/Code-Hex/vz/v3/internal/objc"
)
// VirtualMachineConfiguration defines the configuration of a VirtualMachine.
//
// The following properties must be configured before creating a virtual machine:
// - bootLoader
//
// The configuration of devices is often done in two parts:
// - Device configuration
// - Device attachment
//
// The device configuration defines the characteristics of the emulated hardware device.
// For example, for a network device, the device configuration defines the type of network adapter present
// in the virtual machine and its MAC address.
//
// The device attachment defines the host machine's resources that are exposed by the virtual device.
// For example, for a network device, the device attachment can be virtual network interface with a NAT
// to the real network.
//
// Creating a virtual machine using the Virtualization framework requires the app to have the "com.apple.security.virtualization" entitlement.
// A VirtualMachineConfiguration is considered invalid if the application does not have the entitlement.
//
// see: https://developer.apple.com/documentation/virtualization/vzvirtualmachineconfiguration?language=objc
type VirtualMachineConfiguration struct {
cpuCount uint
memorySize uint64
*pointer
}
// NewVirtualMachineConfiguration creates a new configuration.
//
// - bootLoader parameter is used when the virtual machine starts.
// - cpu parameter is The number of CPUs must be a value between
// VZVirtualMachineConfiguration.minimumAllowedCPUCount and VZVirtualMachineConfiguration.maximumAllowedCPUCount.
// - memorySize parameter represents memory size in bytes.
// The memory size must be a multiple of a 1 megabyte (1024 * 1024 bytes) between
// VZVirtualMachineConfiguration.minimumAllowedMemorySize and VZVirtualMachineConfiguration.maximumAllowedMemorySize.
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewVirtualMachineConfiguration(bootLoader BootLoader, cpu uint, memorySize uint64) (*VirtualMachineConfiguration, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
config := &VirtualMachineConfiguration{
cpuCount: cpu,
memorySize: memorySize,
pointer: objc.NewPointer(
C.newVZVirtualMachineConfiguration(
objc.Ptr(bootLoader),
C.uint(cpu),
C.ulonglong(memorySize),
),
),
}
objc.SetFinalizer(config, func(self *VirtualMachineConfiguration) {
objc.Release(self)
})
return config, nil
}
// Validate the configuration.
//
// Return true if the configuration is valid.
// If error is not nil, assigned with the validation error if the validation failed.
func (v *VirtualMachineConfiguration) Validate() (bool, error) {
nserrPtr := newNSErrorAsNil()
ret := C.validateVZVirtualMachineConfiguration(objc.Ptr(v), &nserrPtr)
err := newNSError(nserrPtr)
if err != nil {
return false, err
}
return (bool)(ret), nil
}
// SetEntropyDevicesVirtualMachineConfiguration sets list of entropy devices. Empty by default.
func (v *VirtualMachineConfiguration) SetEntropyDevicesVirtualMachineConfiguration(cs []*VirtioEntropyDeviceConfiguration) {
ptrs := make([]objc.NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setEntropyDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
}
// SetMemoryBalloonDevicesVirtualMachineConfiguration sets list of memory balloon devices. Empty by default.
func (v *VirtualMachineConfiguration) SetMemoryBalloonDevicesVirtualMachineConfiguration(cs []MemoryBalloonDeviceConfiguration) {
ptrs := make([]objc.NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setMemoryBalloonDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
}
// SetNetworkDevicesVirtualMachineConfiguration sets list of network adapters. Empty by default.
func (v *VirtualMachineConfiguration) SetNetworkDevicesVirtualMachineConfiguration(cs []*VirtioNetworkDeviceConfiguration) {
ptrs := make([]objc.NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setNetworkDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
}
// NetworkDevices return the list of network device configuration set in this virtual machine configuration.
// Return an empty array if no network device configuration is set.
func (v *VirtualMachineConfiguration) NetworkDevices() []*VirtioNetworkDeviceConfiguration {
nsArray := objc.NewNSArray(
C.networkDevicesVZVirtualMachineConfiguration(objc.Ptr(v)),
)
ptrs := nsArray.ToPointerSlice()
networkDevices := make([]*VirtioNetworkDeviceConfiguration, len(ptrs))
for i, ptr := range ptrs {
networkDevices[i] = newVirtioNetworkDeviceConfiguration(ptr)
}
return networkDevices
}
// SetSerialPortsVirtualMachineConfiguration sets list of serial ports. Empty by default.
func (v *VirtualMachineConfiguration) SetSerialPortsVirtualMachineConfiguration(cs []*VirtioConsoleDeviceSerialPortConfiguration) {
ptrs := make([]objc.NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setSerialPortsVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
}
// SetSocketDevicesVirtualMachineConfiguration sets list of socket devices. Empty by default.
func (v *VirtualMachineConfiguration) SetSocketDevicesVirtualMachineConfiguration(cs []SocketDeviceConfiguration) {
ptrs := make([]objc.NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setSocketDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
}
// SocketDevices return the list of socket device configuration configured in this virtual machine configuration.
// Return an empty array if no socket device configuration is set.
func (v *VirtualMachineConfiguration) SocketDevices() []SocketDeviceConfiguration {
nsArray := objc.NewNSArray(
C.socketDevicesVZVirtualMachineConfiguration(objc.Ptr(v)),
)
ptrs := nsArray.ToPointerSlice()
socketDevices := make([]SocketDeviceConfiguration, len(ptrs))
for i, ptr := range ptrs {
socketDevices[i] = newVirtioSocketDeviceConfiguration(ptr)
}
return socketDevices
}
// SetStorageDevicesVirtualMachineConfiguration sets list of disk devices. Empty by default.
func (v *VirtualMachineConfiguration) SetStorageDevicesVirtualMachineConfiguration(cs []StorageDeviceConfiguration) {
ptrs := make([]objc.NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setStorageDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
}
// SetDirectorySharingDevicesVirtualMachineConfiguration sets list of directory sharing devices. Empty by default.
//
// This is only supported on macOS 12 and newer. Older versions do nothing.
func (v *VirtualMachineConfiguration) SetDirectorySharingDevicesVirtualMachineConfiguration(cs []DirectorySharingDeviceConfiguration) {
if err := macOSAvailable(12); err != nil {
return
}
ptrs := make([]objc.NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setDirectorySharingDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
}
// SetPlatformVirtualMachineConfiguration sets the hardware platform to use. Defaults to GenericPlatformConfiguration.
//
// This is only supported on macOS 12 and newer. Older versions do nothing.
func (v *VirtualMachineConfiguration) SetPlatformVirtualMachineConfiguration(c PlatformConfiguration) {
if err := macOSAvailable(12); err != nil {
return
}
C.setPlatformVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(c))
}
// SetGraphicsDevicesVirtualMachineConfiguration sets list of graphics devices. Empty by default.
//
// This is only supported on macOS 12 and newer. Older versions do nothing.
func (v *VirtualMachineConfiguration) SetGraphicsDevicesVirtualMachineConfiguration(cs []GraphicsDeviceConfiguration) {
if err := macOSAvailable(12); err != nil {
return
}
ptrs := make([]objc.NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setGraphicsDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
}
// SetPointingDevicesVirtualMachineConfiguration sets list of pointing devices. Empty by default.
//
// This is only supported on macOS 12 and newer. Older versions do nothing.
func (v *VirtualMachineConfiguration) SetPointingDevicesVirtualMachineConfiguration(cs []PointingDeviceConfiguration) {
if err := macOSAvailable(12); err != nil {
return
}
ptrs := make([]objc.NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setPointingDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
}
// SetKeyboardsVirtualMachineConfiguration sets list of keyboards. Empty by default.
//
// This is only supported on macOS 12 and newer. Older versions do nothing.
func (v *VirtualMachineConfiguration) SetKeyboardsVirtualMachineConfiguration(cs []KeyboardConfiguration) {
if err := macOSAvailable(12); err != nil {
return
}
ptrs := make([]objc.NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setKeyboardsVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
}
// SetAudioDevicesVirtualMachineConfiguration sets list of audio devices. Empty by default.
//
// This is only supported on macOS 12 and newer. Older versions do nothing.
func (v *VirtualMachineConfiguration) SetAudioDevicesVirtualMachineConfiguration(cs []AudioDeviceConfiguration) {
if err := macOSAvailable(12); err != nil {
return
}
ptrs := make([]objc.NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setAudioDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
}
// SetConsoleDevicesVirtualMachineConfiguration sets list of console devices. Empty by default.
//
// This is only supported on macOS 13 and newer. Older versions do nothing.
func (v *VirtualMachineConfiguration) SetConsoleDevicesVirtualMachineConfiguration(cs []ConsoleDeviceConfiguration) {
if err := macOSAvailable(13); err != nil {
return
}
ptrs := make([]objc.NSObject, len(cs))
for i, val := range cs {
ptrs[i] = val
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setConsoleDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
}
// VirtualMachineConfigurationMinimumAllowedMemorySize returns minimum
// amount of memory required by virtual machines.
func VirtualMachineConfigurationMinimumAllowedMemorySize() uint64 {
return uint64(C.minimumAllowedMemorySizeVZVirtualMachineConfiguration())
}
// VirtualMachineConfigurationMaximumAllowedMemorySize returns maximum
// amount of memory allowed for a virtual machine.
func VirtualMachineConfigurationMaximumAllowedMemorySize() uint64 {
return uint64(C.maximumAllowedMemorySizeVZVirtualMachineConfiguration())
}
// VirtualMachineConfigurationMinimumAllowedCPUCount returns minimum
// number of CPUs for a virtual machine.
func VirtualMachineConfigurationMinimumAllowedCPUCount() uint {
return uint(C.minimumAllowedCPUCountVZVirtualMachineConfiguration())
}
// VirtualMachineConfigurationMaximumAllowedCPUCount returns maximum
// number of CPUs for a virtual machine.
func VirtualMachineConfigurationMaximumAllowedCPUCount() uint {
return uint(C.maximumAllowedCPUCountVZVirtualMachineConfiguration())
}

View File

@@ -0,0 +1,173 @@
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_13.h"
*/
import "C"
import (
"unsafe"
"github.com/Code-Hex/vz/v3/internal/objc"
)
// ConsoleDeviceConfiguration interface for an console device configuration.
type ConsoleDeviceConfiguration interface {
objc.NSObject
consoleDeviceConfiguration()
}
type baseConsoleDeviceConfiguration struct{}
func (*baseConsoleDeviceConfiguration) consoleDeviceConfiguration() {}
// VirtioConsoleDeviceConfiguration is Virtio Console Device.
type VirtioConsoleDeviceConfiguration struct {
*pointer
portsPtr unsafe.Pointer
*baseConsoleDeviceConfiguration
consolePorts map[int]*VirtioConsolePortConfiguration
}
var _ ConsoleDeviceConfiguration = (*VirtioConsoleDeviceConfiguration)(nil)
// NewVirtioConsoleDeviceConfiguration creates a new VirtioConsoleDeviceConfiguration.
func NewVirtioConsoleDeviceConfiguration() (*VirtioConsoleDeviceConfiguration, error) {
if err := macOSAvailable(13); err != nil {
return nil, err
}
config := &VirtioConsoleDeviceConfiguration{
pointer: objc.NewPointer(
C.newVZVirtioConsoleDeviceConfiguration(),
),
consolePorts: make(map[int]*VirtioConsolePortConfiguration),
}
config.portsPtr = C.portsVZVirtioConsoleDeviceConfiguration(objc.Ptr(config))
objc.SetFinalizer(config, func(self *VirtioConsoleDeviceConfiguration) {
objc.Release(self)
})
return config, nil
}
// MaximumPortCount returns the maximum number of ports allocated by this device.
// The default is the number of ports attached to this device.
func (v *VirtioConsoleDeviceConfiguration) MaximumPortCount() uint32 {
return uint32(C.maximumPortCountVZVirtioConsolePortConfigurationArray(v.portsPtr))
}
func (v *VirtioConsoleDeviceConfiguration) SetVirtioConsolePortConfiguration(idx int, portConfig *VirtioConsolePortConfiguration) {
C.setObjectAtIndexedSubscriptVZVirtioConsolePortConfigurationArray(
v.portsPtr,
objc.Ptr(portConfig),
C.int(idx),
)
// to mark as currently reachable.
// This ensures that the object is not freed, and its finalizer is not run
v.consolePorts[idx] = portConfig
}
type ConsolePortConfiguration interface {
objc.NSObject
consolePortConfiguration()
}
type baseConsolePortConfiguration struct{}
func (*baseConsolePortConfiguration) consolePortConfiguration() {}
// VirtioConsolePortConfiguration is Virtio Console Port
//
// A console port is a two-way communication channel between a host VZSerialPortAttachment and
// a virtual machine console port. One or more console ports are attached to a Virtio console device.
type VirtioConsolePortConfiguration struct {
*pointer
*baseConsolePortConfiguration
isConsole bool
name string
attachment SerialPortAttachment
}
var _ ConsolePortConfiguration = (*VirtioConsolePortConfiguration)(nil)
// NewVirtioConsolePortConfigurationOption is an option type to initialize a new VirtioConsolePortConfiguration
type NewVirtioConsolePortConfigurationOption func(*VirtioConsolePortConfiguration)
// WithVirtioConsolePortConfigurationName sets the console port's name.
// The default behavior is to not use a name unless set.
func WithVirtioConsolePortConfigurationName(name string) NewVirtioConsolePortConfigurationOption {
return func(vcpc *VirtioConsolePortConfiguration) {
consolePortName := charWithGoString(name)
defer consolePortName.Free()
C.setNameVZVirtioConsolePortConfiguration(
objc.Ptr(vcpc),
consolePortName.CString(),
)
vcpc.name = name
}
}
// WithVirtioConsolePortConfigurationIsConsole sets the console port may be marked
// for use as the system console. The default is false.
func WithVirtioConsolePortConfigurationIsConsole(isConsole bool) NewVirtioConsolePortConfigurationOption {
return func(vcpc *VirtioConsolePortConfiguration) {
C.setIsConsoleVZVirtioConsolePortConfiguration(
objc.Ptr(vcpc),
C.bool(isConsole),
)
vcpc.isConsole = isConsole
}
}
// WithVirtioConsolePortConfigurationAttachment sets the console port attachment.
// The default is nil.
func WithVirtioConsolePortConfigurationAttachment(attachment SerialPortAttachment) NewVirtioConsolePortConfigurationOption {
return func(vcpc *VirtioConsolePortConfiguration) {
C.setAttachmentVZVirtioConsolePortConfiguration(
objc.Ptr(vcpc),
objc.Ptr(attachment),
)
vcpc.attachment = attachment
}
}
// NewVirtioConsolePortConfiguration creates a new VirtioConsolePortConfiguration.
//
// This is only supported on macOS 13 and newer, error will
// be returned on older versions.
func NewVirtioConsolePortConfiguration(opts ...NewVirtioConsolePortConfigurationOption) (*VirtioConsolePortConfiguration, error) {
if err := macOSAvailable(13); err != nil {
return nil, err
}
vcpc := &VirtioConsolePortConfiguration{
pointer: objc.NewPointer(
C.newVZVirtioConsolePortConfiguration(),
),
}
for _, optFunc := range opts {
optFunc(vcpc)
}
objc.SetFinalizer(vcpc, func(self *VirtioConsolePortConfiguration) {
objc.Release(self)
})
return vcpc, nil
}
// Name returns the console port's name.
func (v *VirtioConsolePortConfiguration) Name() string { return v.name }
// IsConsole returns the console port may be marked for use as the system console.
func (v *VirtioConsolePortConfiguration) IsConsole() bool { return v.isConsole }
// Attachment returns the console port attachment.
func (v *VirtioConsolePortConfiguration) Attachment() SerialPortAttachment {
return v.attachment
}

View File

@@ -0,0 +1,65 @@
//go:build darwin && debug
// +build darwin,debug
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_debug.h"
*/
import "C"
import (
"runtime"
"github.com/Code-Hex/vz/v3/internal/objc"
)
// DebugStubConfiguration is an interface to debug configuration.
type DebugStubConfiguration interface {
objc.NSObject
debugStubConfiguration()
}
type baseDebugStubConfiguration struct{}
func (*baseDebugStubConfiguration) debugStubConfiguration() {}
// GDBDebugStubConfiguration is a configuration for gdb debugging.
type GDBDebugStubConfiguration struct {
*pointer
*baseDebugStubConfiguration
}
var _ DebugStubConfiguration = (*GDBDebugStubConfiguration)(nil)
// NewGDBDebugStubConfiguration creates a new GDB debug confiuration.
//
// This API is not officially published and is subject to change without notice.
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewGDBDebugStubConfiguration(port uint32) (*GDBDebugStubConfiguration, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
config := &GDBDebugStubConfiguration{
pointer: objc.NewPointer(
C.newVZGDBDebugStubConfiguration(C.uint32_t(port)),
),
}
objc.SetFinalizer(config, func(self *GDBDebugStubConfiguration) {
objc.Release(self)
})
return config, nil
}
// SetDebugStubVirtualMachineConfiguration sets debug stub configuration. Empty by default.
//
// This API is not officially published and is subject to change without notice.
func (v *VirtualMachineConfiguration) SetDebugStubVirtualMachineConfiguration(dc DebugStubConfiguration) {
C.setDebugStubVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(dc))
}

View File

@@ -0,0 +1,23 @@
package vz
import (
"os"
)
// CreateDiskImage is creating disk image with specified filename and filesize.
// For example, if you want to create disk with 64GiB, you can set "64 * 1024 * 1024 * 1024" to size.
//
// Note that if you have specified a pathname which already exists, this function
// returns os.ErrExist error. So you can handle it with os.IsExist function.
func CreateDiskImage(pathname string, size int64) error {
f, err := os.OpenFile(pathname, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
if err != nil {
return err
}
defer f.Close()
if err := f.Truncate(size); err != nil {
return err
}
return nil
}

View File

@@ -1,12 +1,14 @@
package vz
/*
#cgo darwin CFLAGS: -x objective-c -fno-objc-arc
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization.h"
# include "virtualization_11.h"
*/
import "C"
import "runtime"
import (
"github.com/Code-Hex/vz/v3/internal/objc"
)
// VirtioEntropyDeviceConfiguration is used to expose a source of entropy for the guest operating systems random-number generator.
// When you create this object and add it to your virtual machines configuration, the virtual machine configures a Virtio-compliant
@@ -14,18 +16,25 @@ import "runtime"
//
// see: https://developer.apple.com/documentation/virtualization/vzvirtioentropydeviceconfiguration?language=objc
type VirtioEntropyDeviceConfiguration struct {
pointer
*pointer
}
// NewVirtioEntropyDeviceConfiguration creates a new Virtio Entropy Device confiuration.
func NewVirtioEntropyDeviceConfiguration() *VirtioEntropyDeviceConfiguration {
config := &VirtioEntropyDeviceConfiguration{
pointer: pointer{
ptr: C.newVZVirtioEntropyDeviceConfiguration(),
},
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewVirtioEntropyDeviceConfiguration() (*VirtioEntropyDeviceConfiguration, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
runtime.SetFinalizer(config, func(self *VirtioEntropyDeviceConfiguration) {
self.Release()
config := &VirtioEntropyDeviceConfiguration{
pointer: objc.NewPointer(
C.newVZVirtioEntropyDeviceConfiguration(),
),
}
objc.SetFinalizer(config, func(self *VirtioEntropyDeviceConfiguration) {
objc.Release(self)
})
return config
return config, nil
}

View File

@@ -0,0 +1,51 @@
// Code generated by "stringer -type=ErrorCode"; DO NOT EDIT.
package vz
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[ErrorInternal-1]
_ = x[ErrorInvalidVirtualMachineConfiguration-2]
_ = x[ErrorInvalidVirtualMachineState-3]
_ = x[ErrorInvalidVirtualMachineStateTransition-4]
_ = x[ErrorInvalidDiskImage-5]
_ = x[ErrorVirtualMachineLimitExceeded-6]
_ = x[ErrorNetworkError-7]
_ = x[ErrorOutOfDiskSpace-8]
_ = x[ErrorOperationCancelled-9]
_ = x[ErrorNotSupported-10]
_ = x[ErrorRestoreImageCatalogLoadFailed-10001]
_ = x[ErrorInvalidRestoreImageCatalog-10002]
_ = x[ErrorNoSupportedRestoreImagesInCatalog-10003]
_ = x[ErrorRestoreImageLoadFailed-10004]
_ = x[ErrorInvalidRestoreImage-10005]
_ = x[ErrorInstallationRequiresUpdate-10006]
_ = x[ErrorInstallationFailed-10007]
}
const (
_ErrorCode_name_0 = "ErrorInternalErrorInvalidVirtualMachineConfigurationErrorInvalidVirtualMachineStateErrorInvalidVirtualMachineStateTransitionErrorInvalidDiskImageErrorVirtualMachineLimitExceededErrorNetworkErrorErrorOutOfDiskSpaceErrorOperationCancelledErrorNotSupported"
_ErrorCode_name_1 = "ErrorRestoreImageCatalogLoadFailedErrorInvalidRestoreImageCatalogErrorNoSupportedRestoreImagesInCatalogErrorRestoreImageLoadFailedErrorInvalidRestoreImageErrorInstallationRequiresUpdateErrorInstallationFailed"
)
var (
_ErrorCode_index_0 = [...]uint8{0, 13, 52, 83, 124, 145, 177, 194, 213, 236, 253}
_ErrorCode_index_1 = [...]uint8{0, 34, 65, 103, 130, 154, 185, 208}
)
func (i ErrorCode) String() string {
switch {
case 1 <= i && i <= 10:
i -= 1
return _ErrorCode_name_0[_ErrorCode_index_0[i]:_ErrorCode_index_0[i+1]]
case 10001 <= i && i <= 10007:
i -= 10001
return _ErrorCode_name_1[_ErrorCode_index_1[i]:_ErrorCode_index_1[i+1]]
default:
return "ErrorCode(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

View File

@@ -0,0 +1,98 @@
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_13.h"
*/
import "C"
import (
"github.com/Code-Hex/vz/v3/internal/objc"
)
// GraphicsDeviceConfiguration is an interface for a graphics device configuration.
type GraphicsDeviceConfiguration interface {
objc.NSObject
graphicsDeviceConfiguration()
}
type baseGraphicsDeviceConfiguration struct{}
func (*baseGraphicsDeviceConfiguration) graphicsDeviceConfiguration() {}
// VirtioGraphicsDeviceConfiguration is configuration that represents the configuration
// of a Virtio graphics device for a Linux VM.
//
// This device configuration creates a graphics device using paravirtualization.
// The emulated device follows the Virtio GPU Device specification.
//
// see: https://developer.apple.com/documentation/virtualization/vzvirtiographicsdeviceconfiguration?language=objc
type VirtioGraphicsDeviceConfiguration struct {
*pointer
*baseGraphicsDeviceConfiguration
}
var _ GraphicsDeviceConfiguration = (*VirtioGraphicsDeviceConfiguration)(nil)
// NewVirtioGraphicsDeviceConfiguration creates a new Virtio graphics device.
//
// This is only supported on macOS 13 and newer, error will
// be returned on older versions.
func NewVirtioGraphicsDeviceConfiguration() (*VirtioGraphicsDeviceConfiguration, error) {
if err := macOSAvailable(13); err != nil {
return nil, err
}
graphicsConfiguration := &VirtioGraphicsDeviceConfiguration{
pointer: objc.NewPointer(
C.newVZVirtioGraphicsDeviceConfiguration(),
),
}
objc.SetFinalizer(graphicsConfiguration, func(self *VirtioGraphicsDeviceConfiguration) {
objc.Release(self)
})
return graphicsConfiguration, nil
}
// SetScanouts sets the displays associated with this graphics device.
//
// Maximum of one scanout is supported.
func (v *VirtioGraphicsDeviceConfiguration) SetScanouts(scanoutConfigs ...*VirtioGraphicsScanoutConfiguration) {
ptrs := make([]objc.NSObject, len(scanoutConfigs))
for i, val := range scanoutConfigs {
ptrs[i] = val
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setScanoutsVZVirtioGraphicsDeviceConfiguration(objc.Ptr(v), objc.Ptr(array))
}
// VirtioGraphicsScanoutConfiguration is the configuration for a Virtio graphics device
// that configures the dimensions of the graphics device for a Linux VM.
// see: https://developer.apple.com/documentation/virtualization/vzvirtiographicsscanoutconfiguration?language=objc
type VirtioGraphicsScanoutConfiguration struct {
*pointer
}
// NewVirtioGraphicsScanoutConfiguration creates a Virtio graphics device with the specified dimensions.
//
// This is only supported on macOS 13 and newer, error will
// be returned on older versions.
func NewVirtioGraphicsScanoutConfiguration(widthInPixels int64, heightInPixels int64) (*VirtioGraphicsScanoutConfiguration, error) {
if err := macOSAvailable(13); err != nil {
return nil, err
}
graphicsScanoutConfiguration := &VirtioGraphicsScanoutConfiguration{
pointer: objc.NewPointer(
C.newVZVirtioGraphicsScanoutConfiguration(
C.NSInteger(widthInPixels),
C.NSInteger(heightInPixels),
),
),
}
objc.SetFinalizer(graphicsScanoutConfiguration, func(self *VirtioGraphicsScanoutConfiguration) {
objc.Release(self)
})
return graphicsScanoutConfiguration, nil
}

View File

@@ -0,0 +1,84 @@
//go:build darwin && arm64
// +build darwin,arm64
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_12_arm64.h"
*/
import "C"
import (
"github.com/Code-Hex/vz/v3/internal/objc"
)
// MacGraphicsDeviceConfiguration is a configuration for a display attached to a Mac graphics device.
type MacGraphicsDeviceConfiguration struct {
*pointer
*baseGraphicsDeviceConfiguration
}
var _ GraphicsDeviceConfiguration = (*MacGraphicsDeviceConfiguration)(nil)
// NewMacGraphicsDeviceConfiguration creates a new MacGraphicsDeviceConfiguration.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewMacGraphicsDeviceConfiguration() (*MacGraphicsDeviceConfiguration, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
graphicsConfiguration := &MacGraphicsDeviceConfiguration{
pointer: objc.NewPointer(
C.newVZMacGraphicsDeviceConfiguration(),
),
}
objc.SetFinalizer(graphicsConfiguration, func(self *MacGraphicsDeviceConfiguration) {
objc.Release(self)
})
return graphicsConfiguration, nil
}
// SetDisplays sets the displays associated with this graphics device.
func (m *MacGraphicsDeviceConfiguration) SetDisplays(displayConfigs ...*MacGraphicsDisplayConfiguration) {
ptrs := make([]objc.NSObject, len(displayConfigs))
for i, val := range displayConfigs {
ptrs[i] = val
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setDisplaysVZMacGraphicsDeviceConfiguration(objc.Ptr(m), objc.Ptr(array))
}
// MacGraphicsDisplayConfiguration is the configuration for a Mac graphics device.
type MacGraphicsDisplayConfiguration struct {
*pointer
}
// NewMacGraphicsDisplayConfiguration creates a new MacGraphicsDisplayConfiguration.
//
// Creates a display configuration with the specified pixel dimensions and pixel density.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewMacGraphicsDisplayConfiguration(widthInPixels int64, heightInPixels int64, pixelsPerInch int64) (*MacGraphicsDisplayConfiguration, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
graphicsDisplayConfiguration := &MacGraphicsDisplayConfiguration{
pointer: objc.NewPointer(
C.newVZMacGraphicsDisplayConfiguration(
C.NSInteger(widthInPixels),
C.NSInteger(heightInPixels),
C.NSInteger(pixelsPerInch),
),
),
}
objc.SetFinalizer(graphicsDisplayConfiguration, func(self *MacGraphicsDisplayConfiguration) {
objc.Release(self)
})
return graphicsDisplayConfiguration, nil
}

View File

@@ -0,0 +1,10 @@
//go:build !go1.18
// +build !go1.18
package objc
import "runtime"
func SetFinalizer(obj interface{}, finalizer interface{}) {
runtime.SetFinalizer(obj, finalizer)
}

View File

@@ -0,0 +1,10 @@
//go:build go1.18
// +build go1.18
package objc
import "runtime"
func SetFinalizer[T any](obj T, finalizer func(T)) {
runtime.SetFinalizer(obj, finalizer)
}

View File

@@ -0,0 +1,160 @@
package objc
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c
#cgo darwin LDFLAGS: -lobjc -framework Foundation
#import <Foundation/Foundation.h>
void *makeNSMutableArray(unsigned long cap)
{
return [[NSMutableArray alloc] initWithCapacity:(NSUInteger)cap];
}
void addNSMutableArrayVal(void *ary, void *val)
{
[(NSMutableArray *)ary addObject:(NSObject *)val];
}
void *makeNSMutableDictionary()
{
return [[NSMutableDictionary alloc] init];
}
void insertNSMutableDictionary(void *dict, char *key, void *val)
{
NSString *nskey = [NSString stringWithUTF8String: key];
[(NSMutableDictionary *)dict setValue:(NSObject *)val forKey:nskey];
}
void releaseNSObject(void* o)
{
@autoreleasepool {
[(NSObject*)o release];
}
}
static inline void releaseDispatch(void *queue)
{
dispatch_release((dispatch_queue_t)queue);
}
int getNSArrayCount(void *ptr)
{
return (int)[(NSArray*)ptr count];
}
void* getNSArrayItem(void *ptr, int i)
{
NSArray *arr = (NSArray *)ptr;
return [arr objectAtIndex:i];
}
const char *getUUID()
{
NSString *uuid = [[NSUUID UUID] UUIDString];
return [uuid UTF8String];
}
*/
import "C"
import (
"runtime"
"unsafe"
)
// ReleaseDispatch releases allocated dispatch_queue_t
func ReleaseDispatch(p unsafe.Pointer) {
C.releaseDispatch(p)
}
// Pointer indicates any pointers which are allocated in objective-c world.
type Pointer struct {
_ptr unsafe.Pointer
}
// NewPointer creates a new Pointer for objc
func NewPointer(p unsafe.Pointer) *Pointer {
return &Pointer{_ptr: p}
}
// release releases allocated resources in objective-c world.
func (p *Pointer) release() {
C.releaseNSObject(p._ptr)
runtime.KeepAlive(p)
}
// Ptr returns raw pointer.
func (o *Pointer) ptr() unsafe.Pointer {
if o == nil {
return nil
}
return o._ptr
}
// NSObject indicates NSObject
type NSObject interface {
ptr() unsafe.Pointer
release()
}
// Release releases allocated resources in objective-c world.
func Release(o NSObject) {
o.release()
}
// Ptr returns unsafe.Pointer of the NSObject
func Ptr(o NSObject) unsafe.Pointer {
return o.ptr()
}
// NSArray indicates NSArray
type NSArray struct {
*Pointer
}
// NewNSArray creates a new NSArray from pointer.
func NewNSArray(p unsafe.Pointer) *NSArray {
return &NSArray{NewPointer(p)}
}
// ToPointerSlice method returns slice of the obj-c object as unsafe.Pointer.
func (n *NSArray) ToPointerSlice() []unsafe.Pointer {
count := int(C.getNSArrayCount(n.ptr()))
ret := make([]unsafe.Pointer, count)
for i := 0; i < count; i++ {
ret[i] = C.getNSArrayItem(n.ptr(), C.int(i))
}
return ret
}
// ConvertToNSMutableArray converts to NSMutableArray from NSObject slice in Go world.
func ConvertToNSMutableArray(s []NSObject) *Pointer {
ln := len(s)
ary := C.makeNSMutableArray(C.ulong(ln))
for _, v := range s {
C.addNSMutableArrayVal(ary, v.ptr())
}
p := NewPointer(ary)
runtime.SetFinalizer(p, func(self *Pointer) {
self.release()
})
return p
}
// ConvertToNSMutableDictionary converts to NSMutableDictionary from map[string]NSObject in Go world.
func ConvertToNSMutableDictionary(d map[string]NSObject) *Pointer {
dict := C.makeNSMutableDictionary()
for key, value := range d {
cs := (*C.char)(C.CString(key))
C.insertNSMutableDictionary(dict, cs, value.ptr())
C.free(unsafe.Pointer(cs))
}
p := NewPointer(dict)
runtime.SetFinalizer(p, func(self *Pointer) {
self.release()
})
return p
}
func GetUUID() *C.char {
return C.getUUID()
}

View File

@@ -0,0 +1,62 @@
package progress
import (
"io"
"sync"
"sync/atomic"
)
// Reader is an io.Reader for checking progress.
type Reader struct {
once sync.Once
reader io.Reader
total int64
current int64
finish chan struct{}
err error
}
// NewReader create a new io.Reader for checking progress.
func NewReader(rd io.Reader, total, current int64) *Reader {
return &Reader{
reader: rd,
total: total,
current: current,
finish: make(chan struct{}),
}
}
var _ io.Reader = (*Reader)(nil)
// Finish finishes the progress check operation.
func (r *Reader) Finish(err error) {
r.once.Do(func() {
r.err = err
close(r.finish)
})
}
// Err returns err.
func (r *Reader) Err() error { return r.err }
// Finish sends notification when finished any progress.
func (r *Reader) Finished() <-chan struct{} { return r.finish }
// FractionCompleted returns the fraction of the overall work completed by this progress struct,
// including work done by any children it may have.
func (r *Reader) FractionCompleted() float64 {
return float64(r.Current()) / float64(r.total)
}
func (r *Reader) Current() int64 {
return atomic.LoadInt64(&r.current)
}
// Read reads data using underlying io.Reader.
// The number of bytes read is added to the current progress status.
func (r *Reader) Read(p []byte) (n int, err error) {
n, err = r.reader.Read(p)
atomic.AddInt64(&r.current, int64(n))
return
}

View File

@@ -0,0 +1,49 @@
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_11.h"
# include "virtualization_12.h"
*/
import "C"
import (
"github.com/Code-Hex/vz/v3/internal/objc"
)
// KeyboardConfiguration interface for a keyboard configuration.
type KeyboardConfiguration interface {
objc.NSObject
keyboardConfiguration()
}
type baseKeyboardConfiguration struct{}
func (*baseKeyboardConfiguration) keyboardConfiguration() {}
// USBKeyboardConfiguration is a device that defines the configuration for a USB keyboard.
type USBKeyboardConfiguration struct {
*pointer
*baseKeyboardConfiguration
}
var _ KeyboardConfiguration = (*USBKeyboardConfiguration)(nil)
// NewUSBKeyboardConfiguration creates a new USB keyboard configuration.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewUSBKeyboardConfiguration() (*USBKeyboardConfiguration, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
config := &USBKeyboardConfiguration{
pointer: objc.NewPointer(C.newVZUSBKeyboardConfiguration()),
}
objc.SetFinalizer(config, func(self *USBKeyboardConfiguration) {
objc.Release(self)
})
return config, nil
}

View File

@@ -0,0 +1,28 @@
//go:build darwin && arm64
// +build darwin,arm64
// Code generated by "stringer -type=LinuxRosettaAvailability -output=linuxrosettaavailability_string_arm64.go"; DO NOT EDIT.
package vz
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[LinuxRosettaAvailabilityNotSupported-0]
_ = x[LinuxRosettaAvailabilityNotInstalled-1]
_ = x[LinuxRosettaAvailabilityInstalled-2]
}
const _LinuxRosettaAvailability_name = "LinuxRosettaAvailabilityNotSupportedLinuxRosettaAvailabilityNotInstalledLinuxRosettaAvailabilityInstalled"
var _LinuxRosettaAvailability_index = [...]uint8{0, 36, 72, 105}
func (i LinuxRosettaAvailability) String() string {
if i < 0 || i >= LinuxRosettaAvailability(len(_LinuxRosettaAvailability_index)-1) {
return "LinuxRosettaAvailability(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _LinuxRosettaAvailability_name[_LinuxRosettaAvailability_index[i]:_LinuxRosettaAvailability_index[i+1]]
}

View File

@@ -1,16 +1,18 @@
package vz
/*
#cgo darwin CFLAGS: -x objective-c -fno-objc-arc
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization.h"
# include "virtualization_11.h"
*/
import "C"
import "runtime"
import (
"github.com/Code-Hex/vz/v3/internal/objc"
)
// MemoryBalloonDeviceConfiguration for a memory balloon device configuration.
type MemoryBalloonDeviceConfiguration interface {
NSObject
objc.NSObject
memoryBalloonDeviceConfiguration()
}
@@ -25,20 +27,27 @@ var _ MemoryBalloonDeviceConfiguration = (*VirtioTraditionalMemoryBalloonDeviceC
//
// see: https://developer.apple.com/documentation/virtualization/vzvirtiotraditionalmemoryballoondeviceconfiguration?language=objc
type VirtioTraditionalMemoryBalloonDeviceConfiguration struct {
pointer
*pointer
*baseMemoryBalloonDeviceConfiguration
}
// NewVirtioTraditionalMemoryBalloonDeviceConfiguration creates a new VirtioTraditionalMemoryBalloonDeviceConfiguration.
func NewVirtioTraditionalMemoryBalloonDeviceConfiguration() *VirtioTraditionalMemoryBalloonDeviceConfiguration {
config := &VirtioTraditionalMemoryBalloonDeviceConfiguration{
pointer: pointer{
ptr: C.newVZVirtioTraditionalMemoryBalloonDeviceConfiguration(),
},
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewVirtioTraditionalMemoryBalloonDeviceConfiguration() (*VirtioTraditionalMemoryBalloonDeviceConfiguration, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
runtime.SetFinalizer(config, func(self *VirtioTraditionalMemoryBalloonDeviceConfiguration) {
self.Release()
config := &VirtioTraditionalMemoryBalloonDeviceConfiguration{
pointer: objc.NewPointer(
C.newVZVirtioTraditionalMemoryBalloonDeviceConfiguration(),
),
}
objc.SetFinalizer(config, func(self *VirtioTraditionalMemoryBalloonDeviceConfiguration) {
objc.Release(self)
})
return config
return config, nil
}

View File

@@ -1,15 +1,20 @@
package vz
/*
#cgo darwin CFLAGS: -x objective-c -fno-objc-arc
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization.h"
# include "virtualization_11.h"
# include "virtualization_13.h"
*/
import "C"
import (
"fmt"
"net"
"os"
"runtime"
"syscall"
"unsafe"
"github.com/Code-Hex/vz/v3/internal/objc"
)
// BridgedNetwork defines a network interface that bridges a physical interface with a virtual machine.
@@ -21,7 +26,7 @@ import (
// TODO(codehex): implement...
// see: https://developer.apple.com/documentation/virtualization/vzbridgednetworkinterface?language=objc
type BridgedNetwork interface {
NSObject
objc.NSObject
// NetworkInterfaces returns the list of network interfaces available for bridging.
NetworkInterfaces() []BridgedNetwork
@@ -40,7 +45,7 @@ type BridgedNetwork interface {
// for accesses to outside networks.
// see: https://developer.apple.com/documentation/virtualization/vznatnetworkdeviceattachment?language=objc
type NATNetworkDeviceAttachment struct {
pointer
*pointer
*baseNetworkDeviceAttachment
}
@@ -48,16 +53,21 @@ type NATNetworkDeviceAttachment struct {
var _ NetworkDeviceAttachment = (*NATNetworkDeviceAttachment)(nil)
// NewNATNetworkDeviceAttachment creates a new NATNetworkDeviceAttachment.
func NewNATNetworkDeviceAttachment() *NATNetworkDeviceAttachment {
attachment := &NATNetworkDeviceAttachment{
pointer: pointer{
ptr: C.newVZNATNetworkDeviceAttachment(),
},
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewNATNetworkDeviceAttachment() (*NATNetworkDeviceAttachment, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
runtime.SetFinalizer(attachment, func(self *NATNetworkDeviceAttachment) {
self.Release()
attachment := &NATNetworkDeviceAttachment{
pointer: objc.NewPointer(C.newVZNATNetworkDeviceAttachment()),
}
objc.SetFinalizer(attachment, func(self *NATNetworkDeviceAttachment) {
objc.Release(self)
})
return attachment
return attachment, nil
}
// BridgedNetworkDeviceAttachment represents a physical interface on the host computer.
@@ -71,7 +81,7 @@ func NewNATNetworkDeviceAttachment() *NATNetworkDeviceAttachment {
//
// see: https://developer.apple.com/documentation/virtualization/vzbridgednetworkdeviceattachment?language=objc
type BridgedNetworkDeviceAttachment struct {
pointer
*pointer
*baseNetworkDeviceAttachment
}
@@ -79,18 +89,25 @@ type BridgedNetworkDeviceAttachment struct {
var _ NetworkDeviceAttachment = (*BridgedNetworkDeviceAttachment)(nil)
// NewBridgedNetworkDeviceAttachment creates a new BridgedNetworkDeviceAttachment with networkInterface.
func NewBridgedNetworkDeviceAttachment(networkInterface BridgedNetwork) *BridgedNetworkDeviceAttachment {
attachment := &BridgedNetworkDeviceAttachment{
pointer: pointer{
ptr: C.newVZBridgedNetworkDeviceAttachment(
networkInterface.Ptr(),
),
},
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewBridgedNetworkDeviceAttachment(networkInterface BridgedNetwork) (*BridgedNetworkDeviceAttachment, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
runtime.SetFinalizer(attachment, func(self *BridgedNetworkDeviceAttachment) {
self.Release()
attachment := &BridgedNetworkDeviceAttachment{
pointer: objc.NewPointer(
C.newVZBridgedNetworkDeviceAttachment(
objc.Ptr(networkInterface),
),
),
}
objc.SetFinalizer(attachment, func(self *BridgedNetworkDeviceAttachment) {
objc.Release(self)
})
return attachment
return attachment, nil
}
// FileHandleNetworkDeviceAttachment sending raw network packets over a file handle.
@@ -99,9 +116,11 @@ func NewBridgedNetworkDeviceAttachment(networkInterface BridgedNetwork) *Bridged
// The data transmitted through this attachment is at the level of the data link layer.
// see: https://developer.apple.com/documentation/virtualization/vzfilehandlenetworkdeviceattachment?language=objc
type FileHandleNetworkDeviceAttachment struct {
pointer
*pointer
*baseNetworkDeviceAttachment
mtu int
}
var _ NetworkDeviceAttachment = (*FileHandleNetworkDeviceAttachment)(nil)
@@ -109,24 +128,90 @@ var _ NetworkDeviceAttachment = (*FileHandleNetworkDeviceAttachment)(nil)
// NewFileHandleNetworkDeviceAttachment initialize the attachment with a file handle.
//
// file parameter is holding a connected datagram socket.
func NewFileHandleNetworkDeviceAttachment(file *os.File) *FileHandleNetworkDeviceAttachment {
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewFileHandleNetworkDeviceAttachment(file *os.File) (*FileHandleNetworkDeviceAttachment, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
err := validateDatagramSocket(int(file.Fd()))
if err != nil {
return nil, err
}
attachment := &FileHandleNetworkDeviceAttachment{
pointer: pointer{
ptr: C.newVZFileHandleNetworkDeviceAttachment(
pointer: objc.NewPointer(
C.newVZFileHandleNetworkDeviceAttachment(
C.int(file.Fd()),
),
},
),
mtu: 1500, // The default MTU is 1500.
}
runtime.SetFinalizer(attachment, func(self *FileHandleNetworkDeviceAttachment) {
self.Release()
objc.SetFinalizer(attachment, func(self *FileHandleNetworkDeviceAttachment) {
objc.Release(self)
})
return attachment
return attachment, nil
}
func validateDatagramSocket(fd int) error {
sotype, err := syscall.GetsockoptInt(
fd,
syscall.SOL_SOCKET,
syscall.SO_TYPE,
)
if err != nil {
return os.NewSyscallError("getsockopt", err)
}
if sotype == syscall.SOCK_DGRAM && isAvailableDatagram(fd) {
return nil
}
return fmt.Errorf("The fileHandle must be a datagram socket")
}
func isAvailableDatagram(fd int) bool {
lsa, _ := syscall.Getsockname(fd)
switch lsa.(type) {
case *syscall.SockaddrInet4, *syscall.SockaddrInet6, *syscall.SockaddrUnix:
return true
}
return false
}
// SetMaximumTransmissionUnit sets the maximum transmission unit (MTU) associated with this attachment.
//
// The maximum MTU allowed is 65535, and the minimum MTU allowed is 1500. An invalid MTU value will result in an invalid
// virtual machine configuration.
//
// The client side of the associated datagram socket must be properly configured with the appropriate values
// for SO_SNDBUF, and SO_RCVBUF. Set these using the setsockopt(_:_:_:_:_:) system call. The system expects
// the value of SO_RCVBUF to be at least double the value of SO_SNDBUF, and for optimal performance, the
// recommended value of SO_RCVBUF is four times the value of SO_SNDBUF.
//
// This is only supported on macOS 13 and newer, error will
// be returned on older versions.
func (f *FileHandleNetworkDeviceAttachment) SetMaximumTransmissionUnit(mtu int) error {
if err := macOSAvailable(13); err != nil {
return err
}
C.setMaximumTransmissionUnitVZFileHandleNetworkDeviceAttachment(
objc.Ptr(f),
C.NSInteger(mtu),
)
f.mtu = mtu
return nil
}
// MaximumTransmissionUnit returns the maximum transmission unit (MTU) associated with this attachment.
// The default MTU is 1500.
func (f *FileHandleNetworkDeviceAttachment) MaximumTransmissionUnit() int {
return f.mtu
}
// NetworkDeviceAttachment for a network device attachment.
// see: https://developer.apple.com/documentation/virtualization/vznetworkdeviceattachment?language=objc
type NetworkDeviceAttachment interface {
NSObject
objc.NSObject
networkDeviceAttachment()
}
@@ -144,64 +229,89 @@ func (*baseNetworkDeviceAttachment) networkDeviceAttachment() {}
//
// see: https://developer.apple.com/documentation/virtualization/vzvirtionetworkdeviceconfiguration?language=objc
type VirtioNetworkDeviceConfiguration struct {
pointer
*pointer
}
// NewVirtioNetworkDeviceConfiguration creates a new VirtioNetworkDeviceConfiguration with NetworkDeviceAttachment.
func NewVirtioNetworkDeviceConfiguration(attachment NetworkDeviceAttachment) *VirtioNetworkDeviceConfiguration {
config := &VirtioNetworkDeviceConfiguration{
pointer: pointer{
ptr: C.newVZVirtioNetworkDeviceConfiguration(
attachment.Ptr(),
),
},
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewVirtioNetworkDeviceConfiguration(attachment NetworkDeviceAttachment) (*VirtioNetworkDeviceConfiguration, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
runtime.SetFinalizer(config, func(self *VirtioNetworkDeviceConfiguration) {
self.Release()
config := newVirtioNetworkDeviceConfiguration(
C.newVZVirtioNetworkDeviceConfiguration(
objc.Ptr(attachment),
),
)
objc.SetFinalizer(config, func(self *VirtioNetworkDeviceConfiguration) {
objc.Release(self)
})
return config
return config, nil
}
func (v *VirtioNetworkDeviceConfiguration) SetMacAddress(macAddress *MACAddress) {
C.setNetworkDevicesVZMACAddress(v.Ptr(), macAddress.Ptr())
func newVirtioNetworkDeviceConfiguration(ptr unsafe.Pointer) *VirtioNetworkDeviceConfiguration {
return &VirtioNetworkDeviceConfiguration{
pointer: objc.NewPointer(ptr),
}
}
func (v *VirtioNetworkDeviceConfiguration) SetMACAddress(macAddress *MACAddress) {
C.setNetworkDevicesVZMACAddress(objc.Ptr(v), objc.Ptr(macAddress))
}
// MACAddress represents a media access control address (MAC address), the 48-bit ethernet address.
// see: https://developer.apple.com/documentation/virtualization/vzmacaddress?language=objc
type MACAddress struct {
pointer
*pointer
}
// NewMACAddress creates a new MACAddress with net.HardwareAddr (MAC address).
func NewMACAddress(macAddr net.HardwareAddr) *MACAddress {
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewMACAddress(macAddr net.HardwareAddr) (*MACAddress, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
macAddrChar := charWithGoString(macAddr.String())
defer macAddrChar.Free()
ma := &MACAddress{
pointer: pointer{
ptr: C.newVZMACAddress(macAddrChar.CString()),
},
pointer: objc.NewPointer(
C.newVZMACAddress(macAddrChar.CString()),
),
}
runtime.SetFinalizer(ma, func(self *MACAddress) {
self.Release()
objc.SetFinalizer(ma, func(self *MACAddress) {
objc.Release(self)
})
return ma
return ma, nil
}
// NewRandomLocallyAdministeredMACAddress creates a valid, random, unicast, locally administered address.
func NewRandomLocallyAdministeredMACAddress() *MACAddress {
ma := &MACAddress{
pointer: pointer{
ptr: C.newRandomLocallyAdministeredVZMACAddress(),
},
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewRandomLocallyAdministeredMACAddress() (*MACAddress, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
runtime.SetFinalizer(ma, func(self *MACAddress) {
self.Release()
ma := &MACAddress{
pointer: objc.NewPointer(
C.newRandomLocallyAdministeredVZMACAddress(),
),
}
objc.SetFinalizer(ma, func(self *MACAddress) {
objc.Release(self)
})
return ma
return ma, nil
}
func (m *MACAddress) String() string {
cstring := (*char)(C.getVZMACAddressString(m.Ptr()))
cstring := (*char)(C.getVZMACAddressString(objc.Ptr(m)))
return cstring.String()
}

View File

@@ -0,0 +1,111 @@
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation
# include "virtualization_helper.h"
*/
import "C"
import (
"errors"
"fmt"
"strconv"
"strings"
"sync"
"syscall"
"golang.org/x/mod/semver"
)
var (
// ErrUnsupportedOSVersion is returned when calling a method which is only
// available in newer macOS versions.
ErrUnsupportedOSVersion = errors.New("unsupported macOS version")
// ErrBuildTargetOSVersion indicates that the API is available but the
// running program has disabled it.
ErrBuildTargetOSVersion = errors.New("unsupported build target macOS version")
)
func macOSAvailable(version float64) error {
if macOSMajorMinorVersion() < version {
return ErrUnsupportedOSVersion
}
return macOSBuildTargetAvailable(version)
}
var (
majorMinorVersion float64
majorMinorVersionOnce interface{ Do(func()) } = &sync.Once{}
// This can be replaced in the test code to enable mock.
// It will not be changed in production.
sysctl = syscall.Sysctl
)
func fetchMajorMinorVersion() (float64, error) {
osver, err := sysctl("kern.osproductversion")
if err != nil {
return 0, err
}
prefix := "v"
majorMinor := strings.TrimPrefix(semver.MajorMinor(prefix+osver), prefix)
version, err := strconv.ParseFloat(majorMinor, 64)
if err != nil {
return 0, err
}
return version, nil
}
func macOSMajorMinorVersion() float64 {
majorMinorVersionOnce.Do(func() {
version, err := fetchMajorMinorVersion()
if err != nil {
panic(err)
}
majorMinorVersion = version
})
return majorMinorVersion
}
var (
maxAllowedVersion int
maxAllowedVersionOnce interface{ Do(func()) } = &sync.Once{}
getMaxAllowedVersion = func() int {
return int(C.mac_os_x_version_max_allowed())
}
)
func fetchMaxAllowedVersion() int {
maxAllowedVersionOnce.Do(func() {
maxAllowedVersion = getMaxAllowedVersion()
})
return maxAllowedVersion
}
// macOSBuildTargetAvailable checks whether the API available in a given version has been compiled.
func macOSBuildTargetAvailable(version float64) error {
allowedVersion := fetchMaxAllowedVersion()
if allowedVersion == 0 {
return fmt.Errorf("undefined __MAC_OS_X_VERSION_MAX_ALLOWED: %w", ErrBuildTargetOSVersion)
}
// FIXME(codehex): smart way
// This list from AvailabilityVersions.h
var target int
switch version {
case 11:
target = 110000 // __MAC_11_0
case 12:
target = 120000 // __MAC_12_0
case 12.3:
target = 120300 // __MAC_12_3
case 13:
target = 130000 // __MAC_13_0
}
if allowedVersion < target {
return fmt.Errorf("%w for %.1f", ErrBuildTargetOSVersion, version)
}
return nil
}

View File

@@ -0,0 +1,154 @@
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_11.h"
# include "virtualization_12.h"
# include "virtualization_13.h"
*/
import "C"
import (
"os"
"unsafe"
"github.com/Code-Hex/vz/v3/internal/objc"
)
// PlatformConfiguration is an interface for a platform configuration.
type PlatformConfiguration interface {
objc.NSObject
platformConfiguration()
}
type basePlatformConfiguration struct{}
func (*basePlatformConfiguration) platformConfiguration() {}
// GenericPlatformConfiguration is the platform configuration for a generic Intel or ARM virtual machine.
type GenericPlatformConfiguration struct {
*pointer
*basePlatformConfiguration
machineIdentifier *GenericMachineIdentifier
}
// MachineIdentifier returns the machine identifier.
func (m *GenericPlatformConfiguration) MachineIdentifier() *GenericMachineIdentifier {
return m.machineIdentifier
}
var _ PlatformConfiguration = (*GenericPlatformConfiguration)(nil)
// NewGenericPlatformConfiguration creates a new generic platform configuration.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewGenericPlatformConfiguration(opts ...GenericPlatformConfigurationOption) (*GenericPlatformConfiguration, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
platformConfig := &GenericPlatformConfiguration{
pointer: objc.NewPointer(
C.newVZGenericPlatformConfiguration(),
),
}
for _, optFunc := range opts {
if err := optFunc(platformConfig); err != nil {
return nil, err
}
}
objc.SetFinalizer(platformConfig, func(self *GenericPlatformConfiguration) {
objc.Release(self)
})
return platformConfig, nil
}
// GenericMachineIdentifier is a struct that represents a unique identifier
// for a virtual machine.
type GenericMachineIdentifier struct {
*pointer
dataRepresentation []byte
}
// NewGenericMachineIdentifierWithDataPath initialize a new machine identifier described by the specified pathname.
//
// This is only supported on macOS 13 and newer, error will
// be returned on older versions.
func NewGenericMachineIdentifierWithDataPath(pathname string) (*GenericMachineIdentifier, error) {
b, err := os.ReadFile(pathname)
if err != nil {
return nil, err
}
return NewGenericMachineIdentifierWithData(b)
}
// NewGenericMachineIdentifierWithData initialize a new machine identifier described by the specified data representation.
//
// This is only supported on macOS 13 and newer, error will
// be returned on older versions.
func NewGenericMachineIdentifierWithData(b []byte) (*GenericMachineIdentifier, error) {
if err := macOSAvailable(13); err != nil {
return nil, err
}
ptr := C.newVZGenericMachineIdentifierWithBytes(
unsafe.Pointer(&b[0]),
C.int(len(b)),
)
return newGenericMachineIdentifier(ptr), nil
}
// NewGenericMachineIdentifier initialize a new machine identifier is used by guests to uniquely
// identify the virtual hardware.
//
// Two virtual machines running concurrently should not use the same identifier.
//
// If the virtual machine is serialized to disk, the identifier can be preserved in a binary representation through
// DataRepresentation method.
// The identifier can then be recreated with NewGenericMachineIdentifierWithData function from the binary representation.
//
// This is only supported on macOS 13 and newer, error will
// be returned on older versions.
func NewGenericMachineIdentifier() (*GenericMachineIdentifier, error) {
if err := macOSAvailable(13); err != nil {
return nil, err
}
return newGenericMachineIdentifier(C.newVZGenericMachineIdentifier()), nil
}
func newGenericMachineIdentifier(ptr unsafe.Pointer) *GenericMachineIdentifier {
dataRepresentation := C.getVZGenericMachineIdentifierDataRepresentation(ptr)
bytePointer := (*byte)(unsafe.Pointer(dataRepresentation.ptr))
return &GenericMachineIdentifier{
pointer: objc.NewPointer(ptr),
// https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
dataRepresentation: unsafe.Slice(bytePointer, dataRepresentation.len),
}
}
// DataRepresentation opaque data representation of the machine identifier.
// This can be used to recreate the same machine identifier with NewGenericMachineIdentifierWithData function.
func (g *GenericMachineIdentifier) DataRepresentation() []byte { return g.dataRepresentation }
// GenericPlatformConfigurationOption is an optional function to create its configuration.
type GenericPlatformConfigurationOption func(*GenericPlatformConfiguration) error
// WithGenericMachineIdentifier is an option to create a new GenericPlatformConfiguration.
//
// This is only supported on macOS 13 and newer, error will
// be returned on older versions.
func WithGenericMachineIdentifier(m *GenericMachineIdentifier) GenericPlatformConfigurationOption {
return func(mpc *GenericPlatformConfiguration) error {
if err := macOSAvailable(13); err != nil {
return err
}
mpc.machineIdentifier = m
C.setMachineIdentifierVZGenericPlatformConfiguration(objc.Ptr(mpc), objc.Ptr(m))
return nil
}
}

View File

@@ -0,0 +1,101 @@
//go:build darwin && arm64
// +build darwin,arm64
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_12_arm64.h"
*/
import "C"
import (
"github.com/Code-Hex/vz/v3/internal/objc"
)
// MacPlatformConfiguration is the platform configuration for booting macOS on Apple silicon.
//
// When creating a VM, the hardwareModel and auxiliaryStorage depend on the restore image that you use to install macOS.
//
// To choose the hardware model, start from MacOSRestoreImage.MostFeaturefulSupportedConfiguration method to get a supported
// configuration, then use its MacOSConfigurationRequirements.HardwareModel method to get the hardware model.
//
// Use the hardware model to set up MacPlatformConfiguration and to initialize a new auxiliary storage with
// `WithCreatingStorage` functional option of the `NewMacAuxiliaryStorage`.
//
// When you save a VM to disk and load it again, you must restore the HardwareModel, MachineIdentifier and
// AuxiliaryStorage methods to their original values.
//
// If you create multiple VMs from the same configuration, each should have a unique auxiliaryStorage and machineIdentifier.
type MacPlatformConfiguration struct {
*pointer
*basePlatformConfiguration
hardwareModel *MacHardwareModel
machineIdentifier *MacMachineIdentifier
auxiliaryStorage *MacAuxiliaryStorage
}
var _ PlatformConfiguration = (*MacPlatformConfiguration)(nil)
// MacPlatformConfigurationOption is an optional function to create its configuration.
type MacPlatformConfigurationOption func(*MacPlatformConfiguration)
// WithMacHardwareModel is an option to create a new MacPlatformConfiguration.
func WithMacHardwareModel(m *MacHardwareModel) MacPlatformConfigurationOption {
return func(mpc *MacPlatformConfiguration) {
mpc.hardwareModel = m
C.setHardwareModelVZMacPlatformConfiguration(objc.Ptr(mpc), objc.Ptr(m))
}
}
// WithMacMachineIdentifier is an option to create a new MacPlatformConfiguration.
func WithMacMachineIdentifier(m *MacMachineIdentifier) MacPlatformConfigurationOption {
return func(mpc *MacPlatformConfiguration) {
mpc.machineIdentifier = m
C.setMachineIdentifierVZMacPlatformConfiguration(objc.Ptr(mpc), objc.Ptr(m))
}
}
// WithMacAuxiliaryStorage is an option to create a new MacPlatformConfiguration.
func WithMacAuxiliaryStorage(m *MacAuxiliaryStorage) MacPlatformConfigurationOption {
return func(mpc *MacPlatformConfiguration) {
mpc.auxiliaryStorage = m
C.setAuxiliaryStorageVZMacPlatformConfiguration(objc.Ptr(mpc), objc.Ptr(m))
}
}
// NewMacPlatformConfiguration creates a new MacPlatformConfiguration. see also it's document.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewMacPlatformConfiguration(opts ...MacPlatformConfigurationOption) (*MacPlatformConfiguration, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
platformConfig := &MacPlatformConfiguration{
pointer: objc.NewPointer(
C.newVZMacPlatformConfiguration(),
),
}
for _, optFunc := range opts {
optFunc(platformConfig)
}
objc.SetFinalizer(platformConfig, func(self *MacPlatformConfiguration) {
objc.Release(self)
})
return platformConfig, nil
}
// HardwareModel returns the Mac hardware model.
func (m *MacPlatformConfiguration) HardwareModel() *MacHardwareModel { return m.hardwareModel }
// MachineIdentifier returns the Mac machine identifier.
func (m *MacPlatformConfiguration) MachineIdentifier() *MacMachineIdentifier {
return m.machineIdentifier
}
// AuxiliaryStorage returns the Mac auxiliary storage.
func (m *MacPlatformConfiguration) AuxiliaryStorage() *MacAuxiliaryStorage { return m.auxiliaryStorage }

View File

@@ -0,0 +1,52 @@
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_11.h"
# include "virtualization_12.h"
*/
import "C"
import (
"github.com/Code-Hex/vz/v3/internal/objc"
)
// PointingDeviceConfiguration is an interface for a pointing device configuration.
type PointingDeviceConfiguration interface {
objc.NSObject
pointingDeviceConfiguration()
}
type basePointingDeviceConfiguration struct{}
func (*basePointingDeviceConfiguration) pointingDeviceConfiguration() {}
// USBScreenCoordinatePointingDeviceConfiguration is a struct that defines the configuration
// for a USB pointing device that reports absolute coordinates.
type USBScreenCoordinatePointingDeviceConfiguration struct {
*pointer
*basePointingDeviceConfiguration
}
var _ PointingDeviceConfiguration = (*USBScreenCoordinatePointingDeviceConfiguration)(nil)
// NewUSBScreenCoordinatePointingDeviceConfiguration creates a new USBScreenCoordinatePointingDeviceConfiguration.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewUSBScreenCoordinatePointingDeviceConfiguration() (*USBScreenCoordinatePointingDeviceConfiguration, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
config := &USBScreenCoordinatePointingDeviceConfiguration{
pointer: objc.NewPointer(
C.newVZUSBScreenCoordinatePointingDeviceConfiguration(),
),
}
objc.SetFinalizer(config, func(self *USBScreenCoordinatePointingDeviceConfiguration) {
objc.Release(self)
})
return config, nil
}

View File

@@ -0,0 +1,51 @@
//go:build darwin && arm64
// +build darwin,arm64
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_13_arm64.h"
*/
import "C"
import (
"github.com/Code-Hex/vz/v3/internal/objc"
)
// MacTrackpadConfiguration is a struct that defines the configuration
// for a Mac trackpad.
//
// This device is only recognized by virtual machines running macOS 13.0 and later.
// In order to support both macOS 13.0 and earlier guests, VirtualMachineConfiguration.pointingDevices
// can be set to an array containing both a MacTrackpadConfiguration and
// a USBScreenCoordinatePointingDeviceConfiguration object. macOS 13.0 and later guests will use
// the multi-touch trackpad device, while earlier versions of macOS will use the USB pointing device.
//
// see: https://developer.apple.com/documentation/virtualization/vzmactrackpadconfiguration?language=objc
type MacTrackpadConfiguration struct {
*pointer
*basePointingDeviceConfiguration
}
var _ PointingDeviceConfiguration = (*MacTrackpadConfiguration)(nil)
// NewMacTrackpadConfiguration creates a new MacTrackpadConfiguration.
//
// This is only supported on macOS 13 and newer, error will
// be returned on older versions.
func NewMacTrackpadConfiguration() (*MacTrackpadConfiguration, error) {
if err := macOSAvailable(13); err != nil {
return nil, err
}
config := &MacTrackpadConfiguration{
pointer: objc.NewPointer(
C.newVZMacTrackpadConfiguration(),
),
}
objc.SetFinalizer(config, func(self *MacTrackpadConfiguration) {
objc.Release(self)
})
return config, nil
}

View File

@@ -1,21 +1,22 @@
package vz
/*
#cgo darwin CFLAGS: -x objective-c -fno-objc-arc
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization.h"
# include "virtualization_11.h"
*/
import "C"
import (
"os"
"runtime"
"github.com/Code-Hex/vz/v3/internal/objc"
)
// SerialPortAttachment interface for a serial port attachment.
//
// A serial port attachment defines how the virtual machine's serial port interfaces with the host system.
type SerialPortAttachment interface {
NSObject
objc.NSObject
serialPortAttachment()
}
@@ -31,7 +32,7 @@ var _ SerialPortAttachment = (*FileHandleSerialPortAttachment)(nil)
// Data written to fileHandleForReading goes to the guest. Data sent from the guest appears on fileHandleForWriting.
// see: https://developer.apple.com/documentation/virtualization/vzfilehandleserialportattachment?language=objc
type FileHandleSerialPortAttachment struct {
pointer
*pointer
*baseSerialPortAttachment
}
@@ -40,19 +41,26 @@ type FileHandleSerialPortAttachment struct {
//
// read parameter is an *os.File for reading from the file.
// write parameter is an *os.File for writing to the file.
func NewFileHandleSerialPortAttachment(read, write *os.File) *FileHandleSerialPortAttachment {
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewFileHandleSerialPortAttachment(read, write *os.File) (*FileHandleSerialPortAttachment, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
attachment := &FileHandleSerialPortAttachment{
pointer: pointer{
ptr: C.newVZFileHandleSerialPortAttachment(
pointer: objc.NewPointer(
C.newVZFileHandleSerialPortAttachment(
C.int(read.Fd()),
C.int(write.Fd()),
),
},
),
}
runtime.SetFinalizer(attachment, func(self *FileHandleSerialPortAttachment) {
self.Release()
objc.SetFinalizer(attachment, func(self *FileHandleSerialPortAttachment) {
objc.Release(self)
})
return attachment
return attachment, nil
}
var _ SerialPortAttachment = (*FileSerialPortAttachment)(nil)
@@ -63,7 +71,7 @@ var _ SerialPortAttachment = (*FileSerialPortAttachment)(nil)
// No data is sent to the guest over serial with this attachment.
// see: https://developer.apple.com/documentation/virtualization/vzfileserialportattachment?language=objc
type FileSerialPortAttachment struct {
pointer
*pointer
*baseSerialPortAttachment
}
@@ -71,29 +79,35 @@ type FileSerialPortAttachment struct {
// NewFileSerialPortAttachment initialize the FileSerialPortAttachment from a path of a file.
// If error is not nil, used to report errors if intialization fails.
//
// - path of the file for the attachment on the local file system.
// - shouldAppend True if the file should be opened in append mode, false otherwise.
// When a file is opened in append mode, writing to that file will append to the end of it.
// - path of the file for the attachment on the local file system.
// - shouldAppend True if the file should be opened in append mode, false otherwise.
// When a file is opened in append mode, writing to that file will append to the end of it.
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewFileSerialPortAttachment(path string, shouldAppend bool) (*FileSerialPortAttachment, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
cpath := charWithGoString(path)
defer cpath.Free()
nserr := newNSErrorAsNil()
nserrPtr := nserr.Ptr()
nserrPtr := newNSErrorAsNil()
attachment := &FileSerialPortAttachment{
pointer: pointer{
ptr: C.newVZFileSerialPortAttachment(
pointer: objc.NewPointer(
C.newVZFileSerialPortAttachment(
cpath.CString(),
C.bool(shouldAppend),
&nserrPtr,
),
},
),
}
if err := newNSError(nserrPtr); err != nil {
return nil, err
}
runtime.SetFinalizer(attachment, func(self *FileSerialPortAttachment) {
self.Release()
objc.SetFinalizer(attachment, func(self *FileSerialPortAttachment) {
objc.Release(self)
})
return attachment, nil
}
@@ -104,20 +118,27 @@ func NewFileSerialPortAttachment(path string, shouldAppend bool) (*FileSerialPor
// The device sets up a single port on the Virtio console device.
// see: https://developer.apple.com/documentation/virtualization/vzvirtioconsoledeviceserialportconfiguration?language=objc
type VirtioConsoleDeviceSerialPortConfiguration struct {
pointer
*pointer
}
// NewVirtioConsoleDeviceSerialPortConfiguration creates a new NewVirtioConsoleDeviceSerialPortConfiguration.
func NewVirtioConsoleDeviceSerialPortConfiguration(attachment SerialPortAttachment) *VirtioConsoleDeviceSerialPortConfiguration {
config := &VirtioConsoleDeviceSerialPortConfiguration{
pointer: pointer{
ptr: C.newVZVirtioConsoleDeviceSerialPortConfiguration(
attachment.Ptr(),
),
},
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewVirtioConsoleDeviceSerialPortConfiguration(attachment SerialPortAttachment) (*VirtioConsoleDeviceSerialPortConfiguration, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
runtime.SetFinalizer(config, func(self *VirtioConsoleDeviceSerialPortConfiguration) {
self.Release()
config := &VirtioConsoleDeviceSerialPortConfiguration{
pointer: objc.NewPointer(
C.newVZVirtioConsoleDeviceSerialPortConfiguration(
objc.Ptr(attachment),
),
),
}
objc.SetFinalizer(config, func(self *VirtioConsoleDeviceSerialPortConfiguration) {
objc.Release(self)
})
return config
return config, nil
}

View File

@@ -0,0 +1,184 @@
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_11.h"
# include "virtualization_12.h"
# include "virtualization_13.h"
*/
import "C"
import (
"os"
"github.com/Code-Hex/vz/v3/internal/objc"
)
// DirectorySharingDeviceConfiguration for a directory sharing device configuration.
type DirectorySharingDeviceConfiguration interface {
objc.NSObject
directorySharingDeviceConfiguration()
}
type baseDirectorySharingDeviceConfiguration struct{}
func (*baseDirectorySharingDeviceConfiguration) directorySharingDeviceConfiguration() {}
var _ DirectorySharingDeviceConfiguration = (*VirtioFileSystemDeviceConfiguration)(nil)
// VirtioFileSystemDeviceConfiguration is a configuration of a Virtio file system device.
//
// see: https://developer.apple.com/documentation/virtualization/vzvirtiofilesystemdeviceconfiguration?language=objc
type VirtioFileSystemDeviceConfiguration struct {
*pointer
*baseDirectorySharingDeviceConfiguration
}
// NewVirtioFileSystemDeviceConfiguration create a new VirtioFileSystemDeviceConfiguration.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewVirtioFileSystemDeviceConfiguration(tag string) (*VirtioFileSystemDeviceConfiguration, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
tagChar := charWithGoString(tag)
defer tagChar.Free()
nserrPtr := newNSErrorAsNil()
fsdConfig := &VirtioFileSystemDeviceConfiguration{
pointer: objc.NewPointer(
C.newVZVirtioFileSystemDeviceConfiguration(tagChar.CString(), &nserrPtr),
),
}
if err := newNSError(nserrPtr); err != nil {
return nil, err
}
objc.SetFinalizer(fsdConfig, func(self *VirtioFileSystemDeviceConfiguration) {
objc.Release(self)
})
return fsdConfig, nil
}
// SetDirectoryShare sets the directory share associated with this configuration.
func (c *VirtioFileSystemDeviceConfiguration) SetDirectoryShare(share DirectoryShare) {
C.setVZVirtioFileSystemDeviceConfigurationShare(objc.Ptr(c), objc.Ptr(share))
}
// SharedDirectory is a shared directory.
type SharedDirectory struct {
*pointer
}
// NewSharedDirectory creates a new shared directory.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewSharedDirectory(dirPath string, readOnly bool) (*SharedDirectory, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
if _, err := os.Stat(dirPath); err != nil {
return nil, err
}
dirPathChar := charWithGoString(dirPath)
defer dirPathChar.Free()
sd := &SharedDirectory{
pointer: objc.NewPointer(
C.newVZSharedDirectory(dirPathChar.CString(), C.bool(readOnly)),
),
}
objc.SetFinalizer(sd, func(self *SharedDirectory) {
objc.Release(self)
})
return sd, nil
}
// DirectoryShare is the base interface for a directory share.
type DirectoryShare interface {
objc.NSObject
directoryShare()
}
type baseDirectoryShare struct{}
func (*baseDirectoryShare) directoryShare() {}
var _ DirectoryShare = (*SingleDirectoryShare)(nil)
// SingleDirectoryShare defines the directory share for a single directory.
type SingleDirectoryShare struct {
*pointer
*baseDirectoryShare
}
// NewSingleDirectoryShare creates a new single directory share.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewSingleDirectoryShare(share *SharedDirectory) (*SingleDirectoryShare, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
config := &SingleDirectoryShare{
pointer: objc.NewPointer(
C.newVZSingleDirectoryShare(objc.Ptr(share)),
),
}
objc.SetFinalizer(config, func(self *SingleDirectoryShare) {
objc.Release(self)
})
return config, nil
}
// MultipleDirectoryShare defines the directory share for multiple directories.
type MultipleDirectoryShare struct {
*pointer
*baseDirectoryShare
}
var _ DirectoryShare = (*MultipleDirectoryShare)(nil)
// NewMultipleDirectoryShare creates a new multiple directories share.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewMultipleDirectoryShare(shares map[string]*SharedDirectory) (*MultipleDirectoryShare, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
directories := make(map[string]objc.NSObject, len(shares))
for k, v := range shares {
directories[k] = v
}
dict := objc.ConvertToNSMutableDictionary(directories)
config := &MultipleDirectoryShare{
pointer: objc.NewPointer(
C.newVZMultipleDirectoryShare(objc.Ptr(dict)),
),
}
objc.SetFinalizer(config, func(self *MultipleDirectoryShare) {
objc.Release(self)
})
return config, nil
}
// MacOSGuestAutomountTag returns the macOS automount tag.
//
// A device configured with this tag will be automatically mounted in a macOS guest.
// This is only supported on macOS 13 and newer, error will be returned on older versions.
func MacOSGuestAutomountTag() (string, error) {
if err := macOSAvailable(13); err != nil {
return "", err
}
cstring := (*char)(C.getMacOSGuestAutomountTag())
return cstring.String(), nil
}

View File

@@ -0,0 +1,109 @@
//go:build darwin && arm64
// +build darwin,arm64
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_13_arm64.h"
*/
import "C"
import (
"runtime/cgo"
"unsafe"
"github.com/Code-Hex/vz/v3/internal/objc"
)
// LinuxRosettaAvailability represents an availability of Rosetta support for Linux binaries.
//
//go:generate go run ./cmd/addtags -tags=darwin,arm64 -file linuxrosettaavailability_string_arm64.go stringer -type=LinuxRosettaAvailability -output=linuxrosettaavailability_string_arm64.go
type LinuxRosettaAvailability int
const (
// LinuxRosettaAvailabilityNotSupported Rosetta support for Linux binaries is not available on the host system.
LinuxRosettaAvailabilityNotSupported LinuxRosettaAvailability = iota
// LinuxRosettaAvailabilityNotInstalled Rosetta support for Linux binaries is not installed on the host system.
LinuxRosettaAvailabilityNotInstalled
// LinuxRosettaAvailabilityInstalled Rosetta support for Linux is installed on the host system.
LinuxRosettaAvailabilityInstalled
)
//export linuxInstallRosettaWithCompletionHandler
func linuxInstallRosettaWithCompletionHandler(cgoHandlerPtr, errPtr unsafe.Pointer) {
cgoHandler := *(*cgo.Handle)(cgoHandlerPtr)
handler := cgoHandler.Value().(func(error))
if err := newNSError(errPtr); err != nil {
handler(err)
} else {
handler(nil)
}
}
// LinuxRosettaDirectoryShare directory share to enable Rosetta support for Linux binaries.
// see: https://developer.apple.com/documentation/virtualization/vzlinuxrosettadirectoryshare?language=objc
type LinuxRosettaDirectoryShare struct {
*pointer
*baseDirectoryShare
}
var _ DirectoryShare = (*LinuxRosettaDirectoryShare)(nil)
// NewLinuxRosettaDirectoryShare creates a new Rosetta directory share if Rosetta support
// for Linux binaries is installed.
//
// This is only supported on macOS 13 and newer, error will
// be returned on older versions.
func NewLinuxRosettaDirectoryShare() (*LinuxRosettaDirectoryShare, error) {
if err := macOSAvailable(13); err != nil {
return nil, err
}
nserrPtr := newNSErrorAsNil()
ds := &LinuxRosettaDirectoryShare{
pointer: objc.NewPointer(
C.newVZLinuxRosettaDirectoryShare(&nserrPtr),
),
}
if err := newNSError(nserrPtr); err != nil {
return nil, err
}
objc.SetFinalizer(ds, func(self *LinuxRosettaDirectoryShare) {
objc.Release(self)
})
return ds, nil
}
// LinuxRosettaDirectoryShareInstallRosetta download and install Rosetta support
// for Linux binaries if necessary.
//
// This is only supported on macOS 13 and newer, error will
// be returned on older versions.
func LinuxRosettaDirectoryShareInstallRosetta() error {
if err := macOSAvailable(13); err != nil {
return err
}
errCh := make(chan error, 1)
cgoHandler := cgo.NewHandle(func(err error) {
errCh <- err
})
C.linuxInstallRosetta(unsafe.Pointer(&cgoHandler))
return <-errCh
}
// LinuxRosettaDirectoryShareAvailability checks the availability of Rosetta support
// for the directory share.
//
// This is only supported on macOS 13 and newer, LinuxRosettaAvailabilityNotSupported will
// be returned on older versions.
func LinuxRosettaDirectoryShareAvailability() LinuxRosettaAvailability {
if err := macOSAvailable(13); err != nil {
return LinuxRosettaAvailabilityNotSupported
}
return LinuxRosettaAvailability(C.availabilityVZLinuxRosettaDirectoryShare())
}

View File

@@ -0,0 +1,330 @@
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_11.h"
*/
import "C"
import (
"fmt"
"net"
"os"
"runtime"
"runtime/cgo"
"sync"
"time"
"unsafe"
"github.com/Code-Hex/vz/v3/internal/objc"
)
// SocketDeviceConfiguration for a socket device configuration.
type SocketDeviceConfiguration interface {
objc.NSObject
socketDeviceConfiguration()
}
type baseSocketDeviceConfiguration struct{}
func (*baseSocketDeviceConfiguration) socketDeviceConfiguration() {}
var _ SocketDeviceConfiguration = (*VirtioSocketDeviceConfiguration)(nil)
// VirtioSocketDeviceConfiguration is a configuration of the Virtio socket device.
//
// This configuration creates a Virtio socket device for the guest which communicates with the host through the Virtio interface.
// Only one Virtio socket device can be used per virtual machine.
// see: https://developer.apple.com/documentation/virtualization/vzvirtiosocketdeviceconfiguration?language=objc
type VirtioSocketDeviceConfiguration struct {
*pointer
*baseSocketDeviceConfiguration
}
// NewVirtioSocketDeviceConfiguration creates a new VirtioSocketDeviceConfiguration.
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewVirtioSocketDeviceConfiguration() (*VirtioSocketDeviceConfiguration, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
config := newVirtioSocketDeviceConfiguration(C.newVZVirtioSocketDeviceConfiguration())
objc.SetFinalizer(config, func(self *VirtioSocketDeviceConfiguration) {
objc.Release(self)
})
return config, nil
}
func newVirtioSocketDeviceConfiguration(ptr unsafe.Pointer) *VirtioSocketDeviceConfiguration {
return &VirtioSocketDeviceConfiguration{
pointer: objc.NewPointer(ptr),
}
}
// VirtioSocketDevice a device that manages port-based connections between the guest system and the host computer.
//
// Dont create a VirtioSocketDevice struct directly. Instead, when you request a socket device in your configuration,
// the virtual machine creates it and you can get it via SocketDevices method.
// see: https://developer.apple.com/documentation/virtualization/vzvirtiosocketdevice?language=objc
type VirtioSocketDevice struct {
dispatchQueue unsafe.Pointer
*pointer
}
func newVirtioSocketDevice(ptr, dispatchQueue unsafe.Pointer) *VirtioSocketDevice {
return &VirtioSocketDevice{
dispatchQueue: dispatchQueue,
pointer: objc.NewPointer(ptr),
}
}
// Listen creates a new VirtioSocketListener which is a struct that listens for port-based connection requests
// from the guest operating system.
//
// Be sure to close the listener by calling `VirtioSocketListener.Close` after used this one.
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func (v *VirtioSocketDevice) Listen(port uint32) (*VirtioSocketListener, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
ch := make(chan connResults, 1) // should I increase more caps?
handler := cgo.NewHandle(func(conn *VirtioSocketConnection, err error) {
ch <- connResults{conn, err}
})
ptr := C.newVZVirtioSocketListener(
unsafe.Pointer(&handler),
)
listener := &VirtioSocketListener{
pointer: objc.NewPointer(ptr),
vsockDevice: v,
port: port,
handler: handler,
acceptch: ch,
}
C.VZVirtioSocketDevice_setSocketListenerForPort(
objc.Ptr(v),
v.dispatchQueue,
objc.Ptr(listener),
C.uint32_t(port),
)
return listener, nil
}
//export connectionHandler
func connectionHandler(connPtr, errPtr, cgoHandlerPtr unsafe.Pointer) {
cgoHandler := *(*cgo.Handle)(cgoHandlerPtr)
handler := cgoHandler.Value().(func(*VirtioSocketConnection, error))
defer cgoHandler.Delete()
// see: startHandler
if err := newNSError(errPtr); err != nil {
handler(nil, err)
} else {
conn, err := newVirtioSocketConnection(connPtr)
handler(conn, err)
}
}
// Connect Initiates a connection to the specified port of the guest operating system.
//
// This method initiates the connection asynchronously, and executes the completion handler when the results are available.
// If the guest operating system doesnt listen for connections to the specifed port, this method does nothing.
//
// For a successful connection, this method sets the sourcePort property of the resulting VZVirtioSocketConnection object to a random port number.
// see: https://developer.apple.com/documentation/virtualization/vzvirtiosocketdevice/3656677-connecttoport?language=objc
func (v *VirtioSocketDevice) Connect(port uint32) (*VirtioSocketConnection, error) {
ch := make(chan connResults, 1)
cgoHandler := cgo.NewHandle(func(conn *VirtioSocketConnection, err error) {
ch <- connResults{conn, err}
close(ch)
})
C.VZVirtioSocketDevice_connectToPort(
objc.Ptr(v),
v.dispatchQueue,
C.uint32_t(port),
unsafe.Pointer(&cgoHandler),
)
result := <-ch
runtime.KeepAlive(v)
return result.conn, result.err
}
type connResults struct {
conn *VirtioSocketConnection
err error
}
// VirtioSocketListener a struct that listens for port-based connection requests from the guest operating system.
//
// see: https://developer.apple.com/documentation/virtualization/vzvirtiosocketlistener?language=objc
type VirtioSocketListener struct {
*pointer
vsockDevice *VirtioSocketDevice
handler cgo.Handle
port uint32
acceptch chan connResults
closeOnce sync.Once
}
var _ net.Listener = (*VirtioSocketListener)(nil)
// Accept implements the Accept method in the Listener interface; it waits for the next call and returns a net.Conn.
func (v *VirtioSocketListener) Accept() (net.Conn, error) {
return v.AcceptVirtioSocketConnection()
}
// AcceptVirtioSocketConnection accepts the next incoming call and returns the new connection.
func (v *VirtioSocketListener) AcceptVirtioSocketConnection() (*VirtioSocketConnection, error) {
result := <-v.acceptch
return result.conn, result.err
}
// Close stops listening on the virtio socket.
func (v *VirtioSocketListener) Close() error {
v.closeOnce.Do(func() {
C.VZVirtioSocketDevice_removeSocketListenerForPort(
objc.Ptr(v.vsockDevice),
v.vsockDevice.dispatchQueue,
C.uint32_t(v.port),
)
v.handler.Delete()
})
return nil
}
// Addr returns the listener's network address, a *VirtioSocketListenerAddr.
func (v *VirtioSocketListener) Addr() net.Addr {
const VMADDR_CID_HOST = 2 // copied from unix pacage
return &VirtioSocketListenerAddr{
CID: VMADDR_CID_HOST,
Port: v.port,
}
}
// VirtioSocketListenerAddr represents a network end point address for the vsock protocol.
type VirtioSocketListenerAddr struct {
CID uint32
Port uint32
}
var _ net.Addr = (*VirtioSocketListenerAddr)(nil)
// Network returns "vsock".
func (a *VirtioSocketListenerAddr) Network() string { return "vsock" }
// String returns string of "<cid>:<port>"
func (a *VirtioSocketListenerAddr) String() string { return fmt.Sprintf("%d:%d", a.CID, a.Port) }
//export shouldAcceptNewConnectionHandler
func shouldAcceptNewConnectionHandler(cgoHandlerPtr, connPtr, devicePtr unsafe.Pointer) C.bool {
cgoHandler := *(*cgo.Handle)(cgoHandlerPtr)
handler := cgoHandler.Value().(func(*VirtioSocketConnection, error))
// see: startHandler
conn, err := newVirtioSocketConnection(connPtr)
go handler(conn, err)
return (C.bool)(true)
}
// VirtioSocketConnection is a port-based connection between the guest operating system and the host computer.
//
// You dont create connection objects directly. When the guest operating system initiates a connection, the virtual machine creates
// the connection object and passes it to the appropriate VirtioSocketListener struct, which forwards the object to its delegate.
//
// This is implemented net.Conn interface. This is generated from duplicated a file descriptor which is returned
// from virtualization.framework. macOS cannot connect directly to the Guest operating system using vsock. The vsock
// connection must always be made via virtualization.framework. The diagram looks like this.
//
// ┌─────────┐ ┌────────────────────────────┐ ┌────────────┐
// │ macOS │<─── unix socket ───>│ virtualization.framework │<─── vsock ───>│ Guest OS │
// └─────────┘ └────────────────────────────┘ └────────────┘
//
// You will notice that this is not vsock in using this library. However, all data this connection goes through to the vsock
// connection to which the Guest OS is connected.
//
// This struct does not have any pointers for objects of the Objective-C. Because the various values
// of the VZVirtioSocketConnection object handled by Objective-C are no longer needed after the conversion
// to the Go struct.
//
// see: https://developer.apple.com/documentation/virtualization/vzvirtiosocketconnection?language=objc
type VirtioSocketConnection struct {
rawConn net.Conn
destinationPort uint32
sourcePort uint32
}
var _ net.Conn = (*VirtioSocketConnection)(nil)
func newVirtioSocketConnection(ptr unsafe.Pointer) (*VirtioSocketConnection, error) {
vzVirtioSocketConnection := C.convertVZVirtioSocketConnection2Flat(ptr)
file := os.NewFile((uintptr)(vzVirtioSocketConnection.fileDescriptor), "")
defer file.Close()
rawConn, err := net.FileConn(file)
if err != nil {
return nil, err
}
conn := &VirtioSocketConnection{
rawConn: rawConn,
destinationPort: (uint32)(vzVirtioSocketConnection.destinationPort),
sourcePort: (uint32)(vzVirtioSocketConnection.sourcePort),
}
return conn, nil
}
// Read reads data from connection of the vsock protocol.
func (v *VirtioSocketConnection) Read(b []byte) (n int, err error) { return v.rawConn.Read(b) }
// Write writes data to the connection of the vsock protocol.
func (v *VirtioSocketConnection) Write(b []byte) (n int, err error) { return v.rawConn.Write(b) }
// Close will be called when caused something error in socket.
func (v *VirtioSocketConnection) Close() error {
return v.rawConn.Close()
}
// LocalAddr returns the local network address.
func (v *VirtioSocketConnection) LocalAddr() net.Addr { return v.rawConn.LocalAddr() }
// RemoteAddr returns the remote network address.
func (v *VirtioSocketConnection) RemoteAddr() net.Addr { return v.rawConn.RemoteAddr() }
// SetDeadline sets the read and write deadlines associated
// with the connection. It is equivalent to calling both
// SetReadDeadline and SetWriteDeadline.
func (v *VirtioSocketConnection) SetDeadline(t time.Time) error { return v.rawConn.SetDeadline(t) }
// SetReadDeadline sets the deadline for future Read calls
// and any currently-blocked Read call.
// A zero value for t means Read will not time out.
func (v *VirtioSocketConnection) SetReadDeadline(t time.Time) error {
return v.rawConn.SetReadDeadline(t)
}
// SetWriteDeadline sets the deadline for future Write calls
// and any currently-blocked Write call.
// Even if write times out, it may return n > 0, indicating that
// some of the data was successfully written.
// A zero value for t means Write will not time out.
func (v *VirtioSocketConnection) SetWriteDeadline(t time.Time) error {
return v.rawConn.SetWriteDeadline(t)
}
// DestinationPort returns the destination port number of the connection.
func (v *VirtioSocketConnection) DestinationPort() uint32 {
return v.destinationPort
}
// SourcePort returns the source port number of the connection.
func (v *VirtioSocketConnection) SourcePort() uint32 {
return v.sourcePort
}

View File

@@ -0,0 +1,211 @@
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_11.h"
# include "virtualization_12_3.h"
# include "virtualization_13.h"
*/
import "C"
import (
"os"
"github.com/Code-Hex/vz/v3/internal/objc"
)
type baseStorageDeviceAttachment struct{}
func (*baseStorageDeviceAttachment) storageDeviceAttachment() {}
// StorageDeviceAttachment for a storage device attachment.
//
// A storage device attachment defines how a virtual machine storage device interfaces with the host system.
// see: https://developer.apple.com/documentation/virtualization/vzstoragedeviceattachment?language=objc
type StorageDeviceAttachment interface {
objc.NSObject
storageDeviceAttachment()
}
var _ StorageDeviceAttachment = (*DiskImageStorageDeviceAttachment)(nil)
// DiskImageStorageDeviceAttachment is a storage device attachment using a disk image to implement the storage.
//
// This storage device attachment uses a disk image on the host file system as the drive of the storage device.
// Only raw data disk images are supported.
// see: https://developer.apple.com/documentation/virtualization/vzdiskimagestoragedeviceattachment?language=objc
type DiskImageStorageDeviceAttachment struct {
*pointer
*baseStorageDeviceAttachment
}
// NewDiskImageStorageDeviceAttachment initialize the attachment from a local file path.
// Returns error is not nil, assigned with the error if the initialization failed.
//
// - diskPath is local file URL to the disk image in RAW format.
// - readOnly if YES, the device attachment is read-only, otherwise the device can write data to the disk image.
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewDiskImageStorageDeviceAttachment(diskPath string, readOnly bool) (*DiskImageStorageDeviceAttachment, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
if _, err := os.Stat(diskPath); err != nil {
return nil, err
}
nserrPtr := newNSErrorAsNil()
diskPathChar := charWithGoString(diskPath)
defer diskPathChar.Free()
attachment := &DiskImageStorageDeviceAttachment{
pointer: objc.NewPointer(
C.newVZDiskImageStorageDeviceAttachment(
diskPathChar.CString(),
C.bool(readOnly),
&nserrPtr,
),
),
}
if err := newNSError(nserrPtr); err != nil {
return nil, err
}
objc.SetFinalizer(attachment, func(self *DiskImageStorageDeviceAttachment) {
objc.Release(self)
})
return attachment, nil
}
// StorageDeviceConfiguration for a storage device configuration.
type StorageDeviceConfiguration interface {
objc.NSObject
storageDeviceConfiguration()
}
type baseStorageDeviceConfiguration struct{}
func (*baseStorageDeviceConfiguration) storageDeviceConfiguration() {}
var _ StorageDeviceConfiguration = (*VirtioBlockDeviceConfiguration)(nil)
// VirtioBlockDeviceConfiguration is a configuration of a paravirtualized storage device of type Virtio Block Device.
//
// This device configuration creates a storage device using paravirtualization.
// The emulated device follows the Virtio Block Device specification.
//
// The host implementation of the device is done through an attachment subclassing VZStorageDeviceAttachment
// like VZDiskImageStorageDeviceAttachment.
// see: https://developer.apple.com/documentation/virtualization/vzvirtioblockdeviceconfiguration?language=objc
type VirtioBlockDeviceConfiguration struct {
*pointer
*baseStorageDeviceConfiguration
blockDeviceIdentifier string
}
// NewVirtioBlockDeviceConfiguration initialize a VZVirtioBlockDeviceConfiguration with a device attachment.
//
// - attachment The storage device attachment. This defines how the virtualized device operates on the host side.
//
// This is only supported on macOS 11 and newer, error will
// be returned on older versions.
func NewVirtioBlockDeviceConfiguration(attachment StorageDeviceAttachment) (*VirtioBlockDeviceConfiguration, error) {
if err := macOSAvailable(11); err != nil {
return nil, err
}
config := &VirtioBlockDeviceConfiguration{
pointer: objc.NewPointer(
C.newVZVirtioBlockDeviceConfiguration(
objc.Ptr(attachment),
),
),
}
objc.SetFinalizer(config, func(self *VirtioBlockDeviceConfiguration) {
objc.Release(self)
})
return config, nil
}
// BlockDeviceIdentifier returns the device identifier is a string identifying the Virtio block device.
// Empty string by default.
//
// The identifier can be retrieved in the guest via a VIRTIO_BLK_T_GET_ID request.
//
// This is only supported on macOS 12.3 and newer, error will be returned on older versions.
//
// see: https://developer.apple.com/documentation/virtualization/vzvirtioblockdeviceconfiguration/3917717-blockdeviceidentifier
func (v *VirtioBlockDeviceConfiguration) BlockDeviceIdentifier() (string, error) {
if err := macOSAvailable(12.3); err != nil {
return "", err
}
return v.blockDeviceIdentifier, nil
}
// SetBlockDeviceIdentifier sets the device identifier is a string identifying the Virtio block device.
//
// The device identifier must be at most 20 bytes in length and ASCII-encodable.
//
// This is only supported on macOS 12.3 and newer, error will be returned on older versions.
//
// see: https://developer.apple.com/documentation/virtualization/vzvirtioblockdeviceconfiguration/3917717-blockdeviceidentifier
func (v *VirtioBlockDeviceConfiguration) SetBlockDeviceIdentifier(identifier string) error {
if err := macOSAvailable(12.3); err != nil {
return err
}
idChar := charWithGoString(identifier)
defer idChar.Free()
nserrPtr := newNSErrorAsNil()
C.setBlockDeviceIdentifierVZVirtioBlockDeviceConfiguration(
objc.Ptr(v),
idChar.CString(),
&nserrPtr,
)
if err := newNSError(nserrPtr); err != nil {
return err
}
v.blockDeviceIdentifier = identifier
return nil
}
// USBMassStorageDeviceConfiguration is a configuration of a USB Mass Storage storage device.
//
// This device configuration creates a storage device that conforms to the USB Mass Storage specification.
//
// see: https://developer.apple.com/documentation/virtualization/vzusbmassstoragedeviceconfiguration?language=objc
type USBMassStorageDeviceConfiguration struct {
*pointer
*baseStorageDeviceConfiguration
// marking as currently reachable.
// This ensures that the object is not freed, and its finalizer is not run
attachment StorageDeviceAttachment
}
// NewUSBMassStorageDeviceConfiguration initialize a USBMassStorageDeviceConfiguration
// with a device attachment.
//
// This is only supported on macOS 13 and newer, error will
// be returned on older versions.
func NewUSBMassStorageDeviceConfiguration(attachment StorageDeviceAttachment) (*USBMassStorageDeviceConfiguration, error) {
if err := macOSAvailable(13); err != nil {
return nil, err
}
usbMass := &USBMassStorageDeviceConfiguration{
pointer: objc.NewPointer(
C.newVZUSBMassStorageDeviceConfiguration(objc.Ptr(attachment)),
),
attachment: attachment,
}
objc.SetFinalizer(usbMass, func(self *USBMassStorageDeviceConfiguration) {
objc.Release(self)
})
return usbMass, nil
}

View File

@@ -0,0 +1,345 @@
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"
"github.com/Code-Hex/vz/v3/internal/objc"
)
func init() {
C.sharedApplication()
}
// 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
status cgo.Handle
mu sync.Mutex
}
type machineStatus struct {
state VirtualMachineState
stateNotify chan 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())
status := cgo.NewHandle(&machineStatus{
state: VirtualMachineState(0),
stateNotify: make(chan VirtualMachineState),
})
v := &VirtualMachine{
id: cs.String(),
pointer: objc.NewPointer(
C.newVZVirtualMachineWithDispatchQueue(
objc.Ptr(config),
dispatchQueue,
unsafe.Pointer(&status),
),
),
dispatchQueue: dispatchQueue,
status: status,
}
objc.SetFinalizer(v, func(self *VirtualMachine) {
self.status.Delete()
objc.ReleaseDispatch(self.dispatchQueue)
objc.Release(self)
})
return v, nil
}
// 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(state C.int, cgoHandlerPtr unsafe.Pointer) {
status := *(*cgo.Handle)(cgoHandlerPtr)
// I expected it will not cause panic.
// if caused panic, that's unexpected behavior.
v, _ := status.Value().(*machineStatus)
v.mu.Lock()
newState := VirtualMachineState(state)
v.state = newState
// for non-blocking
go func() { v.stateNotify <- newState }()
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, _ := v.status.Value().(*machineStatus)
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, _ := v.status.Value().(*machineStatus)
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(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(cgoHandlerPtr, errPtr unsafe.Pointer) {
cgoHandler := *(*cgo.Handle)(cgoHandlerPtr)
handler := cgoHandler.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()
handler := cgo.NewHandle(h)
defer handler.Delete()
if o.macOSVirtualMachineStartOptionsPtr != nil {
C.startWithOptionsCompletionHandler(
objc.Ptr(v),
v.dispatchQueue,
o.macOSVirtualMachineStartOptionsPtr,
unsafe.Pointer(&handler),
)
} else {
C.startWithCompletionHandler(objc.Ptr(v), v.dispatchQueue, unsafe.Pointer(&handler))
}
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()
handler := cgo.NewHandle(h)
defer handler.Delete()
C.pauseWithCompletionHandler(objc.Ptr(v), v.dispatchQueue, unsafe.Pointer(&handler))
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()
handler := cgo.NewHandle(h)
defer handler.Delete()
C.resumeWithCompletionHandler(objc.Ptr(v), v.dispatchQueue, unsafe.Pointer(&handler))
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 thats 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()
handler := cgo.NewHandle(h)
defer handler.Delete()
C.stopWithCompletionHandler(objc.Ptr(v), v.dispatchQueue, unsafe.Pointer(&handler))
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
}

View File

@@ -1,24 +1,29 @@
//
// virtualization.h
// virtualization_11.h
//
// Created by codehex.
//
#pragma once
#import <Foundation/Foundation.h>
#import "virtualization_helper.h"
#import <Virtualization/Virtualization.h>
/* exported from cgo */
void startHandler(void *err, char *id);
void pauseHandler(void *err, char *id);
void resumeHandler(void *err, char *id);
void changeStateOnObserver(int state, char *id);
void connectionHandler(void *connection, void *err, void *cgoHandlerPtr);
void changeStateOnObserver(int state, void *cgoHandler);
bool shouldAcceptNewConnectionHandler(void *cgoHandler, void *connection, void *socketDevice);
@interface Observer : NSObject
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
@end
/* VZVirtioSocketListener */
@interface VZVirtioSocketListenerDelegateImpl : NSObject <VZVirtioSocketListenerDelegate>
- (instancetype)initWithHandler:(void *)cgoHandler;
- (BOOL)listener:(VZVirtioSocketListener *)listener shouldAcceptNewConnection:(VZVirtioSocketConnection *)connection fromSocketDevice:(VZVirtioSocketDevice *)socketDevice;
@end
/* BootLoader */
void *newVZLinuxBootLoader(const char *kernelPath);
void setCommandLineVZLinuxBootLoader(void *bootLoaderPtr, const char *commandLine);
@@ -26,21 +31,27 @@ void setInitialRamdiskURLVZLinuxBootLoader(void *bootLoaderPtr, const char *ramd
/* VirtualMachineConfiguration */
bool validateVZVirtualMachineConfiguration(void *config, void **error);
unsigned long long minimumAllowedMemorySizeVZVirtualMachineConfiguration();
unsigned long long maximumAllowedMemorySizeVZVirtualMachineConfiguration();
unsigned int minimumAllowedCPUCountVZVirtualMachineConfiguration();
unsigned int maximumAllowedCPUCountVZVirtualMachineConfiguration();
void *newVZVirtualMachineConfiguration(void *bootLoader,
unsigned int CPUCount,
unsigned long long memorySize);
unsigned int CPUCount,
unsigned long long memorySize);
void setEntropyDevicesVZVirtualMachineConfiguration(void *config,
void *entropyDevices);
void *entropyDevices);
void setMemoryBalloonDevicesVZVirtualMachineConfiguration(void *config,
void *memoryBalloonDevices);
void *memoryBalloonDevices);
void setNetworkDevicesVZVirtualMachineConfiguration(void *config,
void *networkDevices);
void *networkDevices);
void *networkDevicesVZVirtualMachineConfiguration(void *config);
void setSerialPortsVZVirtualMachineConfiguration(void *config,
void *serialPorts);
void *serialPorts);
void setSocketDevicesVZVirtualMachineConfiguration(void *config,
void *socketDevices);
void *socketDevices);
void *socketDevicesVZVirtualMachineConfiguration(void *config);
void setStorageDevicesVZVirtualMachineConfiguration(void *config,
void *storageDevices);
void *storageDevices);
/* Configurations */
void *newVZFileHandleSerialPortAttachment(int readFileDescriptor, int writeFileDescriptor);
@@ -59,16 +70,30 @@ void *newVZVirtioSocketDeviceConfiguration();
void *newVZMACAddress(const char *macAddress);
void *newRandomLocallyAdministeredVZMACAddress();
const char *getVZMACAddressString(void *macAddress);
void *newVZVirtioSocketListener(void *cgoHandlerPtr);
void *VZVirtualMachine_socketDevices(void *machine);
void VZVirtioSocketDevice_setSocketListenerForPort(void *socketDevice, void *vmQueue, void *listener, uint32_t port);
void VZVirtioSocketDevice_removeSocketListenerForPort(void *socketDevice, void *vmQueue, uint32_t port);
void VZVirtioSocketDevice_connectToPort(void *socketDevice, void *vmQueue, uint32_t port, void *cgoHandlerPtr);
/* VirtualMachine */
void *newVZVirtualMachineWithDispatchQueue(void *config, void *queue, const char *vmid);
void *newVZVirtualMachineWithDispatchQueue(void *config, void *queue, void *statusHandler);
bool requestStopVirtualMachine(void *machine, void *queue, void **error);
void startWithCompletionHandler(void *machine, void *queue, const char *vmid);
void pauseWithCompletionHandler(void *machine, void *queue, const char *vmid);
void resumeWithCompletionHandler(void *machine, void *queue, const char *vmid);
void startWithCompletionHandler(void *machine, void *queue, void *completionHandler);
void pauseWithCompletionHandler(void *machine, void *queue, void *completionHandler);
void resumeWithCompletionHandler(void *machine, void *queue, void *completionHandler);
bool vmCanStart(void *machine, void *queue);
bool vmCanPause(void *machine, void *queue);
bool vmCanResume(void *machine, void *queue);
bool vmCanRequestStop(void *machine, void *queue);
void *makeDispatchQueue(const char *label);
void *makeDispatchQueue(const char *label);
/* VZVirtioSocketConnection */
typedef struct VZVirtioSocketConnectionFlat {
uint32_t destinationPort;
uint32_t sourcePort;
int fileDescriptor;
} VZVirtioSocketConnectionFlat;
VZVirtioSocketConnectionFlat convertVZVirtioSocketConnection2Flat(void *connection);

View File

@@ -0,0 +1,887 @@
//
// virtualization_11.m
//
// Created by codehex.
//
#import "virtualization_11.h"
char *copyCString(NSString *nss)
{
const char *cc = [nss UTF8String];
char *c = calloc([nss length] + 1, 1);
strncpy(c, cc, [nss length]);
return c;
}
@implementation Observer
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
{
@autoreleasepool {
if ([keyPath isEqualToString:@"state"]) {
int newState = (int)[change[NSKeyValueChangeNewKey] integerValue];
changeStateOnObserver(newState, context);
} else {
// bool canVal = (bool)[change[NSKeyValueChangeNewKey] boolValue];
// char *vmid = copyCString((NSString *)context);
// char *key = copyCString(keyPath);
// changeCanPropertyOnObserver(canVal, vmid, key);
// free(vmid);
// free(key);
}
}
}
@end
@implementation VZVirtioSocketListenerDelegateImpl {
void *_cgoHandler;
}
- (instancetype)initWithHandler:(void *)cgoHandler
{
self = [super init];
_cgoHandler = cgoHandler;
return self;
}
- (BOOL)listener:(VZVirtioSocketListener *)listener shouldAcceptNewConnection:(VZVirtioSocketConnection *)connection fromSocketDevice:(VZVirtioSocketDevice *)socketDevice;
{
return (BOOL)shouldAcceptNewConnectionHandler(_cgoHandler, connection, socketDevice);
}
@end
/*!
@abstract Create a VZLinuxBootLoader with the Linux kernel passed as URL.
@param kernelPath Path of Linux kernel on the local file system.
*/
void *newVZLinuxBootLoader(const char *kernelPath)
{
if (@available(macOS 11, *)) {
NSString *kernelPathNSString = [NSString stringWithUTF8String:kernelPath];
NSURL *kernelURL = [NSURL fileURLWithPath:kernelPathNSString];
return [[VZLinuxBootLoader alloc] initWithKernelURL:kernelURL];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Set the command-line parameters.
@param bootLoader VZLinuxBootLoader
@param commandLine The command-line parameters passed to the kernel on boot.
@link https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html
*/
void setCommandLineVZLinuxBootLoader(void *bootLoaderPtr, const char *commandLine)
{
if (@available(macOS 11, *)) {
NSString *commandLineNSString = [NSString stringWithUTF8String:commandLine];
[(VZLinuxBootLoader *)bootLoaderPtr setCommandLine:commandLineNSString];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Set the optional initial RAM disk.
@param bootLoader VZLinuxBootLoader
@param ramdiskPath The RAM disk is mapped into memory before booting the kernel.
@link https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html
*/
void setInitialRamdiskURLVZLinuxBootLoader(void *bootLoaderPtr, const char *ramdiskPath)
{
if (@available(macOS 11, *)) {
NSString *ramdiskPathNSString = [NSString stringWithUTF8String:ramdiskPath];
NSURL *ramdiskURL = [NSURL fileURLWithPath:ramdiskPathNSString];
[(VZLinuxBootLoader *)bootLoaderPtr setInitialRamdiskURL:ramdiskURL];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Validate the configuration.
@param config Virtual machine configuration.
@param error If not nil, assigned with the validation error if the validation failed.
@return true if the configuration is valid.
*/
bool validateVZVirtualMachineConfiguration(void *config, void **error)
{
if (@available(macOS 11, *)) {
return (bool)[(VZVirtualMachineConfiguration *)config
validateWithError:(NSError *_Nullable *_Nullable)error];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract: Minimum amount of memory required by virtual machines.
@see VZVirtualMachineConfiguration.memorySize
*/
unsigned long long minimumAllowedMemorySizeVZVirtualMachineConfiguration()
{
if (@available(macOS 11, *)) {
return (unsigned long long)[VZVirtualMachineConfiguration minimumAllowedMemorySize];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract: Maximum amount of memory allowed for a virtual machine.
@see VZVirtualMachineConfiguration.memorySize
*/
unsigned long long maximumAllowedMemorySizeVZVirtualMachineConfiguration()
{
if (@available(macOS 11, *)) {
return (unsigned long long)[VZVirtualMachineConfiguration maximumAllowedMemorySize];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract: Minimum number of CPUs for a virtual machine.
@see VZVirtualMachineConfiguration.CPUCount
*/
unsigned int minimumAllowedCPUCountVZVirtualMachineConfiguration()
{
if (@available(macOS 11, *)) {
return (unsigned int)[VZVirtualMachineConfiguration minimumAllowedCPUCount];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract: Maximum number of CPUs for a virtual machine.
@see VZVirtualMachineConfiguration.CPUCount
*/
unsigned int maximumAllowedCPUCountVZVirtualMachineConfiguration()
{
if (@available(macOS 11, *)) {
return (unsigned int)[VZVirtualMachineConfiguration maximumAllowedCPUCount];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Create a new Virtual machine configuration.
@param bootLoader Boot loader used when the virtual machine starts.
@param CPUCount Number of CPUs.
@discussion
The number of CPUs must be a value between VZVirtualMachineConfiguration.minimumAllowedCPUCount
and VZVirtualMachineConfiguration.maximumAllowedCPUCount.
@see VZVirtualMachineConfiguration.minimumAllowedCPUCount
@see VZVirtualMachineConfiguration.maximumAllowedCPUCount
@param memorySize Virtual machine memory size in bytes.
@discussion
The memory size must be a multiple of a 1 megabyte (1024 * 1024 bytes) between VZVirtualMachineConfiguration.minimumAllowedMemorySize
and VZVirtualMachineConfiguration.maximumAllowedMemorySize.
The memorySize represents the total physical memory seen by a guest OS running in the virtual machine.
Not all memory is allocated on start, the virtual machine allocates memory on demand.
@see VZVirtualMachineConfiguration.minimumAllowedMemorySize
@see VZVirtualMachineConfiguration.maximumAllowedMemorySize
*/
void *newVZVirtualMachineConfiguration(void *bootLoaderPtr,
unsigned int CPUCount,
unsigned long long memorySize)
{
if (@available(macOS 11, *)) {
VZVirtualMachineConfiguration *config = [[VZVirtualMachineConfiguration alloc] init];
[config setBootLoader:(VZLinuxBootLoader *)bootLoaderPtr];
[config setCPUCount:(NSUInteger)CPUCount];
[config setMemorySize:memorySize];
return config;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract List of entropy devices. Empty by default.
@see VZVirtioEntropyDeviceConfiguration
*/
void setEntropyDevicesVZVirtualMachineConfiguration(void *config,
void *entropyDevices)
{
if (@available(macOS 11, *)) {
[(VZVirtualMachineConfiguration *)config setEntropyDevices:[(NSMutableArray *)entropyDevices copy]];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract List of memory balloon devices. Empty by default.
@see VZVirtioTraditionalMemoryBalloonDeviceConfiguration
*/
void setMemoryBalloonDevicesVZVirtualMachineConfiguration(void *config,
void *memoryBalloonDevices)
{
if (@available(macOS 11, *)) {
[(VZVirtualMachineConfiguration *)config setMemoryBalloonDevices:[(NSMutableArray *)memoryBalloonDevices copy]];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract List of network adapters. Empty by default.
@see VZVirtioNetworkDeviceConfiguration
*/
void setNetworkDevicesVZVirtualMachineConfiguration(void *config,
void *networkDevices)
{
if (@available(macOS 11, *)) {
[(VZVirtualMachineConfiguration *)config setNetworkDevices:[(NSMutableArray *)networkDevices copy]];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Return the list of network devices configurations for this VZVirtualMachineConfiguration. Return an empty array if no network device configuration is set.
*/
void *networkDevicesVZVirtualMachineConfiguration(void *config)
{
if (@available(macOS 11, *)) {
return [(VZVirtualMachineConfiguration *)config networkDevices]; // NSArray<VZSocketDeviceConfiguration *>
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract List of serial ports. Empty by default.
@see VZVirtioConsoleDeviceSerialPortConfiguration
*/
void setSerialPortsVZVirtualMachineConfiguration(void *config,
void *serialPorts)
{
if (@available(macOS 11, *)) {
[(VZVirtualMachineConfiguration *)config setSerialPorts:[(NSMutableArray *)serialPorts copy]];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract List of socket devices. Empty by default.
@see VZVirtioSocketDeviceConfiguration
*/
void setSocketDevicesVZVirtualMachineConfiguration(void *config,
void *socketDevices)
{
if (@available(macOS 11, *)) {
[(VZVirtualMachineConfiguration *)config setSocketDevices:[(NSMutableArray *)socketDevices copy]];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Return the list of socket devices configurations for this VZVirtualMachineConfiguration. Return an empty array if no socket device configuration is set.
*/
void *socketDevicesVZVirtualMachineConfiguration(void *config)
{
if (@available(macOS 11, *)) {
return [(VZVirtualMachineConfiguration *)config socketDevices]; // NSArray<VZSocketDeviceConfiguration *>
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract List of disk devices. Empty by default.
@see VZVirtioBlockDeviceConfiguration
*/
void setStorageDevicesVZVirtualMachineConfiguration(void *config,
void *storageDevices)
{
if (@available(macOS 11, *)) {
[(VZVirtualMachineConfiguration *)config setStorageDevices:[(NSMutableArray *)storageDevices copy]];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Intialize the VZFileHandleSerialPortAttachment from file descriptors.
@param readFileDescriptor File descriptor for reading from the file.
@param writeFileDescriptor File descriptor for writing to the file.
@discussion
Each file descriptor must a valid.
*/
void *newVZFileHandleSerialPortAttachment(int readFileDescriptor, int writeFileDescriptor)
{
if (@available(macOS 11, *)) {
VZFileHandleSerialPortAttachment *ret;
@autoreleasepool {
NSFileHandle *fileHandleForReading = [[NSFileHandle alloc] initWithFileDescriptor:readFileDescriptor];
NSFileHandle *fileHandleForWriting = [[NSFileHandle alloc] initWithFileDescriptor:writeFileDescriptor];
ret = [[VZFileHandleSerialPortAttachment alloc]
initWithFileHandleForReading:fileHandleForReading
fileHandleForWriting:fileHandleForWriting];
}
return ret;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize the VZFileSerialPortAttachment from a URL of a file.
@param filePath The path of the file for the attachment on the local file system.
@param shouldAppend True if the file should be opened in append mode, false otherwise.
When a file is opened in append mode, writing to that file will append to the end of it.
@param error If not nil, used to report errors if initialization fails.
@return A VZFileSerialPortAttachment on success. Nil otherwise and the error parameter is populated if set.
*/
void *newVZFileSerialPortAttachment(const char *filePath, bool shouldAppend, void **error)
{
if (@available(macOS 11, *)) {
NSString *filePathNSString = [NSString stringWithUTF8String:filePath];
NSURL *fileURL = [NSURL fileURLWithPath:filePathNSString];
return [[VZFileSerialPortAttachment alloc]
initWithURL:fileURL
append:(BOOL)shouldAppend
error:(NSError *_Nullable *_Nullable)error];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Create a new Virtio Console Serial Port Device configuration
@param attachment Base class for a serial port attachment.
@discussion
The device creates a console which enables communication between the host and the guest through the Virtio interface.
The device sets up a single port on the Virtio console device.
*/
void *newVZVirtioConsoleDeviceSerialPortConfiguration(void *attachment)
{
if (@available(macOS 11, *)) {
VZVirtioConsoleDeviceSerialPortConfiguration *config = [[VZVirtioConsoleDeviceSerialPortConfiguration alloc] init];
[config setAttachment:(VZSerialPortAttachment *)attachment];
return config;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Create a new Network device attachment bridging a host physical interface with a virtual network device.
@param networkInterface a network interface that bridges a physical interface.
@discussion
A bridged network allows the virtual machine to use the same physical interface as the host. Both host and virtual machine
send and receive packets on the same physical interface but have distinct network layers.
The bridge network device attachment is used with a VZNetworkDeviceConfiguration to define a virtual network device.
Using a VZBridgedNetworkDeviceAttachment requires the app to have the "com.apple.vm.networking" entitlement.
@see VZBridgedNetworkInterface
@see VZNetworkDeviceConfiguration
@see VZVirtioNetworkDeviceConfiguration
*/
void *newVZBridgedNetworkDeviceAttachment(void *networkInterface)
{
if (@available(macOS 11, *)) {
return [[VZBridgedNetworkDeviceAttachment alloc] initWithInterface:(VZBridgedNetworkInterface *)networkInterface];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Create a new Network device attachment using network address translation (NAT) with outside networks.
@discussion
Using the NAT attachment type, the host serves as router and performs network address translation for accesses to outside networks.
@see VZNetworkDeviceConfiguration
@see VZVirtioNetworkDeviceConfiguration
*/
void *newVZNATNetworkDeviceAttachment()
{
if (@available(macOS 11, *)) {
return [[VZNATNetworkDeviceAttachment alloc] init];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Create a new Network device attachment sending raw network packets over a file handle.
@discussion
The file handle attachment transmits the raw packets/frames between the virtual network interface and a file handle.
The data transmitted through this attachment is at the level of the data link layer.
The file handle must hold a connected datagram socket.
@see VZNetworkDeviceConfiguration
@see VZVirtioNetworkDeviceConfiguration
*/
void *newVZFileHandleNetworkDeviceAttachment(int fileDescriptor)
{
if (@available(macOS 11, *)) {
VZFileHandleNetworkDeviceAttachment *ret;
@autoreleasepool {
NSFileHandle *fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor];
ret = [[VZFileHandleNetworkDeviceAttachment alloc] initWithFileHandle:fileHandle];
}
return ret;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Create a new Configuration of a paravirtualized network device of type Virtio Network Device.
@discussion
The communication channel used on the host is defined through the attachment. It is set with the VZNetworkDeviceConfiguration.attachment property.
The configuration is only valid with valid MACAddress and attachment.
@see VZVirtualMachineConfiguration.networkDevices
@param attachment Base class for a network device attachment.
@discussion
A network device attachment defines how a virtual network device interfaces with the host system.
VZNetworkDeviceAttachment should not be instantiated directly. One of its subclasses should be used instead.
Common attachment types include:
- VZNATNetworkDeviceAttachment
- VZFileHandleNetworkDeviceAttachment
@see VZBridgedNetworkDeviceAttachment
@see VZFileHandleNetworkDeviceAttachment
@see VZNATNetworkDeviceAttachment
*/
void *newVZVirtioNetworkDeviceConfiguration(void *attachment)
{
if (@available(macOS 11, *)) {
VZVirtioNetworkDeviceConfiguration *config = [[VZVirtioNetworkDeviceConfiguration alloc] init];
[config setAttachment:(VZNetworkDeviceAttachment *)attachment];
return config;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Create a new Virtio Entropy Device confiuration
@discussion The device exposes a source of entropy for the guest's random number generator.
*/
void *newVZVirtioEntropyDeviceConfiguration()
{
if (@available(macOS 11, *)) {
return [[VZVirtioEntropyDeviceConfiguration alloc] init];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize a VZVirtioBlockDeviceConfiguration with a device attachment.
@param attachment The storage device attachment. This defines how the virtualized device operates on the host side.
@see VZDiskImageStorageDeviceAttachment
*/
void *newVZVirtioBlockDeviceConfiguration(void *attachment)
{
if (@available(macOS 11, *)) {
return [[VZVirtioBlockDeviceConfiguration alloc] initWithAttachment:(VZStorageDeviceAttachment *)attachment];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize the attachment from a local file url.
@param diskPath Local file path to the disk image in RAW format.
@param readOnly If YES, the device attachment is read-only, otherwise the device can write data to the disk image.
@param error If not nil, assigned with the error if the initialization failed.
@return A VZDiskImageStorageDeviceAttachment on success. Nil otherwise and the error parameter is populated if set.
*/
void *newVZDiskImageStorageDeviceAttachment(const char *diskPath, bool readOnly, void **error)
{
if (@available(macOS 11, *)) {
NSString *diskPathNSString = [NSString stringWithUTF8String:diskPath];
NSURL *diskURL = [NSURL fileURLWithPath:diskPathNSString];
return [[VZDiskImageStorageDeviceAttachment alloc]
initWithURL:diskURL
readOnly:(BOOL)readOnly
error:(NSError *_Nullable *_Nullable)error];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Create a configuration of the Virtio traditional memory balloon device.
@discussion
This configuration creates a Virtio traditional memory balloon device which allows for managing guest memory.
Only one Virtio traditional memory balloon device can be used per virtual machine.
@see VZVirtioTraditionalMemoryBalloonDevice
*/
void *newVZVirtioTraditionalMemoryBalloonDeviceConfiguration()
{
if (@available(macOS 11, *)) {
return [[VZVirtioTraditionalMemoryBalloonDeviceConfiguration alloc] init];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Create a configuration of the Virtio socket device.
@discussion
This configuration creates a Virtio socket device for the guest which communicates with the host through the Virtio interface.
Only one Virtio socket device can be used per virtual machine.
@see VZVirtioSocketDevice
*/
void *newVZVirtioSocketDeviceConfiguration()
{
if (@available(macOS 11, *)) {
return [[VZVirtioSocketDeviceConfiguration alloc] init];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract The VZVirtioSocketListener object represents a listener for the Virtio socket device.
@discussion
The listener encompasses a VZVirtioSocketListenerDelegate object.
VZVirtioSocketListener is used with VZVirtioSocketDevice to listen to a particular port.
The delegate is used when a guest connects to a port associated with the listener.
@see VZVirtioSocketDevice
@see VZVirtioSocketListenerDelegate
*/
void *newVZVirtioSocketListener(void *cgoHandlerPtr)
{
if (@available(macOS 11, *)) {
VZVirtioSocketListener *ret = [[VZVirtioSocketListener alloc] init];
[ret setDelegate:[[VZVirtioSocketListenerDelegateImpl alloc] initWithHandler:cgoHandlerPtr]];
return ret;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Sets a listener at a specified port.
@discussion
There is only one listener per port, any existing listener will be removed, and the specified listener here will be set instead.
The same listener can be registered on multiple ports.
The listener's delegate will be called whenever the guest connects to that port.
@param listener The VZVirtioSocketListener object to be set.
@param port The port number to set the listener at.
*/
void VZVirtioSocketDevice_setSocketListenerForPort(void *socketDevice, void *vmQueue, void *listener, uint32_t port)
{
if (@available(macOS 11, *)) {
dispatch_sync((dispatch_queue_t)vmQueue, ^{
[(VZVirtioSocketDevice *)socketDevice setSocketListener:(VZVirtioSocketListener *)listener forPort:port];
});
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Removes the listener at a specfied port.
@discussion Does nothing if the port had no listener.
@param port The port number at which the listener is to be removed.
*/
void VZVirtioSocketDevice_removeSocketListenerForPort(void *socketDevice, void *vmQueue, uint32_t port)
{
if (@available(macOS 11, *)) {
dispatch_sync((dispatch_queue_t)vmQueue, ^{
[(VZVirtioSocketDevice *)socketDevice removeSocketListenerForPort:port];
});
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Connects to a specified port.
@discussion Does nothing if the guest does not listen on that port.
@param port The port number to connect to.
@param completionHandler Block called after the connection has been successfully established or on error.
The error parameter passed to the block is nil if the connection was successful.
*/
void VZVirtioSocketDevice_connectToPort(void *socketDevice, void *vmQueue, uint32_t port, void *cgoHandlerPtr)
{
if (@available(macOS 11, *)) {
dispatch_async((dispatch_queue_t)vmQueue, ^{
[(VZVirtioSocketDevice *)socketDevice connectToPort:port
completionHandler:^(VZVirtioSocketConnection *connection, NSError *err) {
connectionHandler(connection, err, cgoHandlerPtr);
}];
});
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
VZVirtioSocketConnectionFlat convertVZVirtioSocketConnection2Flat(void *connection)
{
if (@available(macOS 11, *)) {
VZVirtioSocketConnectionFlat ret;
ret.sourcePort = [(VZVirtioSocketConnection *)connection sourcePort];
ret.destinationPort = [(VZVirtioSocketConnection *)connection destinationPort];
ret.fileDescriptor = [(VZVirtioSocketConnection *)connection fileDescriptor];
return ret;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize the virtual machine.
@param config The configuration of the virtual machine.
The configuration must be valid. Validation can be performed at runtime with [VZVirtualMachineConfiguration validateWithError:].
The configuration is copied by the initializer.
@param queue The serial queue on which the virtual machine operates.
Every operation on the virtual machine must be done on that queue. The callbacks and delegate methods are invoked on that queue.
If the queue is not serial, the behavior is undefined.
*/
void *newVZVirtualMachineWithDispatchQueue(void *config, void *queue, void *statusHandler)
{
if (@available(macOS 11, *)) {
VZVirtualMachine *vm = [[VZVirtualMachine alloc]
initWithConfiguration:(VZVirtualMachineConfiguration *)config
queue:(dispatch_queue_t)queue];
@autoreleasepool {
Observer *o = [[Observer alloc] init];
[vm addObserver:o
forKeyPath:@"state"
options:NSKeyValueObservingOptionNew
context:statusHandler];
}
return vm;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Return the list of socket devices configured on this virtual machine. Return an empty array if no socket device is configured.
@see VZVirtioSocketDeviceConfiguration
@see VZVirtualMachineConfiguration
*/
void *VZVirtualMachine_socketDevices(void *machine)
{
if (@available(macOS 11, *)) {
return [(VZVirtualMachine *)machine socketDevices]; // NSArray<VZSocketDevice *>
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize the VZMACAddress from a string representation of a MAC address.
@param string
The string should be formatted representing the 6 bytes in hexadecimal separated by a colon character.
e.g. "01:23:45:ab:cd:ef"
The alphabetical characters can appear lowercase or uppercase.
@return A VZMACAddress or nil if the string is not formatted correctly.
*/
void *newVZMACAddress(const char *macAddress)
{
if (@available(macOS 11, *)) {
NSString *str = [NSString stringWithUTF8String:macAddress];
return [[VZMACAddress alloc] initWithString:str];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Create a valid, random, unicast, locally administered address.
@discussion The generated address is not guaranteed to be unique.
*/
void *newRandomLocallyAdministeredVZMACAddress()
{
if (@available(macOS 11, *)) {
return [VZMACAddress randomLocallyAdministeredAddress];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Sets the media access control address of the device.
*/
void setNetworkDevicesVZMACAddress(void *config, void *macAddress)
{
if (@available(macOS 11, *)) {
[(VZNetworkDeviceConfiguration *)config setMACAddress:[(VZMACAddress *)macAddress copy]];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract The address represented as a string.
@discussion
The 6 bytes are represented in hexadecimal form, separated by a colon character.
Alphabetical characters are lowercase.
The address is compatible with the parameter of -[VZMACAddress initWithString:].
*/
const char *getVZMACAddressString(void *macAddress)
{
if (@available(macOS 11, *)) {
return [[(VZMACAddress *)macAddress string] UTF8String];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Request that the guest turns itself off.
@param error If not nil, assigned with the error if the request failed.
@return YES if the request was made successfully.
*/
bool requestStopVirtualMachine(void *machine, void *queue, void **error)
{
if (@available(macOS 11, *)) {
__block BOOL ret;
dispatch_sync((dispatch_queue_t)queue, ^{
ret = [(VZVirtualMachine *)machine requestStopWithError:(NSError *_Nullable *_Nullable)error];
});
return (bool)ret;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
void *makeDispatchQueue(const char *label)
{
// dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0);
dispatch_queue_t queue = dispatch_queue_create(label, DISPATCH_QUEUE_SERIAL);
// dispatch_retain(queue);
return queue;
}
void startWithCompletionHandler(void *machine, void *queue, void *completionHandler)
{
if (@available(macOS 11, *)) {
vm_completion_handler_t handler = makeVMCompletionHandler(completionHandler);
dispatch_sync((dispatch_queue_t)queue, ^{
[(VZVirtualMachine *)machine startWithCompletionHandler:handler];
});
Block_release(handler);
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
void pauseWithCompletionHandler(void *machine, void *queue, void *completionHandler)
{
if (@available(macOS 11, *)) {
vm_completion_handler_t handler = makeVMCompletionHandler(completionHandler);
dispatch_sync((dispatch_queue_t)queue, ^{
[(VZVirtualMachine *)machine pauseWithCompletionHandler:handler];
});
Block_release(handler);
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
void resumeWithCompletionHandler(void *machine, void *queue, void *completionHandler)
{
if (@available(macOS 11, *)) {
vm_completion_handler_t handler = makeVMCompletionHandler(completionHandler);
dispatch_sync((dispatch_queue_t)queue, ^{
[(VZVirtualMachine *)machine resumeWithCompletionHandler:handler];
});
Block_release(handler);
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
// TODO(codehex): use KVO
bool vmCanStart(void *machine, void *queue)
{
if (@available(macOS 11, *)) {
__block BOOL result;
dispatch_sync((dispatch_queue_t)queue, ^{
result = ((VZVirtualMachine *)machine).canStart;
});
return (bool)result;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
bool vmCanPause(void *machine, void *queue)
{
if (@available(macOS 11, *)) {
__block BOOL result;
dispatch_sync((dispatch_queue_t)queue, ^{
result = ((VZVirtualMachine *)machine).canPause;
});
return (bool)result;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
bool vmCanResume(void *machine, void *queue)
{
if (@available(macOS 11, *)) {
__block BOOL result;
dispatch_sync((dispatch_queue_t)queue, ^{
result = ((VZVirtualMachine *)machine).canResume;
});
return (bool)result;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
bool vmCanRequestStop(void *machine, void *queue)
{
if (@available(macOS 11, *)) {
__block BOOL result;
dispatch_sync((dispatch_queue_t)queue, ^{
result = ((VZVirtualMachine *)machine).canRequestStop;
});
return (bool)result;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
// --- TODO end

View File

@@ -0,0 +1,48 @@
//
// virtualization_12.h
//
// Created by codehex.
//
#import "virtualization_helper.h"
#import "virtualization_view.h"
// FIXME(codehex): this is dirty hack to avoid clang-format error like below
// "Configuration file(s) do(es) not support C++: /github.com/Code-Hex/vz/.clang-format"
#define NSURLComponents NSURLComponents
bool vmCanStop(void *machine, void *queue);
void stopWithCompletionHandler(void *machine, void *queue, void *completionHandler);
void *newVZGenericPlatformConfiguration();
void *newVZVirtioSoundDeviceInputStreamConfiguration();
void *newVZVirtioSoundDeviceHostInputStreamConfiguration(); // use in Go
void *newVZVirtioSoundDeviceOutputStreamConfiguration();
void *newVZVirtioSoundDeviceHostOutputStreamConfiguration(); // use in Go
void *newVZUSBScreenCoordinatePointingDeviceConfiguration();
void *newVZUSBKeyboardConfiguration();
void *newVZVirtioSoundDeviceConfiguration();
void setStreamsVZVirtioSoundDeviceConfiguration(void *audioDeviceConfiguration, void *streams);
void *newVZSharedDirectory(const char *dirPath, bool readOnly);
void *newVZSingleDirectoryShare(void *sharedDirectory);
void *newVZMultipleDirectoryShare(void *sharedDirectories);
void *newVZVirtioFileSystemDeviceConfiguration(const char *tag, void **error);
void setVZVirtioFileSystemDeviceConfigurationShare(void *config, void *share);
void setDirectorySharingDevicesVZVirtualMachineConfiguration(void *config, void *directorySharingDevices);
void setPlatformVZVirtualMachineConfiguration(void *config,
void *platform);
void setGraphicsDevicesVZVirtualMachineConfiguration(void *config,
void *graphicsDevices);
void setPointingDevicesVZVirtualMachineConfiguration(void *config,
void *pointingDevices);
void setKeyboardsVZVirtualMachineConfiguration(void *config,
void *keyboards);
void setAudioDevicesVZVirtualMachineConfiguration(void *config,
void *audioDevices);
void sharedApplication();
void startVirtualMachineWindow(void *machine, double width, double height);

View File

@@ -0,0 +1,344 @@
//
// virtualization_12.m
//
// Created by codehex.
//
#import "virtualization_12.h"
bool vmCanStop(void *machine, void *queue)
{
if (@available(macOS 12, *)) {
__block BOOL result;
dispatch_sync((dispatch_queue_t)queue, ^{
result = ((VZVirtualMachine *)machine).canStop;
});
return (bool)result;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
void stopWithCompletionHandler(void *machine, void *queue, void *completionHandler)
{
if (@available(macOS 12, *)) {
vm_completion_handler_t handler = makeVMCompletionHandler(completionHandler);
dispatch_sync((dispatch_queue_t)queue, ^{
[(VZVirtualMachine *)machine stopWithCompletionHandler:handler];
});
Block_release(handler);
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract The platform configuration for a generic Intel or ARM virtual machine.
*/
void *newVZGenericPlatformConfiguration()
{
if (@available(macOS 12, *)) {
return [[VZGenericPlatformConfiguration alloc] init];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract List of directory sharing devices. Empty by default.
@see VZDirectorySharingDeviceConfiguration
*/
void setDirectorySharingDevicesVZVirtualMachineConfiguration(void *config, void *directorySharingDevices)
{
if (@available(macOS 12, *)) {
[(VZVirtualMachineConfiguration *)config setDirectorySharingDevices:[(NSMutableArray *)directorySharingDevices copy]];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract The hardware platform to use.
@discussion
Can be an instance of a VZGenericPlatformConfiguration or VZMacPlatformConfiguration. Defaults to VZGenericPlatformConfiguration.
*/
void setPlatformVZVirtualMachineConfiguration(void *config, void *platform)
{
if (@available(macOS 12, *)) {
[(VZVirtualMachineConfiguration *)config setPlatform:(VZPlatformConfiguration *)platform];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract List of graphics devices. Empty by default.
@see VZMacGraphicsDeviceConfiguration
*/
void setGraphicsDevicesVZVirtualMachineConfiguration(void *config, void *graphicsDevices)
{
if (@available(macOS 12, *)) {
[(VZVirtualMachineConfiguration *)config setGraphicsDevices:[(NSMutableArray *)graphicsDevices copy]];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract List of pointing devices. Empty by default.
@see VZUSBScreenCoordinatePointingDeviceConfiguration
*/
void setPointingDevicesVZVirtualMachineConfiguration(void *config, void *pointingDevices)
{
if (@available(macOS 12, *)) {
[(VZVirtualMachineConfiguration *)config setPointingDevices:[(NSMutableArray *)pointingDevices copy]];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract List of keyboards. Empty by default.
@see VZUSBKeyboardConfiguration
*/
void setKeyboardsVZVirtualMachineConfiguration(void *config, void *keyboards)
{
if (@available(macOS 12, *)) {
[(VZVirtualMachineConfiguration *)config setKeyboards:[(NSMutableArray *)keyboards copy]];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract List of audio devices. Empty by default.
@see VZVirtioSoundDeviceConfiguration
*/
void setAudioDevicesVZVirtualMachineConfiguration(void *config, void *audioDevices)
{
if (@available(macOS 12, *)) {
[(VZVirtualMachineConfiguration *)config setAudioDevices:[(NSMutableArray *)audioDevices copy]];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize a new Virtio Sound Device Configuration.
@discussion The device exposes a source or destination of sound.
*/
void *newVZVirtioSoundDeviceConfiguration()
{
if (@available(macOS 12, *)) {
return [[VZVirtioSoundDeviceConfiguration alloc] init];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Set the list of audio streams exposed by this device. Empty by default.
*/
void setStreamsVZVirtioSoundDeviceConfiguration(void *audioDeviceConfiguration, void *streams)
{
if (@available(macOS 12, *)) {
[(VZVirtioSoundDeviceConfiguration *)audioDeviceConfiguration setStreams:[(NSMutableArray *)streams copy]];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize a new Virtio Sound Device Input Stream Configuration.
@discussion A PCM stream of input audio data, such as from a microphone.
*/
void *newVZVirtioSoundDeviceInputStreamConfiguration()
{
if (@available(macOS 12, *)) {
return [[VZVirtioSoundDeviceInputStreamConfiguration alloc] init];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize a new Virtio Sound Device Host Audio Input Stream Configuration.
*/
void *newVZVirtioSoundDeviceHostInputStreamConfiguration()
{
if (@available(macOS 12, *)) {
VZVirtioSoundDeviceInputStreamConfiguration *inputStream = (VZVirtioSoundDeviceInputStreamConfiguration *)newVZVirtioSoundDeviceInputStreamConfiguration();
[inputStream setSource:[[VZHostAudioInputStreamSource alloc] init]];
return inputStream;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize a new Virtio Sound Device Output Stream Configuration.
@discussion A PCM stream of output audio data, such as to a speaker.
*/
void *newVZVirtioSoundDeviceOutputStreamConfiguration()
{
if (@available(macOS 12, *)) {
return [[VZVirtioSoundDeviceOutputStreamConfiguration alloc] init];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize a new Virtio Sound Device Host Audio Output Stream Configuration.
*/
void *newVZVirtioSoundDeviceHostOutputStreamConfiguration()
{
if (@available(macOS 12, *)) {
VZVirtioSoundDeviceOutputStreamConfiguration *outputStream = (VZVirtioSoundDeviceOutputStreamConfiguration *)newVZVirtioSoundDeviceOutputStreamConfiguration();
[outputStream setSink:[[VZHostAudioOutputStreamSink alloc] init]];
return outputStream;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize the VZSharedDirectory from the directory path and read only option.
@param dirPath
The directory path that will be share.
@param readOnly
If the directory should be mounted read only.
@return A VZSharedDirectory
*/
void *newVZSharedDirectory(const char *dirPath, bool readOnly)
{
if (@available(macOS 12, *)) {
NSString *dirPathNSString = [NSString stringWithUTF8String:dirPath];
NSURL *dirURL = [NSURL fileURLWithPath:dirPathNSString];
return [[VZSharedDirectory alloc] initWithURL:dirURL readOnly:(BOOL)readOnly];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize the VZSingleDirectoryShare from the shared directory.
@param sharedDirectory
The shared directory to use.
@return A VZSingleDirectoryShare
*/
void *newVZSingleDirectoryShare(void *sharedDirectory)
{
if (@available(macOS 12, *)) {
return [[VZSingleDirectoryShare alloc] initWithDirectory:(VZSharedDirectory *)sharedDirectory];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize the VZMultipleDirectoryShare from the shared directories.
@param sharedDirectories
NSDictionary mapping names to shared directories.
@return A VZMultipleDirectoryShare
*/
void *newVZMultipleDirectoryShare(void *sharedDirectories)
{
if (@available(macOS 12, *)) {
return [[VZMultipleDirectoryShare alloc] initWithDirectories:(NSDictionary<NSString *, VZSharedDirectory *> *)sharedDirectories];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize the VZVirtioFileSystemDeviceConfiguration from the fs tag.
@param tag
The tag to use for this device configuration.
@return A VZVirtioFileSystemDeviceConfiguration
*/
void *newVZVirtioFileSystemDeviceConfiguration(const char *tag, void **error)
{
if (@available(macOS 12, *)) {
NSString *tagNSString = [NSString stringWithUTF8String:tag];
BOOL valid = [VZVirtioFileSystemDeviceConfiguration validateTag:tagNSString error:(NSError *_Nullable *_Nullable)error];
if (!valid) {
return nil;
}
return [[VZVirtioFileSystemDeviceConfiguration alloc] initWithTag:tagNSString];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Sets share associated with this configuration.
*/
void setVZVirtioFileSystemDeviceConfigurationShare(void *config, void *share)
{
if (@available(macOS 12, *)) {
[(VZVirtioFileSystemDeviceConfiguration *)config setShare:(VZDirectoryShare *)share];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize a new configuration for a USB pointing device that reports absolute coordinates.
@discussion This device can be used by VZVirtualMachineView to send pointer events to the virtual machine.
*/
void *newVZUSBScreenCoordinatePointingDeviceConfiguration()
{
if (@available(macOS 12, *)) {
return [[VZUSBScreenCoordinatePointingDeviceConfiguration alloc] init];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize a new configuration for a USB keyboard.
@discussion This device can be used by VZVirtualMachineView to send key events to the virtual machine.
*/
void *newVZUSBKeyboardConfiguration()
{
if (@available(macOS 12, *)) {
return [[VZUSBKeyboardConfiguration alloc] init];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
void sharedApplication()
{
// Create a shared app instance.
// This will initialize the global variable
// 'NSApp' with the application instance.
[VZApplication sharedApplication];
}
void startVirtualMachineWindow(void *machine, double width, double height)
{
if (@available(macOS 12, *)) {
@autoreleasepool {
AppDelegate *appDelegate = [[[AppDelegate alloc]
initWithVirtualMachine:(VZVirtualMachine *)machine
windowWidth:(CGFloat)width
windowHeight:(CGFloat)height] autorelease];
NSApp.delegate = appDelegate;
[NSApp run];
return;
}
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}

View File

@@ -0,0 +1,16 @@
//
// virtualization_12_3.h
//
// Created by codehex.
//
#pragma once
#import "virtualization_helper.h"
#import <Virtualization/Virtualization.h>
// FIXME(codehex): this is dirty hack to avoid clang-format error like below
// "Configuration file(s) do(es) not support C++: /github.com/Code-Hex/vz/.clang-format"
#define NSURLComponents NSURLComponents
void setBlockDeviceIdentifierVZVirtioBlockDeviceConfiguration(void *blockDeviceConfig, const char *identifier, void **error);

View File

@@ -0,0 +1,25 @@
//
// virtualization_12_3.m
//
// Created by codehex.
//
#import "virtualization_12_3.h"
void setBlockDeviceIdentifierVZVirtioBlockDeviceConfiguration(void *blockDeviceConfig, const char *identifier, void **error)
{
#ifdef INCLUDE_TARGET_OSX_12_3
if (@available(macOS 12.3, *)) {
NSString *identifierNSString = [NSString stringWithUTF8String:identifier];
BOOL valid = [VZVirtioBlockDeviceConfiguration
validateBlockDeviceIdentifier:identifierNSString
error:(NSError *_Nullable *_Nullable)error];
if (!valid) {
return;
}
[(VZVirtioBlockDeviceConfiguration *)blockDeviceConfig setBlockDeviceIdentifier:identifierNSString];
return;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}

View File

@@ -0,0 +1,76 @@
//
// virtualization_12_arm64.h
//
// Created by codehex.
//
#pragma once
#import "virtualization_helper.h"
#import <Foundation/Foundation.h>
#import <Foundation/NSNotification.h>
#import <Virtualization/Virtualization.h>
#ifdef __arm64__
@interface ProgressObserver : NSObject
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
@end
typedef struct VZMacOSRestoreImageStruct {
const char *url;
const char *buildVersion;
NSOperatingSystemVersion operatingSystemVersion;
void *mostFeaturefulSupportedConfiguration; // (VZMacOSConfigurationRequirements *)
} VZMacOSRestoreImageStruct;
typedef struct VZMacOSConfigurationRequirementsStruct {
uint64_t minimumSupportedCPUCount;
uint64_t minimumSupportedMemorySize;
void *hardwareModel; // (VZMacHardwareModel *)
} VZMacOSConfigurationRequirementsStruct;
typedef struct VZMacHardwareModelStruct {
bool supported;
nbyteslice dataRepresentation;
} VZMacHardwareModelStruct;
/* exported from cgo */
void macOSRestoreImageCompletionHandler(void *cgoHandler, void *restoreImage, void *errPtr);
void macOSInstallCompletionHandler(void *cgoHandler, void *errPtr);
void macOSInstallFractionCompletedHandler(void *cgoHandlerPtr, double completed);
/* Mac Configurations */
void *newVZMacPlatformConfiguration();
void *newVZMacAuxiliaryStorageWithCreating(const char *storagePath, void *hardwareModel, void **error);
void *newVZMacAuxiliaryStorage(const char *storagePath);
void *newVZMacPlatformConfiguration();
void setHardwareModelVZMacPlatformConfiguration(void *config, void *hardwareModel);
void storeHardwareModelDataVZMacPlatformConfiguration(void *config, const char *filePath);
void setMachineIdentifierVZMacPlatformConfiguration(void *config, void *machineIdentifier);
void storeMachineIdentifierDataVZMacPlatformConfiguration(void *config, const char *filePath);
void setAuxiliaryStorageVZMacPlatformConfiguration(void *config, void *auxiliaryStorage);
void *newVZMacOSBootLoader();
void *newVZMacGraphicsDeviceConfiguration();
void setDisplaysVZMacGraphicsDeviceConfiguration(void *graphicsConfiguration, void *displays);
void *newVZMacGraphicsDisplayConfiguration(NSInteger widthInPixels, NSInteger heightInPixels, NSInteger pixelsPerInch);
void *newVZMacHardwareModelWithPath(const char *hardwareModelPath);
void *newVZMacHardwareModelWithBytes(void *hardwareModelBytes, int len);
void *newVZMacMachineIdentifier();
void *newVZMacMachineIdentifierWithPath(const char *machineIdentifierPath);
void *newVZMacMachineIdentifierWithBytes(void *machineIdentifierBytes, int len);
nbyteslice getVZMacMachineIdentifierDataRepresentation(void *machineIdentifierPtr);
VZMacOSRestoreImageStruct convertVZMacOSRestoreImage2Struct(void *restoreImagePtr);
void fetchLatestSupportedMacOSRestoreImageWithCompletionHandler(void *cgoHandler);
void loadMacOSRestoreImageFile(const char *ipswPath, void *cgoHandler);
VZMacOSConfigurationRequirementsStruct convertVZMacOSConfigurationRequirements2Struct(void *requirementsPtr);
VZMacHardwareModelStruct convertVZMacHardwareModel2Struct(void *hardwareModelPtr);
void *newVZMacOSInstaller(void *virtualMachine, void *vmQueue, const char *restoreImageFilePath);
void *newProgressObserverVZMacOSInstaller();
void installByVZMacOSInstaller(void *installerPtr, void *vmQueue, void *progressObserverPtr, void *completionHandler, void *fractionCompletedHandler);
void cancelInstallVZMacOSInstaller(void *installerPtr);
#endif

View File

@@ -0,0 +1,459 @@
//
// virtualization_12_arm64.m
//
// Created by codehex.
//
#ifdef __arm64__
#import "virtualization_12_arm64.h"
@implementation ProgressObserver
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
{
if ([keyPath isEqualToString:@"fractionCompleted"] && [object isKindOfClass:[NSProgress class]]) {
NSProgress *progress = (NSProgress *)object;
macOSInstallFractionCompletedHandler(context, progress.fractionCompleted);
if (progress.finished) {
[progress removeObserver:self forKeyPath:@"fractionCompleted"];
}
}
}
@end
/*!
@abstract Write an initialized VZMacAuxiliaryStorage to a storagePath on a file system.
@param storagePath The storagePath to write the auxiliary storage to on the local file system.
@param hardwareModel The hardware model to use. The auxiliary storage can be laid out differently for different hardware models.
@param options Initialization options.
@param error If not nil, used to report errors if creation fails.
@return A newly initialized VZMacAuxiliaryStorage on success. If an error was encountered returns @c nil, and @c error contains the error.
*/
void *newVZMacAuxiliaryStorageWithCreating(const char *storagePath, void *hardwareModel, void **error)
{
if (@available(macOS 12, *)) {
NSString *storagePathNSString = [NSString stringWithUTF8String:storagePath];
NSURL *storageURL = [NSURL fileURLWithPath:storagePathNSString];
return [[VZMacAuxiliaryStorage alloc] initCreatingStorageAtURL:storageURL
hardwareModel:(VZMacHardwareModel *)hardwareModel
options:VZMacAuxiliaryStorageInitializationOptionAllowOverwrite
error:(NSError *_Nullable *_Nullable)error];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize the auxiliary storage from the storagePath of an existing file.
@param URL The URL of the auxiliary storage on the local file system.
@discussion To create a new auxiliary storage, use -[VZMacAuxiliaryStorage initCreatingStorageAtURL:hardwareModel:options:error].
*/
void *newVZMacAuxiliaryStorage(const char *storagePath)
{
if (@available(macOS 12, *)) {
NSString *storagePathNSString = [NSString stringWithUTF8String:storagePath];
NSURL *storageURL = [NSURL fileURLWithPath:storagePathNSString];
// Use initWithURL: in macOS 13.x
// https://developer.apple.com/documentation/virtualization/vzmacauxiliarystorage?language=objc
return [[VZMacAuxiliaryStorage alloc] initWithContentsOfURL:storageURL];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract The platform configuration for booting macOS on Apple Silicon.
@discussion
When creating a virtual machine from scratch, the hardwareModel and auxiliaryStorage depend on the restore image
that will be used to install macOS.
To choose the hardware model, start from VZMacOSRestoreImage.mostFeaturefulSupportedConfiguration to get a supported configuration, then
use its VZMacOSConfigurationRequirements.hardwareModel property to get the hardware model.
Use the hardware model to set up VZMacPlatformConfiguration and to initialize a new auxiliary storage with
-[VZMacAuxiliaryStorage initCreatingStorageAtURL:hardwareModel:options:error:].
When a virtual machine is saved to disk then loaded again, the hardwareModel, machineIdentifier and auxiliaryStorage
must be restored to their original values.
If multiple virtual machines are created from the same configuration, each should have a unique auxiliaryStorage and machineIdentifier.
@seealso VZMacOSRestoreImage
@seealso VZMacOSConfigurationRequirements
*/
void *newVZMacPlatformConfiguration()
{
if (@available(macOS 12, *)) {
return [[VZMacPlatformConfiguration alloc] init];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Set the Mac hardware model.
*/
void setHardwareModelVZMacPlatformConfiguration(void *config, void *hardwareModel)
{
if (@available(macOS 12, *)) {
[(VZMacPlatformConfiguration *)config setHardwareModel:(VZMacHardwareModel *)hardwareModel];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
// Store the hardware model to disk so that we can retrieve them for subsequent boots.
void storeHardwareModelDataVZMacPlatformConfiguration(void *config, const char *filePath)
{
if (@available(macOS 12, *)) {
VZMacPlatformConfiguration *macPlatformConfiguration = (VZMacPlatformConfiguration *)config;
NSString *filePathNSString = [NSString stringWithUTF8String:filePath];
NSURL *fileURL = [NSURL fileURLWithPath:filePathNSString];
[macPlatformConfiguration.hardwareModel.dataRepresentation writeToURL:fileURL atomically:YES];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Set the Mac machine identifier.
@discussion
Running two virtual machines concurrently with the same identifier results in undefined behavior in the guest operating system.
*/
void setMachineIdentifierVZMacPlatformConfiguration(void *config, void *machineIdentifier)
{
if (@available(macOS 12, *)) {
[(VZMacPlatformConfiguration *)config setMachineIdentifier:(VZMacMachineIdentifier *)machineIdentifier];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
// Store the machine identifier to disk so that we can retrieve them for subsequent boots.
void storeMachineIdentifierDataVZMacPlatformConfiguration(void *config, const char *filePath)
{
if (@available(macOS 12, *)) {
VZMacPlatformConfiguration *macPlatformConfiguration = (VZMacPlatformConfiguration *)config;
NSString *filePathNSString = [NSString stringWithUTF8String:filePath];
NSURL *fileURL = [NSURL fileURLWithPath:filePathNSString];
[macPlatformConfiguration.machineIdentifier.dataRepresentation writeToURL:fileURL atomically:YES];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Set the Mac auxiliary storage.
@discussion
When creating a virtual machine from scratch, the hardware model of the auxiliaryStorage must match the hardware model of
the hardwareModel property.
*/
void setAuxiliaryStorageVZMacPlatformConfiguration(void *config, void *auxiliaryStorage)
{
if (@available(macOS 12, *)) {
[(VZMacPlatformConfiguration *)config setAuxiliaryStorage:(VZMacAuxiliaryStorage *)auxiliaryStorage];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Boot loader configuration for booting macOS on Apple Silicon.
@discussion
You must use a VZMacPlatformConfiguration in conjunction with the macOS boot loader.
It is invalid to use it with any other platform configuration.
@see VZMacPlatformConfiguration
@see VZVirtualMachineConfiguration.platform.
*/
void *newVZMacOSBootLoader()
{
if (@available(macOS 12, *)) {
return [[VZMacOSBootLoader alloc] init];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize a new configuration for a Mac graphics device.
@discussion This device can be used to attach a display to be shown in a VZVirtualMachineView.
*/
void *newVZMacGraphicsDeviceConfiguration()
{
if (@available(macOS 12, *)) {
return [[VZMacGraphicsDeviceConfiguration alloc] init];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Set the displays to be attached to this graphics device.
*/
void setDisplaysVZMacGraphicsDeviceConfiguration(void *graphicsConfiguration, void *displays)
{
if (@available(macOS 12, *)) {
[(VZMacGraphicsDeviceConfiguration *)graphicsConfiguration setDisplays:[(NSMutableArray *)displays copy]];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Create a display configuration with the specified pixel dimensions and pixel density.
@param widthInPixels The width of the display, in pixels.
@param heightInPixels The height of the display, in pixels.
@param pixelsPerInch The pixel density as a number of pixels per inch.
*/
void *newVZMacGraphicsDisplayConfiguration(NSInteger widthInPixels, NSInteger heightInPixels, NSInteger pixelsPerInch)
{
if (@available(macOS 12, *)) {
return [[VZMacGraphicsDisplayConfiguration alloc]
initWithWidthInPixels:widthInPixels
heightInPixels:heightInPixels
pixelsPerInch:pixelsPerInch];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Get the hardware model described by the specified data representation.
@param dataRepresentation The opaque data representation of the hardware model to be obtained.
*/
void *newVZMacHardwareModelWithPath(const char *hardwareModelPath)
{
if (@available(macOS 12, *)) {
VZMacHardwareModel *hardwareModel;
NSString *hardwareModelPathNSString = [NSString stringWithUTF8String:hardwareModelPath];
NSURL *hardwareModelPathURL = [NSURL fileURLWithPath:hardwareModelPathNSString];
@autoreleasepool {
NSData *hardwareModelData = [[NSData alloc] initWithContentsOfURL:hardwareModelPathURL];
hardwareModel = [[VZMacHardwareModel alloc] initWithDataRepresentation:hardwareModelData];
}
return hardwareModel;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
void *newVZMacHardwareModelWithBytes(void *hardwareModelBytes, int len)
{
if (@available(macOS 12, *)) {
VZMacHardwareModel *hardwareModel;
@autoreleasepool {
NSData *hardwareModelData = [[NSData alloc] initWithBytes:hardwareModelBytes length:(NSUInteger)len];
hardwareModel = [[VZMacHardwareModel alloc] initWithDataRepresentation:hardwareModelData];
}
return hardwareModel;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Create a new unique machine identifier.
*/
void *newVZMacMachineIdentifier()
{
if (@available(macOS 12, *)) {
return [[VZMacMachineIdentifier alloc] init];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Get the machine identifier described by the specified data representation.
@param dataRepresentation The opaque data representation of the machine identifier to be obtained.
@return A unique identifier identical to the one that generated the dataRepresentation, or nil if the data is invalid.
@see VZMacMachineIdentifier.dataRepresentation
*/
void *newVZMacMachineIdentifierWithPath(const char *machineIdentifierPath)
{
if (@available(macOS 12, *)) {
VZMacMachineIdentifier *machineIdentifier;
NSString *machineIdentifierPathNSString = [NSString stringWithUTF8String:machineIdentifierPath];
NSURL *machineIdentifierPathURL = [NSURL fileURLWithPath:machineIdentifierPathNSString];
@autoreleasepool {
NSData *machineIdentifierData = [[NSData alloc] initWithContentsOfURL:machineIdentifierPathURL];
machineIdentifier = [[VZMacMachineIdentifier alloc] initWithDataRepresentation:machineIdentifierData];
}
return machineIdentifier;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
void *newVZMacMachineIdentifierWithBytes(void *machineIdentifierBytes, int len)
{
if (@available(macOS 12, *)) {
VZMacMachineIdentifier *machineIdentifier;
@autoreleasepool {
NSData *machineIdentifierData = [[NSData alloc] initWithBytes:machineIdentifierBytes length:(NSUInteger)len];
machineIdentifier = [[VZMacMachineIdentifier alloc] initWithDataRepresentation:machineIdentifierData];
}
return machineIdentifier;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
nbyteslice getVZMacMachineIdentifierDataRepresentation(void *machineIdentifierPtr)
{
if (@available(macOS 12, *)) {
VZMacMachineIdentifier *machineIdentifier = (VZMacMachineIdentifier *)machineIdentifierPtr;
NSData *data = [machineIdentifier dataRepresentation];
nbyteslice ret = {
.ptr = (void *)[data bytes],
.len = (int)[data length],
};
return ret;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
VZMacOSRestoreImageStruct convertVZMacOSRestoreImage2Struct(void *restoreImagePtr)
{
if (@available(macOS 12, *)) {
VZMacOSRestoreImage *restoreImage = (VZMacOSRestoreImage *)restoreImagePtr;
VZMacOSRestoreImageStruct ret;
ret.url = [[[restoreImage URL] absoluteString] UTF8String];
ret.buildVersion = [[restoreImage buildVersion] UTF8String];
ret.operatingSystemVersion = [restoreImage operatingSystemVersion];
// maybe unnecessary CFBridgingRetain. if use CFBridgingRetain, should use CFRelease.
ret.mostFeaturefulSupportedConfiguration = (void *)CFBridgingRetain([restoreImage mostFeaturefulSupportedConfiguration]);
return ret;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
void fetchLatestSupportedMacOSRestoreImageWithCompletionHandler(void *cgoHandler)
{
if (@available(macOS 12, *)) {
[VZMacOSRestoreImage fetchLatestSupportedWithCompletionHandler:^(VZMacOSRestoreImage *restoreImage, NSError *error) {
VZMacOSRestoreImageStruct restoreImageStruct = convertVZMacOSRestoreImage2Struct(restoreImage);
macOSRestoreImageCompletionHandler(cgoHandler, &restoreImageStruct, error);
}];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
void loadMacOSRestoreImageFile(const char *ipswPath, void *cgoHandler)
{
if (@available(macOS 12, *)) {
NSString *ipswPathNSString = [NSString stringWithUTF8String:ipswPath];
NSURL *ipswURL = [NSURL fileURLWithPath:ipswPathNSString];
[VZMacOSRestoreImage loadFileURL:ipswURL
completionHandler:^(VZMacOSRestoreImage *restoreImage, NSError *error) {
VZMacOSRestoreImageStruct restoreImageStruct = convertVZMacOSRestoreImage2Struct(restoreImage);
macOSRestoreImageCompletionHandler(cgoHandler, &restoreImageStruct, error);
}];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
VZMacOSConfigurationRequirementsStruct convertVZMacOSConfigurationRequirements2Struct(void *requirementsPtr)
{
if (@available(macOS 12, *)) {
VZMacOSConfigurationRequirements *requirements = (VZMacOSConfigurationRequirements *)requirementsPtr;
VZMacOSConfigurationRequirementsStruct ret;
ret.minimumSupportedCPUCount = (uint64_t)[requirements minimumSupportedCPUCount];
ret.minimumSupportedMemorySize = (uint64_t)[requirements minimumSupportedMemorySize];
// maybe unnecessary CFBridgingRetain. if use CFBridgingRetain, should use CFRelease.
ret.hardwareModel = (void *)CFBridgingRetain([requirements hardwareModel]);
return ret;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
VZMacHardwareModelStruct convertVZMacHardwareModel2Struct(void *hardwareModelPtr)
{
if (@available(macOS 12, *)) {
VZMacHardwareModel *hardwareModel = (VZMacHardwareModel *)hardwareModelPtr;
VZMacHardwareModelStruct ret;
ret.supported = (bool)[hardwareModel isSupported];
NSData *data = [hardwareModel dataRepresentation];
nbyteslice retByteSlice = {
.ptr = (void *)[data bytes],
.len = (int)[data length],
};
ret.dataRepresentation = retByteSlice;
return ret;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize a VZMacOSInstaller object.
@param virtualMachine The virtual machine that the operating system will be installed onto.
@param restoreImageFileURL A file URL indicating the macOS restore image to install.
@discussion
The virtual machine platform must be macOS and the restore image URL must be a file URL referring to a file on disk or an exception will be raised.
This method must be called on the virtual machine's queue.
*/
void *newVZMacOSInstaller(void *virtualMachine, void *vmQueue, const char *restoreImageFilePath)
{
if (@available(macOS 12, *)) {
__block VZMacOSInstaller *ret;
NSString *restoreImageFilePathNSString = [NSString stringWithUTF8String:restoreImageFilePath];
NSURL *restoreImageFileURL = [NSURL fileURLWithPath:restoreImageFilePathNSString];
dispatch_sync((dispatch_queue_t)vmQueue, ^{
ret = [[VZMacOSInstaller alloc] initWithVirtualMachine:(VZVirtualMachine *)virtualMachine restoreImageURL:restoreImageFileURL];
});
return ret;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
void *newProgressObserverVZMacOSInstaller()
{
return [[ProgressObserver alloc] init];
}
void installByVZMacOSInstaller(void *installerPtr, void *vmQueue, void *progressObserverPtr, void *completionHandler, void *fractionCompletedHandler)
{
if (@available(macOS 12, *)) {
VZMacOSInstaller *installer = (VZMacOSInstaller *)installerPtr;
dispatch_sync((dispatch_queue_t)vmQueue, ^{
[installer installWithCompletionHandler:^(NSError *error) {
macOSInstallCompletionHandler(completionHandler, error);
}];
[installer.progress
addObserver:(ProgressObserver *)progressObserverPtr
forKeyPath:@"fractionCompleted"
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:fractionCompletedHandler];
});
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
void cancelInstallVZMacOSInstaller(void *installerPtr)
{
if (@available(macOS 12, *)) {
VZMacOSInstaller *installer = (VZMacOSInstaller *)installerPtr;
if (installer.progress.cancellable) {
[installer.progress cancel];
}
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
#endif

View File

@@ -0,0 +1,48 @@
//
// virtualization_13.h
//
// Created by codehex.
//
#pragma once
#import "virtualization_helper.h"
#import <Virtualization/Virtualization.h>
/* macOS 13 API */
void setConsoleDevicesVZVirtualMachineConfiguration(void *config, void *consoleDevices);
void *newVZEFIBootLoader();
void setVariableStoreVZEFIBootLoader(void *bootLoaderPtr, void *variableStore);
void *newVZEFIVariableStorePath(const char *variableStorePath);
void *newCreatingVZEFIVariableStoreAtPath(const char *variableStorePath, void **error);
void *newVZGenericMachineIdentifierWithBytes(void *machineIdentifierBytes, int len);
nbyteslice getVZGenericMachineIdentifierDataRepresentation(void *machineIdentifierPtr);
void *newVZGenericMachineIdentifier();
void setMachineIdentifierVZGenericPlatformConfiguration(void *config, void *machineIdentifier);
void *newVZUSBMassStorageDeviceConfiguration(void *attachment);
void *newVZVirtioGraphicsDeviceConfiguration();
void setScanoutsVZVirtioGraphicsDeviceConfiguration(void *graphicsConfiguration, void *scanouts);
void *newVZVirtioGraphicsScanoutConfiguration(NSInteger widthInPixels, NSInteger heightInPixels);
void *newVZVirtioConsoleDeviceConfiguration();
void *portsVZVirtioConsoleDeviceConfiguration(void *consoleDevice);
uint32_t maximumPortCountVZVirtioConsolePortConfigurationArray(void *ports);
void *getObjectAtIndexedSubscriptVZVirtioConsolePortConfigurationArray(void *portsPtr, int portIndex);
void setObjectAtIndexedSubscriptVZVirtioConsolePortConfigurationArray(void *portsPtr, void *portConfig, int portIndex);
void *newVZVirtioConsolePortConfiguration();
void setNameVZVirtioConsolePortConfiguration(void *consolePortConfig, const char *name);
void setIsConsoleVZVirtioConsolePortConfiguration(void *consolePortConfig, bool isConsole);
void setAttachmentVZVirtioConsolePortConfiguration(void *consolePortConfig, void *serialPortAttachment);
void *newVZSpiceAgentPortAttachment();
void setSharesClipboardVZSpiceAgentPortAttachment(void *attachment, bool sharesClipboard);
const char *getSpiceAgentPortName();
void startWithOptionsCompletionHandler(void *machine, void *queue, void *options, void *completionHandler);
const char *getMacOSGuestAutomountTag();
void setMaximumTransmissionUnitVZFileHandleNetworkDeviceAttachment(void *attachment, NSInteger mtu);

View File

@@ -0,0 +1,478 @@
//
// virtualization_13.m
//
// Created by codehex.
//
#import "virtualization_13.h"
#import "virtualization_view.h"
/*!
@abstract List of console devices. Empty by default.
@see VZVirtioConsoleDeviceConfiguration
*/
void setConsoleDevicesVZVirtualMachineConfiguration(void *config, void *consoleDevices)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
[(VZVirtualMachineConfiguration *)config
setConsoleDevices:[(NSMutableArray *)consoleDevices copy]];
return;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Boot loader configuration for booting guest operating systems expecting an EFI ROM.
@discussion
You must use a VZGenericPlatformConfiguration in conjunction with the EFI boot loader.
It is invalid to use it with any other platform configuration.
@see VZGenericPlatformConfiguration
@see VZVirtualMachineConfiguration.platform.
*/
void *newVZEFIBootLoader()
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
return [[VZEFIBootLoader alloc] init];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Set the EFI variable store.
*/
void setVariableStoreVZEFIBootLoader(void *bootLoaderPtr, void *variableStore)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
[(VZEFIBootLoader *)bootLoaderPtr setVariableStore:(VZEFIVariableStore *)variableStore];
return;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize the variable store from the path of an existing file.
@param variableStorePath The path of the variable store on the local file system.
@discussion To create a new variable store, use -[VZEFIVariableStore initCreatingVariableStoreAtURL:options:error].
*/
void *newVZEFIVariableStorePath(const char *variableStorePath)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
NSString *variableStorePathNSString = [NSString stringWithUTF8String:variableStorePath];
NSURL *variableStoreURL = [NSURL fileURLWithPath:variableStorePathNSString];
return [[VZEFIVariableStore alloc] initWithURL:variableStoreURL];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Write an initialized VZEFIVariableStore to path on a file system.
@param variableStorePath The path to write the variable store to on the local file system.
@param error If not nil, used to report errors if creation fails.
@return A newly initialized VZEFIVariableStore on success. If an error was encountered returns @c nil, and @c error contains the error.
*/
void *newCreatingVZEFIVariableStoreAtPath(const char *variableStorePath, void **error)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
NSString *variableStorePathNSString = [NSString stringWithUTF8String:variableStorePath];
NSURL *variableStoreURL = [NSURL fileURLWithPath:variableStorePathNSString];
return [[VZEFIVariableStore alloc]
initCreatingVariableStoreAtURL:variableStoreURL
options:VZEFIVariableStoreInitializationOptionAllowOverwrite
error:(NSError *_Nullable *_Nullable)error];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Get the machine identifier described by the specified data representation.
@param dataRepresentation The opaque data representation of the machine identifier to be obtained.
@return A unique identifier identical to the one that generated the dataRepresentation, or nil if the data is invalid.
@see VZGenericMachineIdentifier.dataRepresentation
*/
void *newVZGenericMachineIdentifierWithBytes(void *machineIdentifierBytes, int len)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
VZGenericMachineIdentifier *machineIdentifier;
@autoreleasepool {
NSData *machineIdentifierData = [[NSData alloc] initWithBytes:machineIdentifierBytes length:(NSUInteger)len];
machineIdentifier = [[VZGenericMachineIdentifier alloc] initWithDataRepresentation:machineIdentifierData];
}
return machineIdentifier;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Opaque data representation of the machine identifier.
@discussion This can be used to recreate the same machine identifier with -[VZGenericMachineIdentifier initWithDataRepresentation:].
@see -[VZGenericMachineIdentifier initWithDataRepresentation:]
*/
nbyteslice getVZGenericMachineIdentifierDataRepresentation(void *machineIdentifierPtr)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
VZGenericMachineIdentifier *machineIdentifier = (VZGenericMachineIdentifier *)machineIdentifierPtr;
NSData *data = [machineIdentifier dataRepresentation];
nbyteslice ret = {
.ptr = (void *)[data bytes],
.len = (int)[data length],
};
return ret;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Create a new unique machine identifier.
*/
void *newVZGenericMachineIdentifier()
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
return [[VZGenericMachineIdentifier alloc] init];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Set the machine identifier.
*/
void setMachineIdentifierVZGenericPlatformConfiguration(void *config, void *machineIdentifier)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
[(VZGenericPlatformConfiguration *)config setMachineIdentifier:(VZGenericMachineIdentifier *)machineIdentifier];
return;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Initialize a VZUSBMassStorageDeviceConfiguration with a device attachment.
@param attachment The storage device attachment. This defines how the virtualized device operates on the host side.
@see VZDiskImageStorageDeviceAttachment
*/
void *newVZUSBMassStorageDeviceConfiguration(void *attachment)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
return [[VZUSBMassStorageDeviceConfiguration alloc]
initWithAttachment:(VZStorageDeviceAttachment *)attachment];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Creates a new Configuration for a Virtio graphics device.
@discussion
This device configuration creates a graphics device using paravirtualization.
The emulated device follows the Virtio GPU Device specification.
This device can be used to attach a display to be shown in a VZVirtualMachineView.
*/
void *newVZVirtioGraphicsDeviceConfiguration()
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
return [[VZVirtioGraphicsDeviceConfiguration alloc] init];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Set the scanouts to be attached to this graphics device.
@discussion
Maximum of one scanout is supported.
*/
void setScanoutsVZVirtioGraphicsDeviceConfiguration(void *graphicsConfiguration, void *scanouts)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
[(VZVirtioGraphicsDeviceConfiguration *)graphicsConfiguration
setScanouts:[(NSMutableArray *)scanouts copy]];
return;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Create a scanout configuration with the specified pixel dimensions.
@param widthInPixels The width of the scanout, in pixels.
@param heightInPixels The height of the scanout, in pixels.
*/
void *newVZVirtioGraphicsScanoutConfiguration(NSInteger widthInPixels, NSInteger heightInPixels)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
return [[VZVirtioGraphicsScanoutConfiguration alloc]
initWithWidthInPixels:widthInPixels
heightInPixels:heightInPixels];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Create a new Virtio Console Device
@discussion
This console device enables communication between the host and the guest using console ports through the Virtio interface.
The device sets up one or more ports via VZVirtioConsolePortConfiguration on the Virtio console device.
@see VZVirtioConsolePortConfiguration
@see VZVirtualMachineConfiguration.consoleDevices
*/
void *newVZVirtioConsoleDeviceConfiguration()
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
return [[VZVirtioConsoleDeviceConfiguration alloc] init];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract The console ports to be configured for this console device.
*/
void *portsVZVirtioConsoleDeviceConfiguration(void *consoleDevice)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
return [(VZVirtioConsoleDeviceConfiguration *)consoleDevice ports]; // VZVirtioConsolePortConfigurationArray
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract The maximum number of ports allocated by this device. The default is the number of ports attached to this device.
*/
uint32_t maximumPortCountVZVirtioConsolePortConfigurationArray(void *ports)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
return [(VZVirtioConsolePortConfigurationArray *)ports maximumPortCount];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Get a port configuration at the specified index.
*/
void *getObjectAtIndexedSubscriptVZVirtioConsolePortConfigurationArray(void *portsPtr, int portIndex)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
VZVirtioConsolePortConfigurationArray *ports = (VZVirtioConsolePortConfigurationArray *)portsPtr;
return ports[portIndex];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Set a port configuration at the specified index.
*/
void setObjectAtIndexedSubscriptVZVirtioConsolePortConfigurationArray(void *portsPtr, void *portConfig, int portIndex)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
VZVirtioConsolePortConfigurationArray *ports = (VZVirtioConsolePortConfigurationArray *)portsPtr;
ports[portIndex] = (VZVirtioConsolePortConfiguration *)portConfig;
return;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Virtio Console Port
@discussion
A console port is a two-way communication channel between a host VZSerialPortAttachment and a virtual machine console port. One or more console ports are attached to a Virtio console device.
An optional name may be set for a console port. A console port may also be configured for use as the system console.
@see VZConsolePortConfiguration
@see VZVirtualMachineConfiguration.consoleDevices
*/
void *newVZVirtioConsolePortConfiguration()
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
return [[VZVirtioConsolePortConfiguration alloc] init];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Set the console port's name. The default behavior is to not use a name unless set.
*/
void setNameVZVirtioConsolePortConfiguration(void *consolePortConfig, const char *name)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
NSString *nameNSString = [NSString stringWithUTF8String:name];
[(VZVirtioConsolePortConfiguration *)consolePortConfig setName:nameNSString];
return;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Set the console port may be marked for use as the system console. The default is false.
*/
void setIsConsoleVZVirtioConsolePortConfiguration(void *consolePortConfig, bool isConsole)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
[(VZVirtioConsolePortConfiguration *)consolePortConfig setIsConsole:(BOOL)isConsole];
return;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Set the console port attachment. Defines how the virtual machine's console port interfaces with the host system. Default is nil.
@see VZFileHandleSerialPortAttachment
@see VZFileSerialPortAttachment
@see VZSpiceAgentPortAttachment
*/
void setAttachmentVZVirtioConsolePortConfiguration(void *consolePortConfig, void *serialPortAttachment)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
[(VZVirtioConsolePortConfiguration *)consolePortConfig
setAttachment:(VZSerialPortAttachment *)serialPortAttachment];
return;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
void *newVZSpiceAgentPortAttachment()
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
return [[VZSpiceAgentPortAttachment alloc] init];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Enable the Spice agent clipboard sharing capability.
@discussion
If enabled, the clipboard capability will be advertised to the Spice guest agent. Copy and paste events
will be shared between the host and the virtual machine.
This property is enabled by default.
*/
void setSharesClipboardVZSpiceAgentPortAttachment(void *attachment, bool sharesClipboard)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
return [(VZSpiceAgentPortAttachment *)attachment setSharesClipboard:(BOOL)sharesClipboard];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract The Spice agent port name.
@discussion
A console port configured with this name will spawn a Spice guest agent if supported by the guest.
VZConsolePortConfiguration.attachment must be set to VZSpiceAgentPortAttachment.
VZVirtioConsolePortConfiguration.isConsole must remain false on a Spice agent port.
*/
const char *getSpiceAgentPortName()
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
return [[VZSpiceAgentPortAttachment spiceAgentPortName] UTF8String];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Start a virtual machine with options.
@discussion
Start a virtual machine that is in either Stopped or Error state.
@param options Options used to control how the virtual machine is started.
@param completionHandler Block called after the virtual machine has been successfully started or on error.
The error parameter passed to the block is nil if the start was successful.
@seealso VZMacOSVirtualMachineStartOptions
*/
void startWithOptionsCompletionHandler(void *machine, void *queue, void *options, void *completionHandler)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
vm_completion_handler_t handler = makeVMCompletionHandler(completionHandler);
dispatch_sync((dispatch_queue_t)queue, ^{
[(VZVirtualMachine *)machine startWithOptions:options completionHandler:handler];
});
Block_release(handler);
return;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract The macOS automount tag.
@discussion A device configured with this tag will be automatically mounted in a macOS guest.
*/
const char *getMacOSGuestAutomountTag()
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
return [[VZVirtioFileSystemDeviceConfiguration macOSGuestAutomountTag] UTF8String];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract The maximum transmission unit (MTU) associated with this attachment.
@discussion
The client side of the associated datagram socket must be properly configured with the appropriate values for
`SO_SNDBUF`, and `SO_RCVBUF`, which can be set using the `setsockopt` system call. The value of `SO_RCVBUF` is
expected to be at least double the value of `SO_SNDBUF`, and for optimal performance, the value of `SO_RCVBUF`
is recommended to be four times the value of `SO_SNDBUF`.
The default MTU is 1500.
The maximum MTU allowed is 65535, and the minimum MTU allowed is 1500. An invalid MTU value will result in an invalid
virtual machine configuration.
*/
void setMaximumTransmissionUnitVZFileHandleNetworkDeviceAttachment(void *attachment, NSInteger mtu)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
[(VZFileHandleNetworkDeviceAttachment *)attachment setMaximumTransmissionUnit:mtu];
return;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}

View File

@@ -0,0 +1,29 @@
//
// virtualization_13_arm64.h
//
// Created by codehex.
//
#pragma once
#ifdef __arm64__
// FIXME(codehex): this is dirty hack to avoid clang-format error like below
// "Configuration file(s) do(es) not support C++: /github.com/Code-Hex/vz/.clang-format"
#define NSURLComponents NSURLComponents
#import "virtualization_helper.h"
#import <Virtualization/Virtualization.h>
/* exported from cgo */
void linuxInstallRosettaWithCompletionHandler(void *cgoHandler, void *errPtr);
void *newVZLinuxRosettaDirectoryShare(void **error);
void linuxInstallRosetta(void *cgoHandler);
int availabilityVZLinuxRosettaDirectoryShare();
void *newVZMacOSVirtualMachineStartOptions(bool startUpFromMacOSRecovery);
void *newVZMacTrackpadConfiguration();
#endif

View File

@@ -0,0 +1,89 @@
//
// virtualization_13_arm64.m
//
// Created by codehex.
//
#import "virtualization_13_arm64.h"
/*!
@abstract Initialize a Rosetta directory share if Rosetta support for Linux binaries is installed.
@param error Error object to store the error, if an error exists.
@discussion The call returns an error if Rosetta is not available for a directory share. To install Rosetta support, use +[VZLinuxRosettaDirectoryShare installRosettaWithCompletionHandler:].
*/
void *newVZLinuxRosettaDirectoryShare(void **error)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
return [[VZLinuxRosettaDirectoryShare alloc] initWithError:(NSError *_Nullable *_Nullable)error];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Download and install Rosetta support for Linux binaries if necessary.
@param completionHandler The completion handler gets called with a valid error on failure and a nil error on success. It will also be invoked on an arbitrary queue.
@discussion
The call prompts the user through the download and install flow for Rosetta. This call is successful if the error is nil.
@see +[VZLinuxRosettaDirectoryShare availability]
*/
void linuxInstallRosetta(void *cgoHandler)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
[VZLinuxRosettaDirectoryShare installRosettaWithCompletionHandler:^(NSError *error) {
linuxInstallRosettaWithCompletionHandler(cgoHandler, error);
}];
return;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Check the availability of Rosetta support for the directory share.
*/
int availabilityVZLinuxRosettaDirectoryShare()
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
return (int)[VZLinuxRosettaDirectoryShare availability];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Options controlling startup behavior of a virtual machine using VZMacOSBootLoader.
*/
void *newVZMacOSVirtualMachineStartOptions(bool startUpFromMacOSRecovery)
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
VZMacOSVirtualMachineStartOptions *opts = [[VZMacOSVirtualMachineStartOptions alloc] init];
[opts setStartUpFromMacOSRecovery:(BOOL)startUpFromMacOSRecovery];
return opts;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract Configuration for a Mac trackpad.
@discussion
This device can be used by VZVirtualMachineView to send pointer events and multi-touch trackpad gestures to the virtual machine.
Note: this device is only recognized by virtual machines running macOS 13.0 and later. In order to support both macOS 13.0 and earlier
guests, VZVirtualMachineConfiguration.pointingDevices can be set to an array containing both a VZMacTrackpadConfiguration and
a VZUSBScreenCoordinatePointingDeviceConfiguration object. macOS 13.0 and later guests will use the multi-touch trackpad device,
while earlier versions of macOS will use the USB pointing device.
*/
void *newVZMacTrackpadConfiguration()
{
#ifdef INCLUDE_TARGET_OSX_13
if (@available(macOS 13, *)) {
return [[VZMacTrackpadConfiguration alloc] init];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}

View File

@@ -0,0 +1,578 @@
//go:build darwin && arm64
// +build darwin,arm64
package vz
/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization_11.h"
# include "virtualization_12_arm64.h"
# include "virtualization_13_arm64.h"
*/
import "C"
import (
"context"
"fmt"
"io"
"net/http"
"os"
"runtime/cgo"
"sync"
"sync/atomic"
"unsafe"
"github.com/Code-Hex/vz/v3/internal/objc"
"github.com/Code-Hex/vz/v3/internal/progress"
)
// WithStartUpFromMacOSRecovery is an option to specifiy whether to start up
// from macOS Recovery for macOS VM.
//
// This is only supported on macOS 13 and newer, error will
// be returned on older versions.
func WithStartUpFromMacOSRecovery(startInRecovery bool) VirtualMachineStartOption {
return func(vmso *virtualMachineStartOptions) error {
if err := macOSAvailable(13); err != nil {
return err
}
vmso.macOSVirtualMachineStartOptionsPtr = C.newVZMacOSVirtualMachineStartOptions(
C.bool(startInRecovery),
)
return nil
}
}
// MacHardwareModel describes a specific virtual Mac hardware model.
type MacHardwareModel struct {
*pointer
supported bool
dataRepresentation []byte
}
// NewMacHardwareModelWithDataPath initialize a new hardware model described by the specified pathname.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewMacHardwareModelWithDataPath(pathname string) (*MacHardwareModel, error) {
b, err := os.ReadFile(pathname)
if err != nil {
return nil, err
}
return NewMacHardwareModelWithData(b)
}
// NewMacHardwareModelWithData initialize a new hardware model described by the specified data representation.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewMacHardwareModelWithData(b []byte) (*MacHardwareModel, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
ptr := C.newVZMacHardwareModelWithBytes(
unsafe.Pointer(&b[0]),
C.int(len(b)),
)
ret := newMacHardwareModel(ptr)
objc.SetFinalizer(ret, func(self *MacHardwareModel) {
objc.Release(self)
})
return ret, nil
}
func newMacHardwareModel(ptr unsafe.Pointer) *MacHardwareModel {
ret := C.convertVZMacHardwareModel2Struct(ptr)
dataRepresentation := ret.dataRepresentation
bytePointer := (*byte)(unsafe.Pointer(dataRepresentation.ptr))
return &MacHardwareModel{
pointer: objc.NewPointer(ptr),
supported: bool(ret.supported),
// https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
dataRepresentation: unsafe.Slice(bytePointer, dataRepresentation.len),
}
}
// Supported indicate whether this hardware model is supported by the host.
func (m *MacHardwareModel) Supported() bool { return m.supported }
// DataRepresentation opaque data representation of the hardware model.
// This can be used to recreate the same hardware model with NewMacHardwareModelWithData function.
func (m *MacHardwareModel) DataRepresentation() []byte { return m.dataRepresentation }
// MacMachineIdentifier an identifier to make a virtual machine unique.
type MacMachineIdentifier struct {
*pointer
dataRepresentation []byte
}
// NewMacMachineIdentifierWithDataPath initialize a new machine identifier described by the specified pathname.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewMacMachineIdentifierWithDataPath(pathname string) (*MacMachineIdentifier, error) {
b, err := os.ReadFile(pathname)
if err != nil {
return nil, err
}
return NewMacMachineIdentifierWithData(b)
}
// NewMacMachineIdentifierWithData initialize a new machine identifier described by the specified data representation.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewMacMachineIdentifierWithData(b []byte) (*MacMachineIdentifier, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
ptr := C.newVZMacMachineIdentifierWithBytes(
unsafe.Pointer(&b[0]),
C.int(len(b)),
)
return newMacMachineIdentifier(ptr), nil
}
// NewMacMachineIdentifier initialize a new Mac machine identifier is used by macOS guests to uniquely
// identify the virtual hardware.
//
// Two virtual machines running concurrently should not use the same identifier.
//
// If the virtual machine is serialized to disk, the identifier can be preserved in a binary representation through
// DataRepresentation method.
// The identifier can then be recreated with NewMacMachineIdentifierWithData function from the binary representation.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewMacMachineIdentifier() (*MacMachineIdentifier, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
return newMacMachineIdentifier(C.newVZMacMachineIdentifier()), nil
}
func newMacMachineIdentifier(ptr unsafe.Pointer) *MacMachineIdentifier {
dataRepresentation := C.getVZMacMachineIdentifierDataRepresentation(ptr)
bytePointer := (*byte)(unsafe.Pointer(dataRepresentation.ptr))
return &MacMachineIdentifier{
pointer: objc.NewPointer(ptr),
// https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
dataRepresentation: unsafe.Slice(bytePointer, dataRepresentation.len),
}
}
// DataRepresentation opaque data representation of the machine identifier.
// This can be used to recreate the same machine identifier with NewMacMachineIdentifierWithData function.
func (m *MacMachineIdentifier) DataRepresentation() []byte { return m.dataRepresentation }
// MacAuxiliaryStorage is a struct that contains information the boot loader
// needs for booting macOS as a guest operating system.
type MacAuxiliaryStorage struct {
*pointer
storagePath string
}
// NewMacAuxiliaryStorageOption is an option type to initialize a new Mac auxiliary storage
type NewMacAuxiliaryStorageOption func(*MacAuxiliaryStorage) error
// WithCreatingMacAuxiliaryStorage is an option when initialize a new Mac auxiliary storage with data creation
// to you specified storage path.
func WithCreatingMacAuxiliaryStorage(hardwareModel *MacHardwareModel) NewMacAuxiliaryStorageOption {
return func(mas *MacAuxiliaryStorage) error {
cpath := charWithGoString(mas.storagePath)
defer cpath.Free()
nserrPtr := newNSErrorAsNil()
mas.pointer = objc.NewPointer(
C.newVZMacAuxiliaryStorageWithCreating(
cpath.CString(),
objc.Ptr(hardwareModel),
&nserrPtr,
),
)
if err := newNSError(nserrPtr); err != nil {
return err
}
return nil
}
}
// NewMacAuxiliaryStorage creates a new MacAuxiliaryStorage is based Mac auxiliary storage data from the storagePath
// of an existing file by default.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewMacAuxiliaryStorage(storagePath string, opts ...NewMacAuxiliaryStorageOption) (*MacAuxiliaryStorage, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
storage := &MacAuxiliaryStorage{storagePath: storagePath}
for _, opt := range opts {
if err := opt(storage); err != nil {
return nil, err
}
}
if objc.Ptr(storage) == nil {
cpath := charWithGoString(storagePath)
defer cpath.Free()
storage.pointer = objc.NewPointer(
C.newVZMacAuxiliaryStorage(cpath.CString()),
)
}
return storage, nil
}
// MacOSRestoreImage is a struct that describes a version of macOS to install on to a virtual machine.
type MacOSRestoreImage struct {
url string
buildVersion string
operatingSystemVersion OperatingSystemVersion
mostFeaturefulSupportedConfigurationPtr unsafe.Pointer
}
// URL returns URL of this restore image.
// the value of this property will be a file URL. (https://~)
// the value of this property will be a network URL referring to an installation media file. (file:///~)
func (m *MacOSRestoreImage) URL() string {
return m.url
}
// BuildVersion returns the build version this restore image contains.
func (m *MacOSRestoreImage) BuildVersion() string {
return m.buildVersion
}
// OperatingSystemVersion represents the operating system version this restore image contains.
type OperatingSystemVersion struct {
MajorVersion int64
MinorVersion int64
PatchVersion int64
}
// String returns string for the build version this restore image contains.
func (osv OperatingSystemVersion) String() string {
return fmt.Sprintf("%d.%d.%d", osv.MajorVersion, osv.MinorVersion, osv.PatchVersion)
}
// OperatingSystemVersion returns the operating system version this restore image contains.
func (m *MacOSRestoreImage) OperatingSystemVersion() OperatingSystemVersion {
return m.operatingSystemVersion
}
// MostFeaturefulSupportedConfiguration returns the configuration requirements for the most featureful
// configuration supported by the current host and by this restore image.
//
// A MacOSRestoreImage can contain installation media for multiple Mac hardware models (MacHardwareModel). Some of these
// hardware models may not be supported by the current host. This method can be used to determine the hardware model and
// configuration requirements that will provide the most complete feature set on the current host.
// If none of the hardware models are supported on the current host, this property is nil.
func (m *MacOSRestoreImage) MostFeaturefulSupportedConfiguration() *MacOSConfigurationRequirements {
return newMacOSConfigurationRequirements(m.mostFeaturefulSupportedConfigurationPtr)
}
// MacOSConfigurationRequirements describes the parameter constraints required by a specific configuration of macOS.
//
// When a VZMacOSRestoreImage is loaded, it can be inspected to determine the configurations supported by that restore image.
type MacOSConfigurationRequirements struct {
minimumSupportedCPUCount uint64
minimumSupportedMemorySize uint64
hardwareModelPtr unsafe.Pointer
}
func newMacOSConfigurationRequirements(ptr unsafe.Pointer) *MacOSConfigurationRequirements {
ret := C.convertVZMacOSConfigurationRequirements2Struct(ptr)
return &MacOSConfigurationRequirements{
minimumSupportedCPUCount: uint64(ret.minimumSupportedCPUCount),
minimumSupportedMemorySize: uint64(ret.minimumSupportedMemorySize),
hardwareModelPtr: ret.hardwareModel,
}
}
// HardwareModel returns the hardware model for this configuration.
//
// The hardware model can be used to configure a new virtual machine that meets the requirements.
// Use VZMacPlatformConfiguration.hardwareModel to configure the Mac platform, and
// Use `WithCreatingStorage` functional option of the `NewMacAuxiliaryStorage` to create its auxiliary storage.
func (m *MacOSConfigurationRequirements) HardwareModel() *MacHardwareModel {
return newMacHardwareModel(m.hardwareModelPtr)
}
// MinimumSupportedCPUCount returns the minimum supported number of CPUs for this configuration.
func (m *MacOSConfigurationRequirements) MinimumSupportedCPUCount() uint64 {
return m.minimumSupportedCPUCount
}
// MinimumSupportedMemorySize returns the minimum supported memory size for this configuration.
func (m *MacOSConfigurationRequirements) MinimumSupportedMemorySize() uint64 {
return m.minimumSupportedMemorySize
}
type macOSRestoreImageHandler func(restoreImage *MacOSRestoreImage, err error)
//export macOSRestoreImageCompletionHandler
func macOSRestoreImageCompletionHandler(cgoHandlerPtr, restoreImagePtr, errPtr unsafe.Pointer) {
cgoHandler := *(*cgo.Handle)(cgoHandlerPtr)
handler := cgoHandler.Value().(macOSRestoreImageHandler)
defer cgoHandler.Delete()
restoreImageStruct := (*C.VZMacOSRestoreImageStruct)(restoreImagePtr)
restoreImage := &MacOSRestoreImage{
url: (*char)(restoreImageStruct.url).String(),
buildVersion: (*char)(restoreImageStruct.buildVersion).String(),
operatingSystemVersion: OperatingSystemVersion{
MajorVersion: int64(restoreImageStruct.operatingSystemVersion.majorVersion),
MinorVersion: int64(restoreImageStruct.operatingSystemVersion.minorVersion),
PatchVersion: int64(restoreImageStruct.operatingSystemVersion.patchVersion),
},
mostFeaturefulSupportedConfigurationPtr: restoreImageStruct.mostFeaturefulSupportedConfiguration,
}
if err := newNSError(errPtr); err != nil {
handler(restoreImage, err)
} else {
handler(restoreImage, nil)
}
}
// downloadRestoreImage resumable downloads macOS restore image (ipsw) file.
func downloadRestoreImage(ctx context.Context, url string, destPath string) (*progress.Reader, error) {
// open or create
f, err := os.OpenFile(destPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return nil, err
}
fileInfo, err := f.Stat()
if err != nil {
f.Close()
return nil, err
}
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
f.Close()
return nil, err
}
req.Header.Add("User-Agent", "github.com/Code-Hex/vz")
req.Header.Add("Range", fmt.Sprintf("bytes=%d-", fileInfo.Size()))
resp, err := http.DefaultClient.Do(req)
if err != nil {
f.Close()
return nil, err
}
if 200 > resp.StatusCode || resp.StatusCode >= 300 {
f.Close()
resp.Body.Close()
return nil, fmt.Errorf("unexpected http status code: %d", resp.StatusCode)
}
reader := progress.NewReader(resp.Body, resp.ContentLength, fileInfo.Size())
go func() {
defer f.Close()
defer resp.Body.Close()
_, err := io.Copy(f, reader)
reader.Finish(err)
}()
return reader, nil
}
// FetchLatestSupportedMacOSRestoreImage fetches the latest macOS restore image supported by this host from the network.
//
// After downloading the restore image, you can initialize a MacOSInstaller using LoadMacOSRestoreImageFromPath function
// with the local restore image file.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func FetchLatestSupportedMacOSRestoreImage(ctx context.Context, destPath string) (*progress.Reader, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
waitCh := make(chan struct{})
var (
url string
fetchErr error
)
handler := macOSRestoreImageHandler(func(restoreImage *MacOSRestoreImage, err error) {
url = restoreImage.URL()
fetchErr = err
defer close(waitCh)
})
cgoHandler := cgo.NewHandle(handler)
C.fetchLatestSupportedMacOSRestoreImageWithCompletionHandler(
unsafe.Pointer(&cgoHandler),
)
<-waitCh
if fetchErr != nil {
return nil, fetchErr
}
progressReader, err := downloadRestoreImage(ctx, url, destPath)
if err != nil {
return nil, fmt.Errorf("failed to download from %q: %w", url, err)
}
return progressReader, nil
}
// LoadMacOSRestoreImageFromPath loads a macOS restore image from a filepath on the local file system.
//
// If the imagePath parameter doesnt refer to a local file, the system raises an exception via Objective-C.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func LoadMacOSRestoreImageFromPath(imagePath string) (retImage *MacOSRestoreImage, retErr error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
if _, err := os.Stat(imagePath); err != nil {
return nil, err
}
waitCh := make(chan struct{})
handler := macOSRestoreImageHandler(func(restoreImage *MacOSRestoreImage, err error) {
retImage = restoreImage
retErr = err
close(waitCh)
})
cgoHandler := cgo.NewHandle(handler)
cs := charWithGoString(imagePath)
defer cs.Free()
C.loadMacOSRestoreImageFile(cs.CString(), unsafe.Pointer(&cgoHandler))
<-waitCh
return
}
// MacOSInstaller is a struct you use to install macOS on the specified virtual machine.
type MacOSInstaller struct {
*pointer
observerPointer *pointer
vm *VirtualMachine
progress atomic.Value
doneCh chan struct{}
once sync.Once
err error
}
// NewMacOSInstaller creates a new MacOSInstaller struct.
//
// A param vm is the virtual machine that the operating system will be installed onto.
// A param restoreImageIpsw is a file path indicating the macOS restore image to install.
//
// This is only supported on macOS 12 and newer, error will
// be returned on older versions.
func NewMacOSInstaller(vm *VirtualMachine, restoreImageIpsw string) (*MacOSInstaller, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
if _, err := os.Stat(restoreImageIpsw); err != nil {
return nil, err
}
cs := charWithGoString(restoreImageIpsw)
defer cs.Free()
ret := &MacOSInstaller{
pointer: objc.NewPointer(
C.newVZMacOSInstaller(objc.Ptr(vm), vm.dispatchQueue, cs.CString()),
),
observerPointer: objc.NewPointer(
C.newProgressObserverVZMacOSInstaller(),
),
vm: vm,
doneCh: make(chan struct{}),
}
ret.setFractionCompleted(0)
objc.SetFinalizer(ret, func(self *MacOSInstaller) {
objc.Release(self.observerPointer)
objc.Release(self)
})
return ret, nil
}
//export macOSInstallCompletionHandler
func macOSInstallCompletionHandler(cgoHandlerPtr, errPtr unsafe.Pointer) {
cgoHandler := *(*cgo.Handle)(cgoHandlerPtr)
handler := cgoHandler.Value().(func(error))
defer cgoHandler.Delete()
if err := newNSError(errPtr); err != nil {
handler(err)
} else {
handler(nil)
}
}
//export macOSInstallFractionCompletedHandler
func macOSInstallFractionCompletedHandler(cgoHandlerPtr unsafe.Pointer, completed C.double) {
cgoHandler := *(*cgo.Handle)(cgoHandlerPtr)
handler := cgoHandler.Value().(func(float64))
handler(float64(completed))
}
// Install starts installing macOS.
//
// This method starts the installation process. The VM must be in a stopped state.
// During the installation operation, pausing or stopping the VM results in an undefined behavior.
func (m *MacOSInstaller) Install(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
m.once.Do(func() {
completionHandler := cgo.NewHandle(func(err error) {
m.err = err
close(m.doneCh)
})
fractionCompletedHandler := cgo.NewHandle(func(v float64) {
m.setFractionCompleted(v)
})
C.installByVZMacOSInstaller(
objc.Ptr(m),
m.vm.dispatchQueue,
objc.Ptr(m.observerPointer),
unsafe.Pointer(&completionHandler),
unsafe.Pointer(&fractionCompletedHandler),
)
})
select {
case <-ctx.Done():
C.cancelInstallVZMacOSInstaller(objc.Ptr(m))
return ctx.Err()
case <-m.doneCh:
}
return m.err
}
func (m *MacOSInstaller) setFractionCompleted(completed float64) {
m.progress.Store(completed)
}
// FractionCompleted returns the fraction of the overall work that the install process
// completes.
func (m *MacOSInstaller) FractionCompleted() float64 {
return m.progress.Load().(float64)
}
// Done recieves a notification that indicates the install process is completed.
func (m *MacOSInstaller) Done() <-chan struct{} { return m.doneCh }

View File

@@ -0,0 +1,25 @@
//
// virtualization_debug.h
//
// Created by codehex.
//
#pragma once
#import <Foundation/Foundation.h>
#import <Virtualization/Virtualization.h>
@interface _VZDebugStubConfiguration : NSObject <NSCopying>
@end
@interface _VZGDBDebugStubConfiguration : NSObject <NSCopying>
@property NSInteger port;
- (instancetype)initWithPort:(NSInteger)port;
@end
@interface VZVirtualMachineConfiguration ()
- (void)_setDebugStub:(_VZDebugStubConfiguration *)config;
@end
void *newVZGDBDebugStubConfiguration(uint32_t port);
void setDebugStubVZVirtualMachineConfiguration(void *config, void *debugStub);

View File

@@ -0,0 +1,33 @@
//
// virtualization_debug.m
//
// Created by codehex.
//
#import "virtualization_debug.h"
#import "virtualization_helper.h"
/*!
@abstract Create a VZGDBDebugStubConfiguration with debug port for GDB server.
*/
void *newVZGDBDebugStubConfiguration(uint32_t port)
{
if (@available(macOS 12, *)) {
return [[_VZGDBDebugStubConfiguration alloc] initWithPort:(NSInteger)port];
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
/*!
@abstract _VZDebugStubConfiguration. Empty by default.
*/
void setDebugStubVZVirtualMachineConfiguration(void *config, void *debugStub)
{
if (@available(macOS 12, *)) {
[(VZVirtualMachineConfiguration *)config _setDebugStub:(_VZDebugStubConfiguration *)debugStub];
return;
}
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}

View File

@@ -0,0 +1,59 @@
#pragma once
#import <Availability.h>
#import <Foundation/Foundation.h>
NSDictionary *dumpProcessinfo();
#define RAISE_REASON_MESSAGE \
"This may possibly be a bug due to library handling errors.\n" \
"I would appreciate it if you could report it to https://github.com/Code-Hex/vz/issues/new/choose\n\n" \
"Information: %@\n"
#define RAISE_UNSUPPORTED_MACOS_EXCEPTION() \
do { \
[NSException \
raise:@"UnhandledAvailabilityException" \
format:@RAISE_REASON_MESSAGE, dumpProcessinfo()]; \
__builtin_unreachable(); \
} while (0)
// for macOS 12.3 API
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 120300
#define INCLUDE_TARGET_OSX_12_3 1
#else
#pragma message("macOS 12.3 API has been disabled")
#endif
// for macOS 13 API
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 130000
#define INCLUDE_TARGET_OSX_13 1
#else
#pragma message("macOS 13 API has been disabled")
#endif
static inline int mac_os_x_version_max_allowed()
{
#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED
return __MAC_OS_X_VERSION_MAX_ALLOWED;
#else
return 0;
#endif
}
typedef struct nbyteslice {
void *ptr;
int len;
} nbyteslice;
/* exported from cgo */
void virtualMachineCompletionHandler(void *cgoHandler, void *errPtr);
typedef void (^vm_completion_handler_t)(NSError *);
static inline vm_completion_handler_t makeVMCompletionHandler(void *completionHandler)
{
return Block_copy(^(NSError *err) {
virtualMachineCompletionHandler(completionHandler, err);
});
}

View File

@@ -0,0 +1,30 @@
//
// virtualization_helper.m
//
// Created by codehex.
//
#import "virtualization_helper.h"
#ifdef __arm64__
#define TARGET_ARM64 1
#else
#define TARGET_ARM64 0
#endif
NSDictionary *dumpProcessinfo()
{
NSString *osVersionString = [[NSProcessInfo processInfo] operatingSystemVersionString];
return @{
@"LLVM (Clang) Version" : @__VERSION__,
@"Target for arm64" : @TARGET_ARM64,
// The version of the macOS on which the process is executing.
@"Running OS Version" : osVersionString,
#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED
@"Max Allowed OS Version" : @__MAC_OS_X_VERSION_MAX_ALLOWED,
#endif
#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
@"Min Required OS Version" : @__MAC_OS_X_VERSION_MIN_REQUIRED,
#endif
};
}

View File

@@ -0,0 +1,31 @@
//
// virtualization_view.h
//
// Created by codehex.
//
#pragma once
#import <Availability.h>
#import <Cocoa/Cocoa.h>
#import <Virtualization/Virtualization.h>
@interface VZApplication : NSApplication {
bool shouldKeepRunning;
}
@end
@interface AboutViewController : NSViewController
- (instancetype)init;
@end
@interface AboutPanel : NSPanel
- (instancetype)init;
@end
API_AVAILABLE(macos(12.0))
@interface AppDelegate : NSObject <NSApplicationDelegate, NSWindowDelegate, VZVirtualMachineDelegate>
- (instancetype)initWithVirtualMachine:(VZVirtualMachine *)virtualMachine
windowWidth:(CGFloat)windowWidth
windowHeight:(CGFloat)windowHeight;
@end

View File

@@ -0,0 +1,374 @@
//
// virtualization_view.m
//
// Created by codehex.
//
#import "virtualization_view.h"
@implementation VZApplication
- (void)run
{
@autoreleasepool {
[self finishLaunching];
shouldKeepRunning = YES;
do {
NSEvent *event = [self
nextEventMatchingMask:NSEventMaskAny
untilDate:[NSDate distantFuture]
inMode:NSDefaultRunLoopMode
dequeue:YES];
// NSLog(@"event: %@", event);
[self sendEvent:event];
[self updateWindows];
} while (shouldKeepRunning);
}
}
- (void)terminate:(id)sender
{
shouldKeepRunning = NO;
// We should call this method if we want to use `applicationWillTerminate` method.
//
// [[NSNotificationCenter defaultCenter]
// postNotificationName:NSApplicationWillTerminateNotification
// object:NSApp];
// This method is used to end up the event loop.
// If no events are coming, the event loop will always be in a waiting state.
[self postEvent:self.currentEvent atStart:NO];
}
@end
@implementation AboutViewController
- (instancetype)init
{
self = [super initWithNibName:nil bundle:nil];
return self;
}
- (void)loadView
{
self.view = [NSView new];
NSImageView *imageView = [NSImageView imageViewWithImage:[NSApp applicationIconImage]];
NSTextField *appLabel = [self makeLabel:[[NSProcessInfo processInfo] processName]];
[appLabel setFont:[NSFont boldSystemFontOfSize:16]];
NSTextField *subLabel = [self makePoweredByLabel];
NSStackView *stackView = [NSStackView stackViewWithViews:@[
imageView,
appLabel,
subLabel,
]];
[stackView setOrientation:NSUserInterfaceLayoutOrientationVertical];
[stackView setDistribution:NSStackViewDistributionFillProportionally];
[stackView setSpacing:10];
[stackView setAlignment:NSLayoutAttributeCenterX];
[stackView setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal];
[stackView setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical];
[self.view addSubview:stackView];
[NSLayoutConstraint activateConstraints:@[
[imageView.widthAnchor constraintEqualToConstant:80], // image size
[imageView.heightAnchor constraintEqualToConstant:80], // image size
[stackView.topAnchor constraintEqualToAnchor:self.view.topAnchor
constant:4],
[stackView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor
constant:-16],
[stackView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor
constant:32],
[stackView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor
constant:-32],
[stackView.widthAnchor constraintEqualToConstant:300]
]];
}
- (NSTextField *)makePoweredByLabel
{
NSMutableAttributedString *poweredByAttr = [[[NSMutableAttributedString alloc]
initWithString:@"Powered by "
attributes:@{
NSForegroundColorAttributeName : [NSColor labelColor]
}] autorelease];
NSURL *repositoryURL = [NSURL URLWithString:@"https://github.com/Code-Hex/vz"];
NSMutableAttributedString *repository = [self makeHyperLink:@"github.com/Code-Hex/vz" withURL:repositoryURL];
[poweredByAttr appendAttributedString:repository];
[poweredByAttr addAttribute:NSFontAttributeName
value:[NSFont systemFontOfSize:12]
range:NSMakeRange(0, [poweredByAttr length])];
NSTextField *label = [self makeLabel:@""];
[label setSelectable:YES];
[label setAllowsEditingTextAttributes:YES];
[label setAttributedStringValue:poweredByAttr];
return label;
}
- (NSTextField *)makeLabel:(NSString *)label
{
NSTextField *appLabel = [NSTextField labelWithString:label];
[appLabel setTextColor:[NSColor labelColor]];
[appLabel setEditable:NO];
[appLabel setSelectable:NO];
[appLabel setBezeled:NO];
[appLabel setBordered:NO];
[appLabel setBackgroundColor:[NSColor clearColor]];
[appLabel setAlignment:NSTextAlignmentCenter];
[appLabel setLineBreakMode:NSLineBreakByWordWrapping];
[appLabel setUsesSingleLineMode:NO];
[appLabel setMaximumNumberOfLines:20];
return appLabel;
}
// https://developer.apple.com/library/archive/qa/qa1487/_index.html
- (NSMutableAttributedString *)makeHyperLink:(NSString *)inString withURL:(NSURL *)aURL
{
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:inString];
NSRange range = NSMakeRange(0, [attrString length]);
[attrString beginEditing];
[attrString addAttribute:NSLinkAttributeName value:[aURL absoluteString] range:range];
// make the text appear in blue
[attrString addAttribute:NSForegroundColorAttributeName value:[NSColor blueColor] range:range];
// next make the text appear with an underline
[attrString addAttribute:NSUnderlineStyleAttributeName
value:[NSNumber numberWithInt:NSUnderlineStyleSingle]
range:range];
[attrString endEditing];
return [attrString autorelease];
}
@end
@implementation AboutPanel
- (instancetype)init
{
self = [super initWithContentRect:NSZeroRect styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable backing:NSBackingStoreBuffered defer:NO];
AboutViewController *viewController = [[[AboutViewController alloc] init] autorelease];
[self setContentViewController:viewController];
[self setTitleVisibility:NSWindowTitleHidden];
[self setTitlebarAppearsTransparent:YES];
[self setBecomesKeyOnlyIfNeeded:NO];
[self center];
return self;
}
@end
@implementation AppDelegate {
VZVirtualMachine *_virtualMachine;
VZVirtualMachineView *_virtualMachineView;
CGFloat _windowWidth;
CGFloat _windowHeight;
}
- (instancetype)initWithVirtualMachine:(VZVirtualMachine *)virtualMachine
windowWidth:(CGFloat)windowWidth
windowHeight:(CGFloat)windowHeight
{
self = [super init];
_virtualMachine = virtualMachine;
_virtualMachine.delegate = self;
// Setup virtual machine view configs
VZVirtualMachineView *view = [[[VZVirtualMachineView alloc] init] autorelease];
view.capturesSystemKeys = YES;
view.virtualMachine = _virtualMachine;
_virtualMachineView = view;
// Setup some window configs
_windowWidth = windowWidth;
_windowHeight = windowHeight;
return self;
}
/* IMPORTANT: delegate methods are called from VM's queue */
- (void)guestDidStopVirtualMachine:(VZVirtualMachine *)virtualMachine
{
[NSApp performSelectorOnMainThread:@selector(terminate:) withObject:self waitUntilDone:NO];
}
- (void)virtualMachine:(VZVirtualMachine *)virtualMachine didStopWithError:(NSError *)error
{
NSLog(@"VM %@ didStopWithError: %@", virtualMachine, error);
[NSApp performSelectorOnMainThread:@selector(terminate:) withObject:self waitUntilDone:NO];
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
[self setupMenuBar];
[self setupGraphicWindow];
// These methods are required to call here. Because the menubar will be not active even if
// application is running.
// See: https://stackoverflow.com/questions/62739862/why-doesnt-activateignoringotherapps-enable-the-menu-bar
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp activateIgnoringOtherApps:YES];
}
- (void)windowWillClose:(NSNotification *)notification
{
[NSApp performSelectorOnMainThread:@selector(terminate:) withObject:self waitUntilDone:NO];
}
- (void)setupGraphicWindow
{
NSRect rect = NSMakeRect(0, 0, _windowWidth, _windowHeight);
NSWindow *window = [[[NSWindow alloc] initWithContentRect:rect
styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable //|NSTexturedBackgroundWindowMask
backing:NSBackingStoreBuffered
defer:NO] autorelease];
[window setOpaque:NO];
[window setContentView:_virtualMachineView];
[window setTitleVisibility:NSWindowTitleHidden];
[window center];
[window setDelegate:self];
[window makeKeyAndOrderFront:nil];
// This code to prevent crash when called applicationShouldTerminateAfterLastWindowClosed.
// https://stackoverflow.com/a/13470694
[window setReleasedWhenClosed:NO];
}
- (void)setupMenuBar
{
NSMenu *menuBar = [[[NSMenu alloc] init] autorelease];
NSMenuItem *menuBarItem = [[[NSMenuItem alloc] init] autorelease];
[menuBar addItem:menuBarItem];
[NSApp setMainMenu:menuBar];
// App menu
NSMenu *appMenu = [self setupApplicationMenu];
[menuBarItem setSubmenu:appMenu];
// Window menu
NSMenu *windowMenu = [self setupWindowMenu];
NSMenuItem *windowMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
[menuBar addItem:windowMenuItem];
[windowMenuItem setSubmenu:windowMenu];
// Help menu
NSMenu *helpMenu = [self setupHelpMenu];
NSMenuItem *helpMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Help" action:nil keyEquivalent:@""] autorelease];
[menuBar addItem:helpMenuItem];
[helpMenuItem setSubmenu:helpMenu];
}
- (NSMenu *)setupApplicationMenu
{
NSMenu *appMenu = [[[NSMenu alloc] init] autorelease];
NSString *applicationName = [[NSProcessInfo processInfo] processName];
NSMenuItem *aboutMenuItem = [[[NSMenuItem alloc]
initWithTitle:[NSString stringWithFormat:@"About %@", applicationName]
action:@selector(openAboutWindow:)
keyEquivalent:@""] autorelease];
// CapturesSystemKeys toggle
NSMenuItem *capturesSystemKeysItem = [[[NSMenuItem alloc]
initWithTitle:@"Enable to send system hot keys to virtual machine"
action:@selector(toggleCapturesSystemKeys:)
keyEquivalent:@""] autorelease];
[capturesSystemKeysItem setState:[self capturesSystemKeysState]];
// Service menu
NSMenuItem *servicesMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Services" action:nil keyEquivalent:@""] autorelease];
NSMenu *servicesMenu = [[[NSMenu alloc] initWithTitle:@"Services"] autorelease];
[servicesMenuItem setSubmenu:servicesMenu];
[NSApp setServicesMenu:servicesMenu];
NSMenuItem *hideOthersItem = [[[NSMenuItem alloc]
initWithTitle:@"Hide Others"
action:@selector(hideOtherApplications:)
keyEquivalent:@"h"] autorelease];
[hideOthersItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption | NSEventModifierFlagCommand)];
NSArray *menuItems = @[
aboutMenuItem,
[NSMenuItem separatorItem],
capturesSystemKeysItem,
[NSMenuItem separatorItem],
servicesMenuItem,
[NSMenuItem separatorItem],
[[[NSMenuItem alloc]
initWithTitle:[@"Hide " stringByAppendingString:applicationName]
action:@selector(hide:)
keyEquivalent:@"h"] autorelease],
hideOthersItem,
[NSMenuItem separatorItem],
[[[NSMenuItem alloc]
initWithTitle:[@"Quit " stringByAppendingString:applicationName]
action:@selector(terminate:)
keyEquivalent:@"q"] autorelease],
];
for (NSMenuItem *menuItem in menuItems) {
[appMenu addItem:menuItem];
}
return appMenu;
}
- (NSMenu *)setupWindowMenu
{
NSMenu *windowMenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
NSArray *menuItems = @[
[[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease],
[[[NSMenuItem alloc] initWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""] autorelease],
[NSMenuItem separatorItem],
[[[NSMenuItem alloc] initWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""] autorelease],
];
for (NSMenuItem *menuItem in menuItems) {
[windowMenu addItem:menuItem];
}
[NSApp setWindowsMenu:windowMenu];
return windowMenu;
}
- (NSMenu *)setupHelpMenu
{
NSMenu *helpMenu = [[[NSMenu alloc] initWithTitle:@"Help"] autorelease];
NSArray *menuItems = @[
[[[NSMenuItem alloc] initWithTitle:@"Report issue" action:@selector(reportIssue:) keyEquivalent:@""] autorelease],
];
for (NSMenuItem *menuItem in menuItems) {
[helpMenu addItem:menuItem];
}
[NSApp setHelpMenu:helpMenu];
return helpMenu;
}
- (void)toggleCapturesSystemKeys:(id)sender
{
NSMenuItem *item = (NSMenuItem *)sender;
_virtualMachineView.capturesSystemKeys = !_virtualMachineView.capturesSystemKeys;
[item setState:[self capturesSystemKeysState]];
}
- (NSControlStateValue)capturesSystemKeysState
{
return _virtualMachineView.capturesSystemKeys ? NSControlStateValueOn : NSControlStateValueOff;
}
- (void)reportIssue:(id)sender
{
NSString *url = @"https://github.com/Code-Hex/vz/issues/new";
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:url]];
}
- (void)openAboutWindow:(id)sender
{
AboutPanel *aboutPanel = [[[AboutPanel alloc] init] autorelease];
[aboutPanel makeKeyAndOrderFront:nil];
}
@end

View File

@@ -0,0 +1,30 @@
// Code generated by "stringer -type=VirtualMachineState"; DO NOT EDIT.
package vz
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[VirtualMachineStateStopped-0]
_ = x[VirtualMachineStateRunning-1]
_ = x[VirtualMachineStatePaused-2]
_ = x[VirtualMachineStateError-3]
_ = x[VirtualMachineStateStarting-4]
_ = x[VirtualMachineStatePausing-5]
_ = x[VirtualMachineStateResuming-6]
_ = x[VirtualMachineStateStopping-7]
}
const _VirtualMachineState_name = "VirtualMachineStateStoppedVirtualMachineStateRunningVirtualMachineStatePausedVirtualMachineStateErrorVirtualMachineStateStartingVirtualMachineStatePausingVirtualMachineStateResumingVirtualMachineStateStopping"
var _VirtualMachineState_index = [...]uint8{0, 26, 52, 77, 101, 128, 154, 181, 208}
func (i VirtualMachineState) String() string {
if i < 0 || i >= VirtualMachineState(len(_VirtualMachineState_index)-1) {
return "VirtualMachineState(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _VirtualMachineState_name[_VirtualMachineState_index[i]:_VirtualMachineState_index[i+1]]
}

View File

@@ -0,0 +1,77 @@
package vz
// Error type returned by the Virtualization framework.
// The NSError domain is VZErrorDomain, the code is one of the ErrorCode constants.
//
//go:generate stringer -type=ErrorCode
type ErrorCode int
const (
// ErrorInternal is an internal error such as the virtual machine unexpectedly stopping.
ErrorInternal ErrorCode = 1 + iota
// ErrorInvalidVirtualMachineConfiguration represents invalid machine configuration.
ErrorInvalidVirtualMachineConfiguration
// ErrorInvalidVirtualMachineState represents API used with a machine in the wrong state
// (e.g. interacting with a machine before it is running).
ErrorInvalidVirtualMachineState
// ErrorInvalidVirtualMachineStateTransition is invalid change of state
// (e.g. pausing a virtual machine that is not started).
ErrorInvalidVirtualMachineStateTransition
// ErrorInvalidDiskImage represents unrecognized disk image format or invalid disk image.
ErrorInvalidDiskImage
// ErrorVirtualMachineLimitExceeded represents the running virtual machine limit was exceeded.
// Available from macOS 12.0 and above.
ErrorVirtualMachineLimitExceeded
// ErrorNetworkError represents network error occurred.
// Available from macOS 13.0 and above.
ErrorNetworkError
// ErrorOutOfDiskSpace represents machine ran out of disk space.
// Available from macOS 13.0 and above.
ErrorOutOfDiskSpace
// ErrorOperationCancelled represents the operation was cancelled.
// Available from macOS 13.0 and above.
ErrorOperationCancelled
// ErrorNotSupported represents the operation is not supported.
// Available from macOS 13.0 and above.
ErrorNotSupported
)
/* macOS installation errors. */
const (
// ErrorRestoreImageCatalogLoadFailed represents the restore image catalog failed to load.
// Available from macOS 13.0 and above.
ErrorRestoreImageCatalogLoadFailed ErrorCode = 10001 + iota
// ErrorInvalidRestoreImageCatalog represents the restore image catalog is invalid.
// Available from macOS 13.0 and above.
ErrorInvalidRestoreImageCatalog
// ErrorNoSupportedRestoreImagesInCatalog represents the restore image catalog has no supported restore images.
// Available from macOS 13.0 and above.
ErrorNoSupportedRestoreImagesInCatalog
// ErrorRestoreImageLoadFailed represents the restore image failed to load.
// Available from macOS 13.0 and above.
ErrorRestoreImageLoadFailed
// ErrorInvalidRestoreImage represents the restore image is invalid.
// Available from macOS 13.0 and above.
ErrorInvalidRestoreImage
// ErrorInstallationRequiresUpdate represents a software update is required to complete the installation.
// Available from macOS 13.0 and above.
ErrorInstallationRequiresUpdate
// ErrorInstallationFailed is an error occurred during installation.
// Available from macOS 13.0 and above.
ErrorInstallationFailed
)

View File

@@ -1,283 +0,0 @@
package vz
/*
#cgo darwin CFLAGS: -x objective-c -fno-objc-arc
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization
# include "virtualization.h"
*/
import "C"
import (
"runtime"
"sync"
"unsafe"
"github.com/rs/xid"
)
func init() {
startNSThread()
}
// 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
}
//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
}

View File

@@ -1,570 +0,0 @@
//
// virtualization.m
//
// Created by codehex.
//
#import "virtualization.h"
char *copyCString(NSString *nss)
{
const char *cc = [nss UTF8String];
char *c = calloc([nss length]+1, 1);
strncpy(c, cc, [nss length]);
return c;
}
@implementation Observer
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
{
@autoreleasepool {
if ([keyPath isEqualToString:@"state"]) {
int newState = (int)[change[NSKeyValueChangeNewKey] integerValue];
char *vmid = copyCString((NSString *)context);
changeStateOnObserver(newState, vmid);
free(vmid);
} else {
// bool canVal = (bool)[change[NSKeyValueChangeNewKey] boolValue];
// char *vmid = copyCString((NSString *)context);
// char *key = copyCString(keyPath);
// changeCanPropertyOnObserver(canVal, vmid, key);
// free(vmid);
// free(key);
}
}
}
@end
/*!
@abstract Create a VZLinuxBootLoader with the Linux kernel passed as URL.
@param kernelPath Path of Linux kernel on the local file system.
*/
void *newVZLinuxBootLoader(const char *kernelPath)
{
VZLinuxBootLoader *ret;
@autoreleasepool {
NSString *kernelPathNSString = [NSString stringWithUTF8String:kernelPath];
NSURL *kernelURL = [NSURL fileURLWithPath:kernelPathNSString];
ret = [[VZLinuxBootLoader alloc] initWithKernelURL:kernelURL];
}
return ret;
}
/*!
@abstract Set the command-line parameters.
@param bootLoader VZLinuxBootLoader
@param commandLine The command-line parameters passed to the kernel on boot.
@link https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html
*/
void setCommandLineVZLinuxBootLoader(void *bootLoaderPtr, const char *commandLine)
{
VZLinuxBootLoader *bootLoader = (VZLinuxBootLoader *)bootLoaderPtr;
@autoreleasepool {
NSString *commandLineNSString = [NSString stringWithUTF8String:commandLine];
[bootLoader setCommandLine:commandLineNSString];
}
}
/*!
@abstract Set the optional initial RAM disk.
@param bootLoader VZLinuxBootLoader
@param ramdiskPath The RAM disk is mapped into memory before booting the kernel.
@link https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html
*/
void setInitialRamdiskURLVZLinuxBootLoader(void *bootLoaderPtr, const char *ramdiskPath)
{
VZLinuxBootLoader *bootLoader = (VZLinuxBootLoader *)bootLoaderPtr;
@autoreleasepool {
NSString *ramdiskPathNSString = [NSString stringWithUTF8String:ramdiskPath];
NSURL *ramdiskURL = [NSURL fileURLWithPath:ramdiskPathNSString];
[bootLoader setInitialRamdiskURL:ramdiskURL];
}
}
/*!
@abstract Validate the configuration.
@param config Virtual machine configuration.
@param error If not nil, assigned with the validation error if the validation failed.
@return true if the configuration is valid.
*/
bool validateVZVirtualMachineConfiguration(void *config, void **error)
{
return (bool)[(VZVirtualMachineConfiguration *)config
validateWithError:(NSError * _Nullable * _Nullable)error];
}
/*!
@abstract Create a new Virtual machine configuration.
@param bootLoader Boot loader used when the virtual machine starts.
@param CPUCount Number of CPUs.
@discussion
The number of CPUs must be a value between VZVirtualMachineConfiguration.minimumAllowedCPUCount
and VZVirtualMachineConfiguration.maximumAllowedCPUCount.
@see VZVirtualMachineConfiguration.minimumAllowedCPUCount
@see VZVirtualMachineConfiguration.maximumAllowedCPUCount
@param memorySize Virtual machine memory size in bytes.
@discussion
The memory size must be a multiple of a 1 megabyte (1024 * 1024 bytes) between VZVirtualMachineConfiguration.minimumAllowedMemorySize
and VZVirtualMachineConfiguration.maximumAllowedMemorySize.
The memorySize represents the total physical memory seen by a guest OS running in the virtual machine.
Not all memory is allocated on start, the virtual machine allocates memory on demand.
@see VZVirtualMachineConfiguration.minimumAllowedMemorySize
@see VZVirtualMachineConfiguration.maximumAllowedMemorySize
*/
void *newVZVirtualMachineConfiguration(void *bootLoaderPtr,
unsigned int CPUCount,
unsigned long long memorySize)
{
VZVirtualMachineConfiguration *config = [[VZVirtualMachineConfiguration alloc] init];
[config setBootLoader:(VZLinuxBootLoader *)bootLoaderPtr];
[config setCPUCount:(NSUInteger)CPUCount];
[config setMemorySize:memorySize];
return config;
}
/*!
@abstract List of entropy devices. Empty by default.
@see VZVirtioEntropyDeviceConfiguration
*/
void setEntropyDevicesVZVirtualMachineConfiguration(void *config,
void *entropyDevices)
{
[(VZVirtualMachineConfiguration *)config setEntropyDevices:[(NSMutableArray *)entropyDevices copy]];
}
/*!
@abstract List of memory balloon devices. Empty by default.
@see VZVirtioTraditionalMemoryBalloonDeviceConfiguration
*/
void setMemoryBalloonDevicesVZVirtualMachineConfiguration(void *config,
void *memoryBalloonDevices)
{
[(VZVirtualMachineConfiguration *)config setMemoryBalloonDevices:[(NSMutableArray *)memoryBalloonDevices copy]];
}
/*!
@abstract List of network adapters. Empty by default.
@see VZVirtioNetworkDeviceConfiguration
*/
void setNetworkDevicesVZVirtualMachineConfiguration(void *config,
void *networkDevices)
{
[(VZVirtualMachineConfiguration *)config setNetworkDevices:[(NSMutableArray *)networkDevices copy]];
}
/*!
@abstract List of serial ports. Empty by default.
@see VZVirtioConsoleDeviceSerialPortConfiguration
*/
void setSerialPortsVZVirtualMachineConfiguration(void *config,
void *serialPorts)
{
[(VZVirtualMachineConfiguration *)config setSerialPorts:[(NSMutableArray *)serialPorts copy]];
}
/*!
@abstract List of socket devices. Empty by default.
@see VZVirtioSocketDeviceConfiguration
*/
void setSocketDevicesVZVirtualMachineConfiguration(void *config,
void *socketDevices)
{
[(VZVirtualMachineConfiguration *)config setSocketDevices:[(NSMutableArray *)socketDevices copy]];
}
/*!
@abstract List of disk devices. Empty by default.
@see VZVirtioBlockDeviceConfiguration
*/
void setStorageDevicesVZVirtualMachineConfiguration(void *config,
void *storageDevices)
{
[(VZVirtualMachineConfiguration *)config setStorageDevices:[(NSMutableArray *)storageDevices copy]];
}
/*!
@abstract Intialize the VZFileHandleSerialPortAttachment from file descriptors.
@param readFileDescriptor File descriptor for reading from the file.
@param writeFileDescriptor File descriptor for writing to the file.
@discussion
Each file descriptor must a valid.
*/
void *newVZFileHandleSerialPortAttachment(int readFileDescriptor, int writeFileDescriptor)
{
VZFileHandleSerialPortAttachment *ret;
@autoreleasepool {
NSFileHandle *fileHandleForReading = [[NSFileHandle alloc] initWithFileDescriptor:readFileDescriptor];
NSFileHandle *fileHandleForWriting = [[NSFileHandle alloc] initWithFileDescriptor:writeFileDescriptor];
ret = [[VZFileHandleSerialPortAttachment alloc]
initWithFileHandleForReading:fileHandleForReading
fileHandleForWriting:fileHandleForWriting];
}
return ret;
}
/*!
@abstract Initialize the VZFileSerialPortAttachment from a URL of a file.
@param filePath The path of the file for the attachment on the local file system.
@param shouldAppend True if the file should be opened in append mode, false otherwise.
When a file is opened in append mode, writing to that file will append to the end of it.
@param error If not nil, used to report errors if intialization fails.
@return A VZFileSerialPortAttachment on success. Nil otherwise and the error parameter is populated if set.
*/
void *newVZFileSerialPortAttachment(const char *filePath, bool shouldAppend, void **error)
{
VZFileSerialPortAttachment *ret;
@autoreleasepool {
NSString *filePathNSString = [NSString stringWithUTF8String:filePath];
NSURL *fileURL = [NSURL fileURLWithPath:filePathNSString];
ret = [[VZFileSerialPortAttachment alloc]
initWithURL:fileURL append:(BOOL)shouldAppend error:(NSError * _Nullable * _Nullable)error];
}
return ret;
}
/*!
@abstract Create a new Virtio Console Serial Port Device configuration
@param attachment Base class for a serial port attachment.
@discussion
The device creates a console which enables communication between the host and the guest through the Virtio interface.
The device sets up a single port on the Virtio console device.
*/
void *newVZVirtioConsoleDeviceSerialPortConfiguration(void *attachment)
{
VZVirtioConsoleDeviceSerialPortConfiguration *config = [[VZVirtioConsoleDeviceSerialPortConfiguration alloc] init];
[config setAttachment:(VZSerialPortAttachment *)attachment];
return config;
}
/*!
@abstract Create a new Network device attachment bridging a host physical interface with a virtual network device.
@param networkInterface a network interface that bridges a physical interface.
@discussion
A bridged network allows the virtual machine to use the same physical interface as the host. Both host and virtual machine
send and receive packets on the same physical interface but have distinct network layers.
The bridge network device attachment is used with a VZNetworkDeviceConfiguration to define a virtual network device.
Using a VZBridgedNetworkDeviceAttachment requires the app to have the "com.apple.vm.networking" entitlement.
@see VZBridgedNetworkInterface
@see VZNetworkDeviceConfiguration
@see VZVirtioNetworkDeviceConfiguration
*/
void *newVZBridgedNetworkDeviceAttachment(void *networkInterface)
{
return [[VZBridgedNetworkDeviceAttachment alloc] initWithInterface:(VZBridgedNetworkInterface *)networkInterface];
}
/*!
@abstract Create a new Network device attachment using network address translation (NAT) with outside networks.
@discussion
Using the NAT attachment type, the host serves as router and performs network address translation for accesses to outside networks.
@see VZNetworkDeviceConfiguration
@see VZVirtioNetworkDeviceConfiguration
*/
void *newVZNATNetworkDeviceAttachment()
{
return [[VZNATNetworkDeviceAttachment alloc] init];
}
/*!
@abstract Create a new Network device attachment sending raw network packets over a file handle.
@discussion
The file handle attachment transmits the raw packets/frames between the virtual network interface and a file handle.
The data transmitted through this attachment is at the level of the data link layer.
The file handle must hold a connected datagram socket.
@see VZNetworkDeviceConfiguration
@see VZVirtioNetworkDeviceConfiguration
*/
void *newVZFileHandleNetworkDeviceAttachment(int fileDescriptor)
{
VZFileHandleNetworkDeviceAttachment *ret;
@autoreleasepool {
NSFileHandle *fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor];
ret = [[VZFileHandleNetworkDeviceAttachment alloc] initWithFileHandle:fileHandle];
}
return ret;
}
/*!
@abstract Create a new Configuration of a paravirtualized network device of type Virtio Network Device.
@discussion
The communication channel used on the host is defined through the attachment. It is set with the VZNetworkDeviceConfiguration.attachment property.
The configuration is only valid with valid MACAddress and attachment.
@see VZVirtualMachineConfiguration.networkDevices
@param attachment Base class for a network device attachment.
@discussion
A network device attachment defines how a virtual network device interfaces with the host system.
VZNetworkDeviceAttachment should not be instantiated directly. One of its subclasses should be used instead.
Common attachment types include:
- VZNATNetworkDeviceAttachment
- VZFileHandleNetworkDeviceAttachment
@see VZBridgedNetworkDeviceAttachment
@see VZFileHandleNetworkDeviceAttachment
@see VZNATNetworkDeviceAttachment
*/
void *newVZVirtioNetworkDeviceConfiguration(void *attachment)
{
VZVirtioNetworkDeviceConfiguration *config = [[VZVirtioNetworkDeviceConfiguration alloc] init];
[config setAttachment:(VZNetworkDeviceAttachment *)attachment];
return config;
}
/*!
@abstract Create a new Virtio Entropy Device confiuration
@discussion The device exposes a source of entropy for the guest's random number generator.
*/
void *newVZVirtioEntropyDeviceConfiguration()
{
return [[VZVirtioEntropyDeviceConfiguration alloc] init];
}
/*!
@abstract Initialize a VZVirtioBlockDeviceConfiguration with a device attachment.
@param attachment The storage device attachment. This defines how the virtualized device operates on the host side.
@see VZDiskImageStorageDeviceAttachment
*/
void *newVZVirtioBlockDeviceConfiguration(void *attachment)
{
return [[VZVirtioBlockDeviceConfiguration alloc] initWithAttachment:(VZStorageDeviceAttachment *)attachment];
}
/*!
@abstract Initialize the attachment from a local file url.
@param diskPath Local file path to the disk image in RAW format.
@param readOnly If YES, the device attachment is read-only, otherwise the device can write data to the disk image.
@param error If not nil, assigned with the error if the initialization failed.
@return A VZDiskImageStorageDeviceAttachment on success. Nil otherwise and the error parameter is populated if set.
*/
void *newVZDiskImageStorageDeviceAttachment(const char *diskPath, bool readOnly, void **error)
{
VZDiskImageStorageDeviceAttachment *ret;
@autoreleasepool {
NSString *diskPathNSString = [NSString stringWithUTF8String:diskPath];
NSURL *diskURL = [NSURL fileURLWithPath:diskPathNSString];
ret = [[VZDiskImageStorageDeviceAttachment alloc]
initWithURL:diskURL
readOnly:(BOOL)readOnly
error:(NSError * _Nullable * _Nullable)error];
}
return ret;
}
/*!
@abstract Create a configuration of the Virtio traditional memory balloon device.
@discussion
This configuration creates a Virtio traditional memory balloon device which allows for managing guest memory.
Only one Virtio traditional memory balloon device can be used per virtual machine.
@see VZVirtioTraditionalMemoryBalloonDevice
*/
void *newVZVirtioTraditionalMemoryBalloonDeviceConfiguration()
{
return [[VZVirtioTraditionalMemoryBalloonDeviceConfiguration alloc] init];
}
/*!
@abstract Create a configuration of the Virtio socket device.
@discussion
This configuration creates a Virtio socket device for the guest which communicates with the host through the Virtio interface.
Only one Virtio socket device can be used per virtual machine.
@see VZVirtioSocketDevice
*/
void *newVZVirtioSocketDeviceConfiguration()
{
return [[VZVirtioSocketDeviceConfiguration alloc] init];
}
/*!
@abstract Initialize the virtual machine.
@param config The configuration of the virtual machine.
The configuration must be valid. Validation can be performed at runtime with [VZVirtualMachineConfiguration validateWithError:].
The configuration is copied by the initializer.
@param queue The serial queue on which the virtual machine operates.
Every operation on the virtual machine must be done on that queue. The callbacks and delegate methods are invoked on that queue.
If the queue is not serial, the behavior is undefined.
*/
void *newVZVirtualMachineWithDispatchQueue(void *config, void *queue, const char *vmid)
{
VZVirtualMachine *vm = [[VZVirtualMachine alloc]
initWithConfiguration:(VZVirtualMachineConfiguration *)config
queue:(dispatch_queue_t)queue];
@autoreleasepool {
Observer *o = [[Observer alloc] init];
NSString *str = [NSString stringWithUTF8String:vmid];
[vm addObserver:o forKeyPath:@"state"
options:NSKeyValueObservingOptionNew
context:[str copy]];
}
return vm;
}
/*!
@abstract Initialize the VZMACAddress from a string representation of a MAC address.
@param string
The string should be formatted representing the 6 bytes in hexadecimal separated by a colon character.
e.g. "01:23:45:ab:cd:ef"
The alphabetical characters can appear lowercase or uppercase.
@return A VZMACAddress or nil if the string is not formatted correctly.
*/
void *newVZMACAddress(const char *macAddress)
{
VZMACAddress *ret;
@autoreleasepool {
NSString *str = [NSString stringWithUTF8String:macAddress];
ret = [[VZMACAddress alloc] initWithString:str];
}
return ret;
}
/*!
@abstract Create a valid, random, unicast, locally administered address.
@discussion The generated address is not guaranteed to be unique.
*/
void *newRandomLocallyAdministeredVZMACAddress()
{
return [VZMACAddress randomLocallyAdministeredAddress];
}
/*!
@abstract Sets the media access control address of the device.
*/
void setNetworkDevicesVZMACAddress(void *config, void *macAddress)
{
[(VZNetworkDeviceConfiguration *)config setMACAddress:[(VZMACAddress *)macAddress copy]];
}
/*!
@abstract The address represented as a string.
@discussion
The 6 bytes are represented in hexadecimal form, separated by a colon character.
Alphabetical characters are lowercase.
The address is compatible with the parameter of -[VZMACAddress initWithString:].
*/
const char *getVZMACAddressString(void *macAddress)
{
return [[(VZMACAddress *)macAddress string] UTF8String];
}
/*!
@abstract Request that the guest turns itself off.
@param error If not nil, assigned with the error if the request failed.
@return YES if the request was made successfully.
*/
bool requestStopVirtualMachine(void *machine, void *queue, void **error)
{
__block BOOL ret;
dispatch_sync((dispatch_queue_t)queue, ^{
ret = [(VZVirtualMachine *)machine requestStopWithError:(NSError * _Nullable *_Nullable)error];
});
return (bool)ret;
}
void *makeDispatchQueue(const char *label)
{
//dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0);
dispatch_queue_t queue = dispatch_queue_create(label, DISPATCH_QUEUE_SERIAL);
//dispatch_retain(queue);
return queue;
}
typedef void (^handler_t)(NSError *);
handler_t generateHandler(const char *vmid, void handler(void *, char *))
{
handler_t ret;
@autoreleasepool {
NSString *str = [NSString stringWithUTF8String:vmid];
ret = Block_copy(^(NSError *err){
handler(err, copyCString(str));
});
}
return ret;
}
void startWithCompletionHandler(void *machine, void *queue, const char *vmid)
{
handler_t handler = generateHandler(vmid, startHandler);
dispatch_sync((dispatch_queue_t)queue, ^{
[(VZVirtualMachine *)machine startWithCompletionHandler:handler];
});
Block_release(handler);
}
void pauseWithCompletionHandler(void *machine, void *queue, const char *vmid)
{
handler_t handler = generateHandler(vmid, pauseHandler);
dispatch_sync((dispatch_queue_t)queue, ^{
[(VZVirtualMachine *)machine pauseWithCompletionHandler:handler];
});
Block_release(handler);
}
void resumeWithCompletionHandler(void *machine, void *queue, const char *vmid)
{
handler_t handler = generateHandler(vmid, pauseHandler);
dispatch_sync((dispatch_queue_t)queue, ^{
[(VZVirtualMachine *)machine resumeWithCompletionHandler:handler];
});
Block_release(handler);
}
// TODO(codehex): use KVO
bool vmCanStart(void *machine, void *queue)
{
__block BOOL result;
dispatch_sync((dispatch_queue_t)queue, ^{
result = ((VZVirtualMachine *)machine).canStart;
});
return (bool)result;
}
bool vmCanPause(void *machine, void *queue)
{
__block BOOL result;
dispatch_sync((dispatch_queue_t)queue, ^{
result = ((VZVirtualMachine *)machine).canPause;
});
return (bool)result;
}
bool vmCanResume(void *machine, void *queue)
{
__block BOOL result;
dispatch_sync((dispatch_queue_t)queue, ^{
result = ((VZVirtualMachine *)machine).canResume;
});
return (bool)result;
}
bool vmCanRequestStop(void *machine, void *queue)
{
__block BOOL result;
dispatch_sync((dispatch_queue_t)queue, ^{
result = ((VZVirtualMachine *)machine).canRequestStop;
});
return (bool)result;
}
// --- TODO end

View File

@@ -1,27 +0,0 @@
version: 1.0.0.{build}
platform: x64
branches:
only:
- master
clone_folder: c:\gopath\src\github.com\rs\xid
environment:
GOPATH: c:\gopath
install:
- echo %PATH%
- echo %GOPATH%
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
- go version
- go env
- go get -t .
build_script:
- go build
test_script:
- go test

View File

@@ -1,8 +0,0 @@
language: go
go:
- "1.9"
- "1.10"
- "master"
matrix:
allow_failures:
- go: "master"

View File

@@ -1,19 +0,0 @@
Copyright (c) 2015 Olivier Poitrey <rs@dailymotion.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,115 +0,0 @@
# Globally Unique ID Generator
[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/xid) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/xid/master/LICENSE) [![Build Status](https://travis-ci.org/rs/xid.svg?branch=master)](https://travis-ci.org/rs/xid) [![Coverage](http://gocover.io/_badge/github.com/rs/xid)](http://gocover.io/github.com/rs/xid)
Package xid is a globally unique id generator library, ready to safely be used directly in your server code.
Xid uses the Mongo Object ID algorithm to generate globally unique ids with a different serialization (base64) to make it shorter when transported as a string:
https://docs.mongodb.org/manual/reference/object-id/
- 4-byte value representing the seconds since the Unix epoch,
- 3-byte machine identifier,
- 2-byte process id, and
- 3-byte counter, starting with a random value.
The binary representation of the id is compatible with Mongo 12 bytes Object IDs.
The string representation is using base32 hex (w/o padding) for better space efficiency
when stored in that form (20 bytes). The hex variant of base32 is used to retain the
sortable property of the id.
Xid doesn't use base64 because case sensitivity and the 2 non alphanum chars may be an
issue when transported as a string between various systems. Base36 wasn't retained either
because 1/ it's not standard 2/ the resulting size is not predictable (not bit aligned)
and 3/ it would not remain sortable. To validate a base32 `xid`, expect a 20 chars long,
all lowercase sequence of `a` to `v` letters and `0` to `9` numbers (`[0-9a-v]{20}`).
UUIDs are 16 bytes (128 bits) and 36 chars as string representation. Twitter Snowflake
ids are 8 bytes (64 bits) but require machine/data-center configuration and/or central
generator servers. xid stands in between with 12 bytes (96 bits) and a more compact
URL-safe string representation (20 chars). No configuration or central generator server
is required so it can be used directly in server's code.
| Name | Binary Size | String Size | Features
|-------------|-------------|----------------|----------------
| [UUID] | 16 bytes | 36 chars | configuration free, not sortable
| [shortuuid] | 16 bytes | 22 chars | configuration free, not sortable
| [Snowflake] | 8 bytes | up to 20 chars | needs machine/DC configuration, needs central server, sortable
| [MongoID] | 12 bytes | 24 chars | configuration free, sortable
| xid | 12 bytes | 20 chars | configuration free, sortable
[UUID]: https://en.wikipedia.org/wiki/Universally_unique_identifier
[shortuuid]: https://github.com/stochastic-technologies/shortuuid
[Snowflake]: https://blog.twitter.com/2010/announcing-snowflake
[MongoID]: https://docs.mongodb.org/manual/reference/object-id/
Features:
- Size: 12 bytes (96 bits), smaller than UUID, larger than snowflake
- Base32 hex encoded by default (20 chars when transported as printable string, still sortable)
- Non configured, you don't need set a unique machine and/or data center id
- K-ordered
- Embedded time with 1 second precision
- Unicity guaranteed for 16,777,216 (24 bits) unique ids per second and per host/process
- Lock-free (i.e.: unlike UUIDv1 and v2)
Best used with [zerolog](https://github.com/rs/zerolog)'s
[RequestIDHandler](https://godoc.org/github.com/rs/zerolog/hlog#RequestIDHandler).
Notes:
- Xid is dependent on the system time, a monotonic counter and so is not cryptographically secure. If unpredictability of IDs is important, you should not use Xids. It is worth noting that most other UUID-like implementations are also not cryptographically secure. You should use libraries that rely on cryptographically secure sources (like /dev/urandom on unix, crypto/rand in golang), if you want a truly random ID generator.
References:
- http://www.slideshare.net/davegardnerisme/unique-id-generation-in-distributed-systems
- https://en.wikipedia.org/wiki/Universally_unique_identifier
- https://blog.twitter.com/2010/announcing-snowflake
- Python port by [Graham Abbott](https://github.com/graham): https://github.com/graham/python_xid
- Scala port by [Egor Kolotaev](https://github.com/kolotaev): https://github.com/kolotaev/ride
- Rust port by [Jérôme Renard](https://github.com/jeromer/): https://github.com/jeromer/libxid
- Ruby port by [Valar](https://github.com/valarpirai/): https://github.com/valarpirai/ruby_xid
- Java port by [0xShamil](https://github.com/0xShamil/): https://github.com/0xShamil/java-xid
## Install
go get github.com/rs/xid
## Usage
```go
guid := xid.New()
println(guid.String())
// Output: 9m4e2mr0ui3e8a215n4g
```
Get `xid` embedded info:
```go
guid.Machine()
guid.Pid()
guid.Time()
guid.Counter()
```
## Benchmark
Benchmark against Go [Maxim Bublis](https://github.com/satori)'s [UUID](https://github.com/satori/go.uuid).
```
BenchmarkXID 20000000 91.1 ns/op 32 B/op 1 allocs/op
BenchmarkXID-2 20000000 55.9 ns/op 32 B/op 1 allocs/op
BenchmarkXID-4 50000000 32.3 ns/op 32 B/op 1 allocs/op
BenchmarkUUIDv1 10000000 204 ns/op 48 B/op 1 allocs/op
BenchmarkUUIDv1-2 10000000 160 ns/op 48 B/op 1 allocs/op
BenchmarkUUIDv1-4 10000000 195 ns/op 48 B/op 1 allocs/op
BenchmarkUUIDv4 1000000 1503 ns/op 64 B/op 2 allocs/op
BenchmarkUUIDv4-2 1000000 1427 ns/op 64 B/op 2 allocs/op
BenchmarkUUIDv4-4 1000000 1452 ns/op 64 B/op 2 allocs/op
```
Note: UUIDv1 requires a global lock, hence the performance degradation as we add more CPUs.
## Licenses
All source code is licensed under the [MIT License](https://raw.github.com/rs/xid/master/LICENSE).

View File

@@ -1,9 +0,0 @@
// +build darwin
package xid
import "syscall"
func readPlatformMachineID() (string, error) {
return syscall.Sysctl("kern.uuid")
}

View File

@@ -1,9 +0,0 @@
// +build !darwin,!linux,!freebsd,!windows
package xid
import "errors"
func readPlatformMachineID() (string, error) {
return "", errors.New("not implemented")
}

View File

@@ -1,9 +0,0 @@
// +build freebsd
package xid
import "syscall"
func readPlatformMachineID() (string, error) {
return syscall.Sysctl("kern.hostuuid")
}

View File

@@ -1,13 +0,0 @@
// +build linux
package xid
import "io/ioutil"
func readPlatformMachineID() (string, error) {
b, err := ioutil.ReadFile("/etc/machine-id")
if err != nil || len(b) == 0 {
b, err = ioutil.ReadFile("/sys/class/dmi/id/product_uuid")
}
return string(b), err
}

View File

@@ -1,38 +0,0 @@
// +build windows
package xid
import (
"fmt"
"syscall"
"unsafe"
)
func readPlatformMachineID() (string, error) {
// source: https://github.com/shirou/gopsutil/blob/master/host/host_syscall.go
var h syscall.Handle
err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE, syscall.StringToUTF16Ptr(`SOFTWARE\Microsoft\Cryptography`), 0, syscall.KEY_READ|syscall.KEY_WOW64_64KEY, &h)
if err != nil {
return "", err
}
defer syscall.RegCloseKey(h)
const syscallRegBufLen = 74 // len(`{`) + len(`abcdefgh-1234-456789012-123345456671` * 2) + len(`}`) // 2 == bytes/UTF16
const uuidLen = 36
var regBuf [syscallRegBufLen]uint16
bufLen := uint32(syscallRegBufLen)
var valType uint32
err = syscall.RegQueryValueEx(h, syscall.StringToUTF16Ptr(`MachineGuid`), nil, &valType, (*byte)(unsafe.Pointer(&regBuf[0])), &bufLen)
if err != nil {
return "", err
}
hostID := syscall.UTF16ToString(regBuf[:])
hostIDLen := len(hostID)
if hostIDLen != uuidLen {
return "", fmt.Errorf("HostID incorrect: %q\n", hostID)
}
return hostID, nil
}

View File

@@ -1,380 +0,0 @@
// Package xid is a globally unique id generator suited for web scale
//
// Xid is using Mongo Object ID algorithm to generate globally unique ids:
// https://docs.mongodb.org/manual/reference/object-id/
//
// - 4-byte value representing the seconds since the Unix epoch,
// - 3-byte machine identifier,
// - 2-byte process id, and
// - 3-byte counter, starting with a random value.
//
// The binary representation of the id is compatible with Mongo 12 bytes Object IDs.
// The string representation is using base32 hex (w/o padding) for better space efficiency
// when stored in that form (20 bytes). The hex variant of base32 is used to retain the
// sortable property of the id.
//
// Xid doesn't use base64 because case sensitivity and the 2 non alphanum chars may be an
// issue when transported as a string between various systems. Base36 wasn't retained either
// because 1/ it's not standard 2/ the resulting size is not predictable (not bit aligned)
// and 3/ it would not remain sortable. To validate a base32 `xid`, expect a 20 chars long,
// all lowercase sequence of `a` to `v` letters and `0` to `9` numbers (`[0-9a-v]{20}`).
//
// UUID is 16 bytes (128 bits), snowflake is 8 bytes (64 bits), xid stands in between
// with 12 bytes with a more compact string representation ready for the web and no
// required configuration or central generation server.
//
// Features:
//
// - Size: 12 bytes (96 bits), smaller than UUID, larger than snowflake
// - Base32 hex encoded by default (16 bytes storage when transported as printable string)
// - Non configured, you don't need set a unique machine and/or data center id
// - K-ordered
// - Embedded time with 1 second precision
// - Unicity guaranteed for 16,777,216 (24 bits) unique ids per second and per host/process
//
// Best used with xlog's RequestIDHandler (https://godoc.org/github.com/rs/xlog#RequestIDHandler).
//
// References:
//
// - http://www.slideshare.net/davegardnerisme/unique-id-generation-in-distributed-systems
// - https://en.wikipedia.org/wiki/Universally_unique_identifier
// - https://blog.twitter.com/2010/announcing-snowflake
package xid
import (
"bytes"
"crypto/md5"
"crypto/rand"
"database/sql/driver"
"encoding/binary"
"errors"
"fmt"
"hash/crc32"
"io/ioutil"
"os"
"sort"
"sync/atomic"
"time"
"unsafe"
)
// Code inspired from mgo/bson ObjectId
// ID represents a unique request id
type ID [rawLen]byte
const (
encodedLen = 20 // string encoded len
rawLen = 12 // binary raw len
// encoding stores a custom version of the base32 encoding with lower case
// letters.
encoding = "0123456789abcdefghijklmnopqrstuv"
)
var (
// ErrInvalidID is returned when trying to unmarshal an invalid ID
ErrInvalidID = errors.New("xid: invalid ID")
// objectIDCounter is atomically incremented when generating a new ObjectId
// using NewObjectId() function. It's used as a counter part of an id.
// This id is initialized with a random value.
objectIDCounter = randInt()
// machineId stores machine id generated once and used in subsequent calls
// to NewObjectId function.
machineID = readMachineID()
// pid stores the current process id
pid = os.Getpid()
nilID ID
// dec is the decoding map for base32 encoding
dec [256]byte
)
func init() {
for i := 0; i < len(dec); i++ {
dec[i] = 0xFF
}
for i := 0; i < len(encoding); i++ {
dec[encoding[i]] = byte(i)
}
// If /proc/self/cpuset exists and is not /, we can assume that we are in a
// form of container and use the content of cpuset xor-ed with the PID in
// order get a reasonable machine global unique PID.
b, err := ioutil.ReadFile("/proc/self/cpuset")
if err == nil && len(b) > 1 {
pid ^= int(crc32.ChecksumIEEE(b))
}
}
// readMachineId generates machine id and puts it into the machineId global
// variable. If this function fails to get the hostname, it will cause
// a runtime error.
func readMachineID() []byte {
id := make([]byte, 3)
hid, err := readPlatformMachineID()
if err != nil || len(hid) == 0 {
hid, err = os.Hostname()
}
if err == nil && len(hid) != 0 {
hw := md5.New()
hw.Write([]byte(hid))
copy(id, hw.Sum(nil))
} else {
// Fallback to rand number if machine id can't be gathered
if _, randErr := rand.Reader.Read(id); randErr != nil {
panic(fmt.Errorf("xid: cannot get hostname nor generate a random number: %v; %v", err, randErr))
}
}
return id
}
// randInt generates a random uint32
func randInt() uint32 {
b := make([]byte, 3)
if _, err := rand.Reader.Read(b); err != nil {
panic(fmt.Errorf("xid: cannot generate random number: %v;", err))
}
return uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2])
}
// New generates a globally unique ID
func New() ID {
return NewWithTime(time.Now())
}
// NewWithTime generates a globally unique ID with the passed in time
func NewWithTime(t time.Time) ID {
var id ID
// Timestamp, 4 bytes, big endian
binary.BigEndian.PutUint32(id[:], uint32(t.Unix()))
// Machine, first 3 bytes of md5(hostname)
id[4] = machineID[0]
id[5] = machineID[1]
id[6] = machineID[2]
// Pid, 2 bytes, specs don't specify endianness, but we use big endian.
id[7] = byte(pid >> 8)
id[8] = byte(pid)
// Increment, 3 bytes, big endian
i := atomic.AddUint32(&objectIDCounter, 1)
id[9] = byte(i >> 16)
id[10] = byte(i >> 8)
id[11] = byte(i)
return id
}
// FromString reads an ID from its string representation
func FromString(id string) (ID, error) {
i := &ID{}
err := i.UnmarshalText([]byte(id))
return *i, err
}
// String returns a base32 hex lowercased with no padding representation of the id (char set is 0-9, a-v).
func (id ID) String() string {
text := make([]byte, encodedLen)
encode(text, id[:])
return *(*string)(unsafe.Pointer(&text))
}
// Encode encodes the id using base32 encoding, writing 20 bytes to dst and return it.
func (id ID) Encode(dst []byte) []byte {
encode(dst, id[:])
return dst
}
// MarshalText implements encoding/text TextMarshaler interface
func (id ID) MarshalText() ([]byte, error) {
text := make([]byte, encodedLen)
encode(text, id[:])
return text, nil
}
// MarshalJSON implements encoding/json Marshaler interface
func (id ID) MarshalJSON() ([]byte, error) {
if id.IsNil() {
return []byte("null"), nil
}
text := make([]byte, encodedLen+2)
encode(text[1:encodedLen+1], id[:])
text[0], text[encodedLen+1] = '"', '"'
return text, nil
}
// encode by unrolling the stdlib base32 algorithm + removing all safe checks
func encode(dst, id []byte) {
_ = dst[19]
_ = id[11]
dst[19] = encoding[(id[11]<<4)&0x1F]
dst[18] = encoding[(id[11]>>1)&0x1F]
dst[17] = encoding[(id[11]>>6)&0x1F|(id[10]<<2)&0x1F]
dst[16] = encoding[id[10]>>3]
dst[15] = encoding[id[9]&0x1F]
dst[14] = encoding[(id[9]>>5)|(id[8]<<3)&0x1F]
dst[13] = encoding[(id[8]>>2)&0x1F]
dst[12] = encoding[id[8]>>7|(id[7]<<1)&0x1F]
dst[11] = encoding[(id[7]>>4)&0x1F|(id[6]<<4)&0x1F]
dst[10] = encoding[(id[6]>>1)&0x1F]
dst[9] = encoding[(id[6]>>6)&0x1F|(id[5]<<2)&0x1F]
dst[8] = encoding[id[5]>>3]
dst[7] = encoding[id[4]&0x1F]
dst[6] = encoding[id[4]>>5|(id[3]<<3)&0x1F]
dst[5] = encoding[(id[3]>>2)&0x1F]
dst[4] = encoding[id[3]>>7|(id[2]<<1)&0x1F]
dst[3] = encoding[(id[2]>>4)&0x1F|(id[1]<<4)&0x1F]
dst[2] = encoding[(id[1]>>1)&0x1F]
dst[1] = encoding[(id[1]>>6)&0x1F|(id[0]<<2)&0x1F]
dst[0] = encoding[id[0]>>3]
}
// UnmarshalText implements encoding/text TextUnmarshaler interface
func (id *ID) UnmarshalText(text []byte) error {
if len(text) != encodedLen {
return ErrInvalidID
}
for _, c := range text {
if dec[c] == 0xFF {
return ErrInvalidID
}
}
decode(id, text)
return nil
}
// UnmarshalJSON implements encoding/json Unmarshaler interface
func (id *ID) UnmarshalJSON(b []byte) error {
s := string(b)
if s == "null" {
*id = nilID
return nil
}
return id.UnmarshalText(b[1 : len(b)-1])
}
// decode by unrolling the stdlib base32 algorithm + removing all safe checks
func decode(id *ID, src []byte) {
_ = src[19]
_ = id[11]
id[11] = dec[src[17]]<<6 | dec[src[18]]<<1 | dec[src[19]]>>4
id[10] = dec[src[16]]<<3 | dec[src[17]]>>2
id[9] = dec[src[14]]<<5 | dec[src[15]]
id[8] = dec[src[12]]<<7 | dec[src[13]]<<2 | dec[src[14]]>>3
id[7] = dec[src[11]]<<4 | dec[src[12]]>>1
id[6] = dec[src[9]]<<6 | dec[src[10]]<<1 | dec[src[11]]>>4
id[5] = dec[src[8]]<<3 | dec[src[9]]>>2
id[4] = dec[src[6]]<<5 | dec[src[7]]
id[3] = dec[src[4]]<<7 | dec[src[5]]<<2 | dec[src[6]]>>3
id[2] = dec[src[3]]<<4 | dec[src[4]]>>1
id[1] = dec[src[1]]<<6 | dec[src[2]]<<1 | dec[src[3]]>>4
id[0] = dec[src[0]]<<3 | dec[src[1]]>>2
}
// Time returns the timestamp part of the id.
// It's a runtime error to call this method with an invalid id.
func (id ID) Time() time.Time {
// First 4 bytes of ObjectId is 32-bit big-endian seconds from epoch.
secs := int64(binary.BigEndian.Uint32(id[0:4]))
return time.Unix(secs, 0)
}
// Machine returns the 3-byte machine id part of the id.
// It's a runtime error to call this method with an invalid id.
func (id ID) Machine() []byte {
return id[4:7]
}
// Pid returns the process id part of the id.
// It's a runtime error to call this method with an invalid id.
func (id ID) Pid() uint16 {
return binary.BigEndian.Uint16(id[7:9])
}
// Counter returns the incrementing value part of the id.
// It's a runtime error to call this method with an invalid id.
func (id ID) Counter() int32 {
b := id[9:12]
// Counter is stored as big-endian 3-byte value
return int32(uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2]))
}
// Value implements the driver.Valuer interface.
func (id ID) Value() (driver.Value, error) {
if id.IsNil() {
return nil, nil
}
b, err := id.MarshalText()
return string(b), err
}
// Scan implements the sql.Scanner interface.
func (id *ID) Scan(value interface{}) (err error) {
switch val := value.(type) {
case string:
return id.UnmarshalText([]byte(val))
case []byte:
return id.UnmarshalText(val)
case nil:
*id = nilID
return nil
default:
return fmt.Errorf("xid: scanning unsupported type: %T", value)
}
}
// IsNil Returns true if this is a "nil" ID
func (id ID) IsNil() bool {
return id == nilID
}
// NilID returns a zero value for `xid.ID`.
func NilID() ID {
return nilID
}
// Bytes returns the byte array representation of `ID`
func (id ID) Bytes() []byte {
return id[:]
}
// FromBytes convert the byte array representation of `ID` back to `ID`
func FromBytes(b []byte) (ID, error) {
var id ID
if len(b) != rawLen {
return id, ErrInvalidID
}
copy(id[:], b)
return id, nil
}
// Compare returns an integer comparing two IDs. It behaves just like `bytes.Compare`.
// The result will be 0 if two IDs are identical, -1 if current id is less than the other one,
// and 1 if current id is greater than the other.
func (id ID) Compare(other ID) int {
return bytes.Compare(id[:], other[:])
}
type sorter []ID
func (s sorter) Len() int {
return len(s)
}
func (s sorter) Less(i, j int) bool {
return s[i].Compare(s[j]) < 0
}
func (s sorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Sort sorts an array of IDs inplace.
// It works by wrapping `[]ID` and use `sort.Sort`.
func Sort(ids []ID) {
sort.Sort(sorter(ids))
}

View File

@@ -1,3 +0,0 @@
# This source code refers to The Go Authors for copyright purposes.
# The master list of authors is in the main Go distribution,
# visible at https://tip.golang.org/AUTHORS.

View File

@@ -1,3 +0,0 @@
# This source code was written by the Go contributors.
# The master list of contributors is in the main Go distribution,
# visible at https://tip.golang.org/CONTRIBUTORS.

View File

@@ -12,7 +12,7 @@ import (
"errors"
"math/bits"
"golang.org/x/crypto/internal/subtle"
"golang.org/x/crypto/internal/alias"
)
const (
@@ -189,7 +189,7 @@ func (s *Cipher) XORKeyStream(dst, src []byte) {
panic("chacha20: output smaller than input")
}
dst = dst[:len(src)]
if subtle.InexactOverlap(dst, src) {
if alias.InexactOverlap(dst, src) {
panic("chacha20: invalid buffer overlap")
}

View File

@@ -9,7 +9,8 @@ package curve25519 // import "golang.org/x/crypto/curve25519"
import (
"crypto/subtle"
"fmt"
"errors"
"strconv"
"golang.org/x/crypto/curve25519/internal/field"
)
@@ -124,10 +125,10 @@ func X25519(scalar, point []byte) ([]byte, error) {
func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
var in [32]byte
if l := len(scalar); l != 32 {
return nil, fmt.Errorf("bad scalar length: %d, expected %d", l, 32)
return nil, errors.New("bad scalar length: " + strconv.Itoa(l) + ", expected 32")
}
if l := len(point); l != 32 {
return nil, fmt.Errorf("bad point length: %d, expected %d", l, 32)
return nil, errors.New("bad point length: " + strconv.Itoa(l) + ", expected 32")
}
copy(in[:], scalar)
if &point[0] == &Basepoint[0] {
@@ -138,7 +139,7 @@ func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
copy(base[:], point)
ScalarMult(dst, &in, &base)
if subtle.ConstantTimeCompare(dst[:], zero[:]) == 1 {
return nil, fmt.Errorf("bad input point: low order point")
return nil, errors.New("bad input point: low order point")
}
}
return dst[:], nil

View File

@@ -5,9 +5,8 @@
//go:build !purego
// +build !purego
// Package subtle implements functions that are often useful in cryptographic
// code but require careful thought to use correctly.
package subtle // import "golang.org/x/crypto/internal/subtle"
// Package alias implements memory aliasing tests.
package alias
import "unsafe"

View File

@@ -5,9 +5,8 @@
//go:build purego
// +build purego
// Package subtle implements functions that are often useful in cryptographic
// code but require careful thought to use correctly.
package subtle // import "golang.org/x/crypto/internal/subtle"
// Package alias implements memory aliasing tests.
package alias
// This is the Google App Engine standard variant based on reflect
// because the unsafe package and cgo are disallowed.

View File

@@ -117,7 +117,7 @@ func pbDecrypt(info decryptable, password []byte) (decrypted []byte, err error)
}
ps := decrypted[len(decrypted)-psLen:]
decrypted = decrypted[:len(decrypted)-psLen]
if bytes.Compare(ps, bytes.Repeat([]byte{byte(psLen)}, psLen)) != 0 {
if !bytes.Equal(ps, bytes.Repeat([]byte{byte(psLen)}, psLen)) {
return nil, ErrDecryption
}

View File

@@ -93,7 +93,7 @@ type ExtendedAgent interface {
type ConstraintExtension struct {
// ExtensionName consist of a UTF-8 string suffixed by the
// implementation domain following the naming scheme defined
// in Section 4.2 of [RFC4251], e.g. "foo@example.com".
// in Section 4.2 of RFC 4251, e.g. "foo@example.com".
ExtensionName string
// ExtensionDetails contains the actual content of the extended
// constraint.
@@ -226,7 +226,9 @@ var ErrExtensionUnsupported = errors.New("agent: extension unsupported")
type extensionAgentMsg struct {
ExtensionType string `sshtype:"27"`
Contents []byte
// NOTE: this matches OpenSSH's PROTOCOL.agent, not the IETF draft [PROTOCOL.agent],
// so that it matches what OpenSSH actually implements in the wild.
Contents []byte `ssh:"rest"`
}
// Key represents a protocol 2 public key as defined in
@@ -729,7 +731,7 @@ func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string
if err != nil {
return err
}
if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 {
if !bytes.Equal(cert.Key.Marshal(), signer.PublicKey().Marshal()) {
return errors.New("agent: signer and cert have different public key")
}

View File

@@ -251,7 +251,7 @@ type algorithmOpenSSHCertSigner struct {
// private key is held by signer. It returns an error if the public key in cert
// doesn't match the key used by signer.
func NewCertSigner(cert *Certificate, signer Signer) (Signer, error) {
if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 {
if !bytes.Equal(cert.Key.Marshal(), signer.PublicKey().Marshal()) {
return nil, errors.New("ssh: signer and cert have different public key")
}

View File

@@ -15,7 +15,6 @@ import (
"fmt"
"hash"
"io"
"io/ioutil"
"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/internal/poly1305"
@@ -97,13 +96,13 @@ func streamCipherMode(skip int, createFunc func(key, iv []byte) (cipher.Stream,
// are not supported and will not be negotiated, even if explicitly requested in
// ClientConfig.Crypto.Ciphers.
var cipherModes = map[string]*cipherMode{
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
// Ciphers from RFC 4344, which introduced many CTR-based ciphers. Algorithms
// are defined in the order specified in the RFC.
"aes128-ctr": {16, aes.BlockSize, streamCipherMode(0, newAESCTR)},
"aes192-ctr": {24, aes.BlockSize, streamCipherMode(0, newAESCTR)},
"aes256-ctr": {32, aes.BlockSize, streamCipherMode(0, newAESCTR)},
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
// Ciphers from RFC 4345, which introduces security-improved arcfour ciphers.
// They are defined in the order specified in the RFC.
"arcfour128": {16, 0, streamCipherMode(1536, newRC4)},
"arcfour256": {32, 0, streamCipherMode(1536, newRC4)},
@@ -111,7 +110,7 @@ var cipherModes = map[string]*cipherMode{
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
// RC4) has problems with weak keys, and should be used with caution."
// RFC4345 introduces improved versions of Arcfour.
// RFC 4345 introduces improved versions of Arcfour.
"arcfour": {16, 0, streamCipherMode(0, newRC4)},
// AEAD ciphers
@@ -497,7 +496,7 @@ func (c *cbcCipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error)
// data, to make distinguishing between
// failing MAC and failing length check more
// difficult.
io.CopyN(ioutil.Discard, r, int64(c.oracleCamouflage))
io.CopyN(io.Discard, r, int64(c.oracleCamouflage))
}
}
return p, err
@@ -642,7 +641,7 @@ const chacha20Poly1305ID = "chacha20-poly1305@openssh.com"
//
// https://tools.ietf.org/html/draft-josefsson-ssh-chacha20-poly1305-openssh-00
//
// the methods here also implement padding, which RFC4253 Section 6
// the methods here also implement padding, which RFC 4253 Section 6
// also requires of stream ciphers.
type chacha20Poly1305Cipher struct {
lengthKey [32]byte

View File

@@ -149,7 +149,7 @@ type directionAlgorithms struct {
// rekeyBytes returns a rekeying intervals in bytes.
func (a *directionAlgorithms) rekeyBytes() int64 {
// According to RFC4344 block ciphers should rekey after
// According to RFC 4344 block ciphers should rekey after
// 2^(BLOCKSIZE/4) blocks. For all AES flavors BLOCKSIZE is
// 128.
switch a.Cipher {
@@ -158,7 +158,7 @@ func (a *directionAlgorithms) rekeyBytes() int64 {
}
// For others, stick with RFC4253 recommendation to rekey after 1 Gb of data.
// For others, stick with RFC 4253 recommendation to rekey after 1 Gb of data.
return 1 << 30
}

View File

@@ -52,7 +52,7 @@ type Conn interface {
// SendRequest sends a global request, and returns the
// reply. If wantReply is true, it returns the response status
// and payload. See also RFC4254, section 4.
// and payload. See also RFC 4254, section 4.
SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error)
// OpenChannel tries to open an channel. If the request is

View File

@@ -184,7 +184,7 @@ func ParseKnownHosts(in []byte) (marker string, hosts []string, pubKey PublicKey
return "", nil, nil, "", nil, io.EOF
}
// ParseAuthorizedKeys parses a public key from an authorized_keys
// ParseAuthorizedKey parses a public key from an authorized_keys
// file used in OpenSSH according to the sshd(8) manual page.
func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) {
for len(in) > 0 {

View File

@@ -68,8 +68,16 @@ type ServerConfig struct {
// NoClientAuth is true if clients are allowed to connect without
// authenticating.
// To determine NoClientAuth at runtime, set NoClientAuth to true
// and the optional NoClientAuthCallback to a non-nil value.
NoClientAuth bool
// NoClientAuthCallback, if non-nil, is called when a user
// attempts to authenticate with auth method "none".
// NoClientAuth must also be set to true for this be used, or
// this func is unused.
NoClientAuthCallback func(ConnMetadata) (*Permissions, error)
// MaxAuthTries specifies the maximum number of authentication attempts
// permitted per connection. If set to a negative number, the number of
// attempts are unlimited. If set to zero, the number of attempts are limited
@@ -455,7 +463,11 @@ userAuthLoop:
switch userAuthReq.Method {
case "none":
if config.NoClientAuth {
authErr = nil
if config.NoClientAuthCallback != nil {
perms, authErr = config.NoClientAuthCallback(s)
} else {
authErr = nil
}
}
// allow initial attempt of 'none' without penalty

View File

@@ -13,7 +13,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"sync"
)
@@ -124,7 +123,7 @@ type Session struct {
// output and error.
//
// If either is nil, Run connects the corresponding file
// descriptor to an instance of ioutil.Discard. There is a
// descriptor to an instance of io.Discard. There is a
// fixed amount of buffering that is shared for the two streams.
// If either blocks it may eventually cause the remote
// command to block.
@@ -506,7 +505,7 @@ func (s *Session) stdout() {
return
}
if s.Stdout == nil {
s.Stdout = ioutil.Discard
s.Stdout = io.Discard
}
s.copyFuncs = append(s.copyFuncs, func() error {
_, err := io.Copy(s.Stdout, s.ch)
@@ -519,7 +518,7 @@ func (s *Session) stderr() {
return
}
if s.Stderr == nil {
s.Stderr = ioutil.Discard
s.Stderr = io.Discard
}
s.copyFuncs = append(s.copyFuncs, func() error {
_, err := io.Copy(s.Stderr, s.ch.Stderr())

27
src/cmd/linuxkit/vendor/golang.org/x/mod/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

22
src/cmd/linuxkit/vendor/golang.org/x/mod/PATENTS generated vendored Normal file
View File

@@ -0,0 +1,22 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.

View File

@@ -0,0 +1,401 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package semver implements comparison of semantic version strings.
// In this package, semantic version strings must begin with a leading "v",
// as in "v1.0.0".
//
// The general form of a semantic version string accepted by this package is
//
// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]]
//
// where square brackets indicate optional parts of the syntax;
// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros;
// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers
// using only alphanumeric characters and hyphens; and
// all-numeric PRERELEASE identifiers must not have leading zeros.
//
// This package follows Semantic Versioning 2.0.0 (see semver.org)
// with two exceptions. First, it requires the "v" prefix. Second, it recognizes
// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes)
// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0.
package semver
import "sort"
// parsed returns the parsed form of a semantic version string.
type parsed struct {
major string
minor string
patch string
short string
prerelease string
build string
}
// IsValid reports whether v is a valid semantic version string.
func IsValid(v string) bool {
_, ok := parse(v)
return ok
}
// Canonical returns the canonical formatting of the semantic version v.
// It fills in any missing .MINOR or .PATCH and discards build metadata.
// Two semantic versions compare equal only if their canonical formattings
// are identical strings.
// The canonical invalid semantic version is the empty string.
func Canonical(v string) string {
p, ok := parse(v)
if !ok {
return ""
}
if p.build != "" {
return v[:len(v)-len(p.build)]
}
if p.short != "" {
return v + p.short
}
return v
}
// Major returns the major version prefix of the semantic version v.
// For example, Major("v2.1.0") == "v2".
// If v is an invalid semantic version string, Major returns the empty string.
func Major(v string) string {
pv, ok := parse(v)
if !ok {
return ""
}
return v[:1+len(pv.major)]
}
// MajorMinor returns the major.minor version prefix of the semantic version v.
// For example, MajorMinor("v2.1.0") == "v2.1".
// If v is an invalid semantic version string, MajorMinor returns the empty string.
func MajorMinor(v string) string {
pv, ok := parse(v)
if !ok {
return ""
}
i := 1 + len(pv.major)
if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor {
return v[:j]
}
return v[:i] + "." + pv.minor
}
// Prerelease returns the prerelease suffix of the semantic version v.
// For example, Prerelease("v2.1.0-pre+meta") == "-pre".
// If v is an invalid semantic version string, Prerelease returns the empty string.
func Prerelease(v string) string {
pv, ok := parse(v)
if !ok {
return ""
}
return pv.prerelease
}
// Build returns the build suffix of the semantic version v.
// For example, Build("v2.1.0+meta") == "+meta".
// If v is an invalid semantic version string, Build returns the empty string.
func Build(v string) string {
pv, ok := parse(v)
if !ok {
return ""
}
return pv.build
}
// Compare returns an integer comparing two versions according to
// semantic version precedence.
// The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
//
// An invalid semantic version string is considered less than a valid one.
// All invalid semantic version strings compare equal to each other.
func Compare(v, w string) int {
pv, ok1 := parse(v)
pw, ok2 := parse(w)
if !ok1 && !ok2 {
return 0
}
if !ok1 {
return -1
}
if !ok2 {
return +1
}
if c := compareInt(pv.major, pw.major); c != 0 {
return c
}
if c := compareInt(pv.minor, pw.minor); c != 0 {
return c
}
if c := compareInt(pv.patch, pw.patch); c != 0 {
return c
}
return comparePrerelease(pv.prerelease, pw.prerelease)
}
// Max canonicalizes its arguments and then returns the version string
// that compares greater.
//
// Deprecated: use Compare instead. In most cases, returning a canonicalized
// version is not expected or desired.
func Max(v, w string) string {
v = Canonical(v)
w = Canonical(w)
if Compare(v, w) > 0 {
return v
}
return w
}
// ByVersion implements sort.Interface for sorting semantic version strings.
type ByVersion []string
func (vs ByVersion) Len() int { return len(vs) }
func (vs ByVersion) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
func (vs ByVersion) Less(i, j int) bool {
cmp := Compare(vs[i], vs[j])
if cmp != 0 {
return cmp < 0
}
return vs[i] < vs[j]
}
// Sort sorts a list of semantic version strings using ByVersion.
func Sort(list []string) {
sort.Sort(ByVersion(list))
}
func parse(v string) (p parsed, ok bool) {
if v == "" || v[0] != 'v' {
return
}
p.major, v, ok = parseInt(v[1:])
if !ok {
return
}
if v == "" {
p.minor = "0"
p.patch = "0"
p.short = ".0.0"
return
}
if v[0] != '.' {
ok = false
return
}
p.minor, v, ok = parseInt(v[1:])
if !ok {
return
}
if v == "" {
p.patch = "0"
p.short = ".0"
return
}
if v[0] != '.' {
ok = false
return
}
p.patch, v, ok = parseInt(v[1:])
if !ok {
return
}
if len(v) > 0 && v[0] == '-' {
p.prerelease, v, ok = parsePrerelease(v)
if !ok {
return
}
}
if len(v) > 0 && v[0] == '+' {
p.build, v, ok = parseBuild(v)
if !ok {
return
}
}
if v != "" {
ok = false
return
}
ok = true
return
}
func parseInt(v string) (t, rest string, ok bool) {
if v == "" {
return
}
if v[0] < '0' || '9' < v[0] {
return
}
i := 1
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
i++
}
if v[0] == '0' && i != 1 {
return
}
return v[:i], v[i:], true
}
func parsePrerelease(v string) (t, rest string, ok bool) {
// "A pre-release version MAY be denoted by appending a hyphen and
// a series of dot separated identifiers immediately following the patch version.
// Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes."
if v == "" || v[0] != '-' {
return
}
i := 1
start := 1
for i < len(v) && v[i] != '+' {
if !isIdentChar(v[i]) && v[i] != '.' {
return
}
if v[i] == '.' {
if start == i || isBadNum(v[start:i]) {
return
}
start = i + 1
}
i++
}
if start == i || isBadNum(v[start:i]) {
return
}
return v[:i], v[i:], true
}
func parseBuild(v string) (t, rest string, ok bool) {
if v == "" || v[0] != '+' {
return
}
i := 1
start := 1
for i < len(v) {
if !isIdentChar(v[i]) && v[i] != '.' {
return
}
if v[i] == '.' {
if start == i {
return
}
start = i + 1
}
i++
}
if start == i {
return
}
return v[:i], v[i:], true
}
func isIdentChar(c byte) bool {
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
}
func isBadNum(v string) bool {
i := 0
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
i++
}
return i == len(v) && i > 1 && v[0] == '0'
}
func isNum(v string) bool {
i := 0
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
i++
}
return i == len(v)
}
func compareInt(x, y string) int {
if x == y {
return 0
}
if len(x) < len(y) {
return -1
}
if len(x) > len(y) {
return +1
}
if x < y {
return -1
} else {
return +1
}
}
func comparePrerelease(x, y string) int {
// "When major, minor, and patch are equal, a pre-release version has
// lower precedence than a normal version.
// Example: 1.0.0-alpha < 1.0.0.
// Precedence for two pre-release versions with the same major, minor,
// and patch version MUST be determined by comparing each dot separated
// identifier from left to right until a difference is found as follows:
// identifiers consisting of only digits are compared numerically and
// identifiers with letters or hyphens are compared lexically in ASCII
// sort order. Numeric identifiers always have lower precedence than
// non-numeric identifiers. A larger set of pre-release fields has a
// higher precedence than a smaller set, if all of the preceding
// identifiers are equal.
// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta <
// 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0."
if x == y {
return 0
}
if x == "" {
return +1
}
if y == "" {
return -1
}
for x != "" && y != "" {
x = x[1:] // skip - or .
y = y[1:] // skip - or .
var dx, dy string
dx, x = nextIdent(x)
dy, y = nextIdent(y)
if dx != dy {
ix := isNum(dx)
iy := isNum(dy)
if ix != iy {
if ix {
return -1
} else {
return +1
}
}
if ix {
if len(dx) < len(dy) {
return -1
}
if len(dx) > len(dy) {
return +1
}
}
if dx < dy {
return -1
} else {
return +1
}
}
}
if x == "" {
return -1
} else {
return +1
}
}
func nextIdent(x string) (dx, rest string) {
i := 0
for i < len(x) && x[i] != '.' {
i++
}
return x[:i], x[i:]
}

View File

@@ -41,9 +41,11 @@ github.com/Azure/go-autorest/logger
# github.com/Azure/go-autorest/tracing v0.6.0
## explicit; go 1.12
github.com/Azure/go-autorest/tracing
# github.com/Code-Hex/vz v0.0.4
## explicit; go 1.16
github.com/Code-Hex/vz
# github.com/Code-Hex/vz/v3 v3.0.0
## explicit; go 1.19
github.com/Code-Hex/vz/v3
github.com/Code-Hex/vz/v3/internal/objc
github.com/Code-Hex/vz/v3/internal/progress
# github.com/Microsoft/go-winio v0.5.2
## explicit; go 1.13
github.com/Microsoft/go-winio
@@ -559,7 +561,6 @@ github.com/radu-matei/azure-vhd-utils/vhdcore/writer
github.com/rn/iso9660wrap
# github.com/rs/xid v1.3.0
## explicit; go 1.12
github.com/rs/xid
# github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6
## explicit; go 1.12
github.com/scaleway/scaleway-sdk-go/api/instance/v1
@@ -698,20 +699,23 @@ go.opentelemetry.io/proto/otlp/collector/trace/v1
go.opentelemetry.io/proto/otlp/common/v1
go.opentelemetry.io/proto/otlp/resource/v1
go.opentelemetry.io/proto/otlp/trace/v1
# golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
# golang.org/x/crypto v0.1.0
## explicit; go 1.17
golang.org/x/crypto/blowfish
golang.org/x/crypto/chacha20
golang.org/x/crypto/curve25519
golang.org/x/crypto/curve25519/internal/field
golang.org/x/crypto/ed25519
golang.org/x/crypto/internal/alias
golang.org/x/crypto/internal/poly1305
golang.org/x/crypto/internal/subtle
golang.org/x/crypto/pkcs12
golang.org/x/crypto/pkcs12/internal/rc2
golang.org/x/crypto/ssh
golang.org/x/crypto/ssh/agent
golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
# golang.org/x/mod v0.6.0
## explicit; go 1.17
golang.org/x/mod/semver
# golang.org/x/net v0.1.0
## explicit; go 1.17
golang.org/x/net/context