add support for virtualization framework

Signed-off-by: Avi Deitcher <avi@deitcher.net>
This commit is contained in:
Avi Deitcher
2022-02-02 10:35:42 +02:00
parent ea61ff95ce
commit de1d8cdeda
64 changed files with 3937 additions and 10 deletions

View File

@@ -10,20 +10,25 @@ jobs:
- os: linux
arch: amd64
suffix: amd64-linux
runner: ubuntu-latest
- os: linux
arch: arm64
suffix: arm64-linux
runner: ubuntu-latest
- os: linux
arch: s390x
suffix: s390x-linux
runner: ubuntu-latest
- os: darwin
arch: amd64
suffix: amd64-darwin
runner: macos-latest
- os: windows
arch: amd64
suffix: amd64-windows.exe
runner: ubuntu-latest
runs-on: ubuntu-latest
runs-on: ${{ matrix.target.runner }}
steps:
- name: Set up Go 1.16
@@ -64,7 +69,10 @@ jobs:
- name: Checksum
run: |
cd bin && sha256sum linuxkit-${{matrix.target.suffix}} > linuxkit-${{matrix.target.suffix}}.SHA256SUM
cd bin
if command -v sha256sum > /dev/null; then sha256sum linuxkit-${{matrix.target.suffix}} > linuxkit-${{matrix.target.suffix}}.SHA256SUM
else openssl sha256 -r linuxkit-${{matrix.target.suffix}} | tr -d '*' > linuxkit-${{matrix.target.suffix}}.SHA256SUM
fi
cat linuxkit-${{matrix.target.suffix}}.SHA256SUM
- name: Test

View File

@@ -5,17 +5,14 @@ GO_COMPILE=linuxkit/go-compile:7b1f5a37d2a93cd4a9aa2a87db264d8145944006
ifeq ($(OS),Windows_NT)
LINUXKIT?=$(CURDIR)/bin/linuxkit.exe
RTF?=bin/rtf.exe
GOOS?=windows
else
LINUXKIT?=$(CURDIR)/bin/linuxkit
RTF?=bin/rtf
GOOS?=$(shell uname -s | tr '[:upper:]' '[:lower:]')
endif
GOARCH?=amd64
ifneq ($(GOOS),linux)
ifneq ($(GOOS),)
CROSS+=-e GOOS=$(GOOS)
endif
ifneq ($(GOARCH),amd64)
ifneq ($(GOARCH),)
CROSS+=-e GOARCH=$(GOARCH)
endif
@@ -74,6 +71,9 @@ bin:
install:
cp -R bin/* $(PREFIX)/bin
sign:
codesign --entitlements linuxkit.entitlements --force -s - $(PREFIX)/bin/linuxkit
.PHONY: test
test:
$(MAKE) -C test

View File

@@ -75,6 +75,7 @@ for example VMWare. See `linuxkit run --help`.
Currently supported platforms are:
- Local hypervisors
- [Virtualization.Framework (macOS)](docs/platform-virtualization-framework.md) `[x86_64, arm64]`
- [HyperKit (macOS)](docs/platform-hyperkit.md) `[x86_64]`
- [Hyper-V (Windows)](docs/platform-hyperv.md) `[x86_64]`
- [qemu (macOS, Linux, Windows)](docs/platform-qemu.md) `[x86_64, arm64, s390x]`

View File

@@ -7,7 +7,8 @@
## Make Disk Available
In order to make the disk available, you need to tell `linuxkit` where the disk file or block device is.
All local `linuxkit run` methods (currently `hyperkit`, `qemu`, and `vmware`) take a `-disk` argument:
All local `linuxkit run` methods (currently `hyperkit`, `qemu`, `virtualization.framework` and `vmware`)
take a `-disk` argument:
* `-disk path,size=100M,format=qcow2`. For size the default is in GB but an `M` can be appended to specify sizes in MB. The format can be omitted for the platform default, and is only useful on `qemu` at present.

View File

@@ -115,3 +115,9 @@ and made available in `/run/config/userdata`.
HyperKit does not distinguish metadata and userdata, it's simply
refered to as data, which is passed to the VM as a disk image
in ISO9660 format.
## Virtualization.Framework
Virtualization.Framework does not distinguish metadata and userdata, it's simply
refered to as data, which is passed to the VM as a disk image
in ISO9660 format.

View File

@@ -0,0 +1,204 @@
# LinuxKit with Virtualization.Framework (macOS)
We recommend using LinuxKit in conjunction with
[Docker for Mac](https://docs.docker.com/docker-for-mac/install/). For
the time being it's best to be on the latest edge release. `linuxkit
run` uses [Virtualization.Framework](https://developer.apple.com/documentation/virtualization) and
[VPNKit](https://github.com/moby/vpnkit) and the edge release ships
with updated versions of both.
Alternatively, you can install Virtualization.Framework and VPNKit standalone and use it without Docker for Mac.
## Boot
The Virtualization.Framework backend currently supports booting:
- `kernel+initrd` output from `linuxkit build`.
- `kernel+squashfs` output from `linuxkit build`.
- EFI ISOs using the EFI firmware.
You need to select the boot method manually using the command line
options. The default is `kernel+initrd`. `kernel+squashfs` can be
selected using `-squashfs` and to boot a ISO with EFI you have to
specify `-iso -uefi`.
The `kernel+initrd` uses a RAM disk for the root filesystem. If you
have RAM constraints or large images we recommend using either the
`kernel+squashfs` or the EFI ISO boot.
## Console
With `linuxkit run` on Virtualization.Framework the serial console is redirected to
stdio, providing interactive access to the VM. The output of the VM
can be re-directed to a file or pipe, but then stdin is not available.
Virtualization.Framework does not provide a console device.
## Disks
The Virtualization.Framework backend support configuring a persistent disk using the
standard `linuxkit` `-disk` syntax. Multiple disks are
supported and the disks are in raw format.
## Power management
Virtualization.Framework sends an ACPI power event when it receives SIGTERM to allow the VM to
shut down properly. The VM has to be able to receive ACPI events to initiate the
shutdown. This is provided by the [`acpid` package](../pkg/acpid). An example
is available in the [Docker for Mac example](../examples/docker-for-mac.yml).
## Networking
By default, `linuxkit run` creates a VM with a single network
interface which, logically, is attached to a L2 bridge. The bridge
also has the VM used by Docker for Mac attached to it. This means that
the LinuxKit VMs, created with `linuxkit run`, can be accessed from
containers running on Docker for Mac.
The LinuxKit VMs have IP addresses on the `192.168.65.0/24` subnet
assigned by a DHCP server part of VPNKit. `192.168.65.1` is reserved
for VPNKit as the default gateway and `192.168.65.2` is used by the
Docker for Mac VM.
By default, LinuxKit VMs get incrementally increasing IP addresses,
but you can assign a fixed IP address with `linuxkit run -ip`. It's
best to choose an IP address from the DHCP address range above, but
care must be taken to avoid clashes of IP address.
*NOTE:* The LinuxKit VMs can *not* be directly accessed by IP address
from the host. Enabling this would require use of the macOS `vmnet`
framework, which requires the VMs to run as `root`. We don't consider
this option palatable, and provide alternative options to access the
VMs over the network below.
### Accessing network services
Virtualization.Framework offers a number of ways for accessing network services
running inside the LinuxKit VM from the host. These depend on the
networking mode selected via `-networking`. The default mode is
`vmnet`, where it sets up a network bridge. We intend to add support for
`docker-for-mac`, where the same VPNkit instance is shared between
LinuxKit VMs and the VM running as part of Docker for Mac, in the future.
#### Access from the Docker for Mac VM (`-networking docker-for-mac`)
The simplest way to access networking services exposed by a LinuxKit
VM is to use a Docker for Mac container. For example, to access an ssh
server in a LinuxKit VM, create a ssh client container from:
```
FROM alpine:edge
RUN apk add --no-cache openssh-client
```
and then run
```
docker build -t ssh .
docker run --rm -ti -v ~/.ssh:/root/.ssh ssh ssh <IP address of VM>
```
#### Forwarding ports with `socat` (`-networking docker-for-mac`)
A `socat` container on Docker for Mac can be used to proxy between the
LinuxKit VM's ports and localhost. For example, to expose the redis
port from the [RedisOS example](../examples/redis-os.yml), use this
Dockerfile:
```
FROM alpine:edge
RUN apk add --no-cache socat
ENTRYPOINT [ "/usr/bin/socat" ]
```
and then:
```
docker build -t socat .
docker run --rm -t -d -p 6379:6379 socat tcp-listen:6379,reuseaddr,fork tcp:<IP address of VM>:6379
```
#### Port forwarding with VPNKit (`-networking docker-for-mac`)
There is **experimental** support for exposing selected ports of the
guest on `localhost` using the `-publish` command line option. For
example, using `-publish 2222:22/tcp` exposes the guest TCP port 22 on
localhost on port 2222. Multiple `-publish` options can be
specified. For example, the image build from the [`sshd
example`](../examples/sshd.yml) can be started with:
```
linuxkit run -publish 2222:22/tcp sshd
```
and then you can log into the LinuxKit VM with `ssh -p 2222
root@localhost`.
Note, this mode is **experimental** and may cause the VPNKit instance
shared with Docker for Mac being confused about which ports are
currently in use, in particular if the LinuxKit VM does not exit
gracefully. This can typically be fixed by restarting Docker for Mac.
#### Port forwarding with VPNKit (`-networking vpnkit`)
An alternative to the previous method is to start your own copy of
`vpnkit` (or connect to an already running instance). This can be done
using the `-networking vpnkit` command line option.
VPNKit uses a 9P mount in `/port` for coordination between
components. The first VM on a VPNKit instance currently needs mount
the 9P filesystem and also needs to run the `vpnkit-forwarder` service
to enable port forwarding to localhost. A full example with `vpnkit`
forwarding of `sshd` is available in
[examples/vpnkit-forwarder.yml](/examples/vpnkit-forwarder.yml).
To run this example with its own instance of VPNKit, use:
```
linuxkit run -networking vpnkit -publish 2222:22/tcp vpnkit-forwarder
```
You can then access it via:
```
ssh -p 2222 root@localhost
```
More details about the VPNKit forwarding mechanism is available in the
[VPNKit
documentation](https://github.com/moby/vpnkit/blob/master/docs/ports.md#signalling-from-the-vm-to-the-host).
## Integration services and Metadata
There are no special integration services available for Virtualization.Framework, but
there are a number of packages, such as `vsudd`, which enable
tighter integration of the VM with the host (see below).
The Virtualization.Framework backend also allows passing custom userdata into the
[metadata package](./metadata.md) using either the `-data` or `-data-file` command-line
option. This attaches a CD device with the data on.
### `vsudd` unix domain socket forwarding
The [`vsudd` package](/pkg/vsudd) provides a daemon that exposes unix
domain socket inside the VM to the host via virtio or Hyper-V sockets.
With Virtualization.Framework, the virtio sockets can be exposed as unix domain
sockets on the host, enabling access to other daemons, like
`containerd` and `dockerd`, from the host. An example configuration
file is available in [examples/vsudd-containerd.yml](/examples/vsudd-containerd.yml).
After building the example, run it with `linuxkit run virtualization.framework
-vsock-ports 2374 vsudd`. This will create a unix domain socket in the state directory that maps to the `containerd` control socket. The socket is called `guest.00000946`.
If you install the `ctr` tool on the host you should be able to access the
`containerd` running in the VM:
```
$ go get -u -ldflags -s github.com/containerd/containerd/cmd/ctr
...
$ ctr -a vsudd-state/guest.00000946 list
ID IMAGE PID STATUS
vsudd 466 RUNNING
```

View File

@@ -25,10 +25,16 @@ default: $(LINUXKIT)
all: default
LINUXKIT_DEPS=$(wildcard *.go) $(wildcard */*.go) Makefile
ifeq ($(GOOS),darwin)
$(LINUXKIT): local-build sign | bin
else
$(LINUXKIT): tmp_linuxkit_bin.tar | bin
tar -C $(dir $(LINUXKIT)) -xf $<
rm $<
touch $@
endif
tmp_linuxkit_bin.tar: $(LINUXKIT_DEPS)
tar cf - -C . . | docker run --rm --net=none --log-driver=none -i $(CROSS) $(GO_COMPILE) --package github.com/linuxkit/linuxkit/src/cmd/linuxkit --ldflags "-X github.com/linuxkit/linuxkit/src/cmd/linuxkit/version.GitCommit=$(GIT_COMMIT) -X github.com/linuxkit/linuxkit/src/cmd/linuxkit/version.Version=$(VERSION)" -o $(notdir $(LINUXKIT)) > $@
@@ -54,6 +60,11 @@ ifeq ($(STATIC), 1)
CGO_ENABLED=0
endif
# darwin needs CGO to build for virtualization framework
ifeq ($(GOOS), darwin)
CGO_ENABLED=1
endif
ifeq ($(PIE), 1)
CGO_ENABLED=0
BUILD_FLAGS+= --buildmode pie
@@ -81,6 +92,9 @@ local: local-check local-build local-test
bin:
mkdir -p $@
sign:
codesign --entitlements linuxkit.entitlements --force -s - $(LOCAL_TARGET)
install:
cp -R ./bin/* $(PREFIX)/bin

View File

@@ -10,6 +10,7 @@ require (
github.com/Azure/go-autorest/autorest/adal v0.9.13
github.com/Azure/go-autorest/autorest/to v0.4.0
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
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.34.9
@@ -34,6 +35,7 @@ require (
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
github.com/packethost/packngo v0.1.1-0.20171201154433-f1be085ecd6f
github.com/pkg/term v1.1.0
github.com/radu-matei/azure-sdk-for-go v5.0.0-beta.0.20161118192335-3b1282355199+incompatible
github.com/radu-matei/azure-vhd-utils v0.0.0-20170531165126-e52754d5569d
github.com/rn/iso9660wrap v0.0.0-20171120145750-baf8d62ad315
@@ -47,6 +49,7 @@ require (
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
google.golang.org/api v0.57.0
gopkg.in/yaml.v2 v2.4.0
)

View File

@@ -134,6 +134,8 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
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/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=
@@ -1215,6 +1217,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk=
github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -1285,6 +1289,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=

View File

@@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
package main

View File

@@ -0,0 +1,8 @@
<?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>

View File

@@ -20,7 +20,8 @@ func runUsage() {
fmt.Printf(" aws\n")
fmt.Printf(" azure\n")
fmt.Printf(" gcp\n")
fmt.Printf(" hyperkit [macOS]\n")
fmt.Printf(" virtualization [macOS]\n")
fmt.Printf(" hyperkit\n")
fmt.Printf(" hyperv [Windows]\n")
fmt.Printf(" openstack\n")
fmt.Printf(" packet\n")
@@ -55,6 +56,8 @@ func run(args []string) {
os.Exit(0)
case "hyperkit":
runHyperKit(args[1:])
case "virtualization":
runVirtualizationFramework(args[1:])
case "hyperv":
runHyperV(args[1:])
case "openstack":
@@ -74,7 +77,7 @@ func run(args []string) {
default:
switch runtime.GOOS {
case "darwin":
runHyperKit(args)
runVirtualizationFramework(args)
case "linux":
runQemu(args)
case "windows":

View File

@@ -0,0 +1,13 @@
//go:build !darwin
// +build !darwin
package main
import (
log "github.com/sirupsen/logrus"
)
// Process the run arguments and execute run
func runVirtualizationFramework(args []string) {
log.Fatal("virtualization framework is available only on macOS")
}

View File

@@ -0,0 +1,324 @@
//go:build darwin
// +build darwin
package main
import (
"compress/gzip"
"flag"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/Code-Hex/vz"
"github.com/pkg/term/termios"
log "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
const (
virtualizationNetworkingNone string = "none"
virtualizationNetworkingDockerForMac = "docker-for-mac"
virtualizationNetworkingVPNKit = "vpnkit"
virtualizationNetworkingVMNet = "vmnet"
virtualizationNetworkingDefault = virtualizationNetworkingVMNet
virtualizationFrameworkConsole = "console=hvc0"
)
// Process the run arguments and execute run
func runVirtualizationFramework(args []string) {
flags := flag.NewFlagSet("virtualization", flag.ExitOnError)
invoked := filepath.Base(os.Args[0])
flags.Usage = func() {
fmt.Printf("USAGE: %s run virtualization [options] prefix\n\n", invoked)
fmt.Printf("'prefix' specifies the path to the VM image.\n")
fmt.Printf("\n")
fmt.Printf("Options:\n")
flags.PrintDefaults()
}
cpus := flags.Uint("cpus", 1, "Number of CPUs")
mem := flags.Uint64("mem", 1024, "Amount of memory in MB")
memBytes := *mem * 1024 * 1024
var disks Disks
flags.Var(&disks, "disk", "Disk config. [file=]path[,size=1G]")
data := flags.String("data", "", "String of metadata to pass to VM; error to specify both -data and -data-file")
dataPath := flags.String("data-file", "", "Path to file containing metadata to pass to VM; error to specify both -data and -data-file")
if *data != "" && *dataPath != "" {
log.Fatal("Cannot specify both -data and -data-file")
}
state := flags.String("state", "", "Path to directory to keep VM state in")
networking := flags.String("networking", virtualizationNetworkingDefault, "Networking mode. Valid options are 'default', 'vmnet' and 'none'. 'vmnet' uses the Apple vmnet framework. 'none' disables networking.`")
kernelBoot := flags.Bool("kernel", false, "Boot image is kernel+initrd+cmdline 'path'-kernel/-initrd/-cmdline")
if err := flags.Parse(args); err != nil {
log.Fatal("Unable to parse args")
}
remArgs := flags.Args()
if len(remArgs) == 0 {
fmt.Println("Please specify the prefix to the image to boot")
flags.Usage()
os.Exit(1)
}
path := remArgs[0]
prefix := path
_, err := os.Stat(path + "-kernel")
statKernel := err == nil
var isoPaths []string
// Default to kernel+initrd
if !statKernel {
log.Fatalf("Cannot find kernel file: %s", path+"-kernel")
}
_, err = os.Stat(path + "-initrd.img")
statInitrd := err == nil
if !statInitrd {
log.Fatalf("Cannot find initrd file (%s): %v", path+"-initrd.img", err)
}
*kernelBoot = true
metadataPaths, err := CreateMetadataISO(*state, *data, *dataPath)
if err != nil {
log.Fatalf("%v", err)
}
isoPaths = append(isoPaths, metadataPaths...)
// TODO: We should generate new UUID, otherwise /sys/class/dmi/id/product_uuid is identical on all VMs.
// but it is not clear if support is there in VF, or if it is built-in
// Run
cmdlineBytes, err := ioutil.ReadFile(prefix + "-cmdline")
if err != nil {
log.Fatalf("Cannot open cmdline file: %v", err)
}
// must have hvc0 as console for vf
kernelCommandLineArguments := strings.Split(string(cmdlineBytes), " ")
// Use the first virtio console device as system console.
//"console=hvc0",
// Stop in the initial ramdisk before attempting to transition to
// the root file system.
//"root=/dev/vda",
kernelCommandLineArguments = append(kernelCommandLineArguments, "console=hvc0")
vmlinuz := prefix + "-kernel"
initrd := prefix + "-initrd.img"
vmlinuzFile := vmlinuz
// need to check if it is gzipped, and, if so, gunzip it
filetype, err := checkFileType(vmlinuz)
if err != nil {
log.Fatalf("unable to check kernel file type at %s: %v", vmlinuz, err)
}
if filetype == "application/x-gzip" {
vmlinuzUncompressed := fmt.Sprintf("%s-uncompressed", vmlinuz)
// gzipped kernel, we load it into memory, unzip it, and pass it
f, err := os.Open(vmlinuz)
if err != nil {
log.Fatalf("unable to read kernel file %s: %v", vmlinuz, err)
}
defer f.Close()
r, err := gzip.NewReader(f)
if err != nil {
log.Fatalf("unable to read from file %s: %v", vmlinuz, err)
}
defer r.Close()
writer, err := os.Create(vmlinuzUncompressed)
if err != nil {
log.Fatalf("unable to create decompressed kernel file %s: %v", vmlinuzUncompressed, err)
}
defer writer.Close()
if _, err = io.Copy(writer, r); err != nil {
log.Fatalf("unable to decompress kernel file to %s: %v", vmlinuzUncompressed, err)
}
vmlinuzFile = vmlinuzUncompressed
}
bootLoader := vz.NewLinuxBootLoader(
vmlinuzFile,
vz.WithCommandLine(strings.Join(kernelCommandLineArguments, " ")),
vz.WithInitrd(initrd),
)
config := vz.NewVirtualMachineConfiguration(
bootLoader,
*cpus,
memBytes,
)
// console
stdin, stdout := os.Stdin, os.Stdout
serialPortAttachment := vz.NewFileHandleSerialPortAttachment(stdin, stdout)
consoleConfig := vz.NewVirtioConsoleDeviceSerialPortConfiguration(serialPortAttachment)
config.SetSerialPortsVirtualMachineConfiguration([]*vz.VirtioConsoleDeviceSerialPortConfiguration{
consoleConfig,
})
setRawMode(os.Stdin)
// network
// Select network mode
// for now, we only support vmnet and none, but hoping to have more in the future
if *networking == "" || *networking == "default" {
dflt := virtualizationNetworkingDefault
networking = &dflt
}
netMode := strings.SplitN(*networking, ",", 3)
switch netMode[0] {
case virtualizationNetworkingVMNet:
natAttachment := vz.NewNATNetworkDeviceAttachment()
networkConfig := vz.NewVirtioNetworkDeviceConfiguration(natAttachment)
config.SetNetworkDevicesVirtualMachineConfiguration([]*vz.VirtioNetworkDeviceConfiguration{
networkConfig,
})
networkConfig.SetMacAddress(vz.NewRandomLocallyAdministeredMACAddress())
case virtualizationNetworkingNone:
default:
log.Fatalf("Invalid networking mode: %s", netMode[0])
}
// entropy
entropyConfig := vz.NewVirtioEntropyDeviceConfiguration()
config.SetEntropyDevicesVirtualMachineConfiguration([]*vz.VirtioEntropyDeviceConfiguration{
entropyConfig,
})
var storageDevices []vz.StorageDeviceConfiguration
for i, d := range disks {
var id, diskPath string
if i != 0 {
id = strconv.Itoa(i)
}
if d.Size != 0 && d.Path == "" {
diskPath = filepath.Join(*state, "disk"+id+".raw")
}
if d.Path == "" {
log.Fatalf("disk specified with no size or name")
}
diskImageAttachment, err := vz.NewDiskImageStorageDeviceAttachment(
diskPath,
false,
)
if err != nil {
log.Fatal(err)
}
storageDeviceConfig := vz.NewVirtioBlockDeviceConfiguration(diskImageAttachment)
storageDevices = append(storageDevices, storageDeviceConfig)
}
for _, iso := range isoPaths {
diskImageAttachment, err := vz.NewDiskImageStorageDeviceAttachment(
iso,
true,
)
if err != nil {
log.Fatal(err)
}
storageDeviceConfig := vz.NewVirtioBlockDeviceConfiguration(diskImageAttachment)
storageDevices = append(storageDevices, storageDeviceConfig)
}
config.SetStorageDevicesVirtualMachineConfiguration(storageDevices)
// traditional memory balloon device which allows for managing guest memory. (optional)
config.SetMemoryBalloonDevicesVirtualMachineConfiguration([]vz.MemoryBalloonDeviceConfiguration{
vz.NewVirtioTraditionalMemoryBalloonDeviceConfiguration(),
})
// socket device (optional)
config.SetSocketDevicesVirtualMachineConfiguration([]vz.SocketDeviceConfiguration{
vz.NewVirtioSocketDeviceConfiguration(),
})
validated, err := config.Validate()
if !validated || err != nil {
log.Fatal("validation failed", err)
}
vm := vz.NewVirtualMachine(config)
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
}
})
for {
select {
case <-signalCh:
result, err := vm.RequestStop()
if err != nil {
log.Println("request stop error:", err)
return
}
log.Println("recieved signal", result)
case newState := <-vm.StateChangedNotify():
if newState == vz.VirtualMachineStateRunning {
log.Println("start VM is running")
}
if newState == vz.VirtualMachineStateStopped {
log.Println("stopped successfully")
return
}
case err := <-errCh:
log.Println("in start:", err)
}
}
}
// https://developer.apple.com/documentation/virtualization/running_linux_in_a_virtual_machine?language=objc#:~:text=Configure%20the%20Serial%20Port%20Device%20for%20Standard%20In%20and%20Out
func setRawMode(f *os.File) {
var attr unix.Termios
// Get settings for terminal
termios.Tcgetattr(f.Fd(), &attr)
// Put stdin into raw mode, disabling local echo, input canonicalization,
// and CR-NL mapping.
attr.Iflag &^= syscall.ICRNL
attr.Lflag &^= syscall.ICANON | syscall.ECHO
// Set minimum characters when reading = 1 char
attr.Cc[syscall.VMIN] = 1
// set timeout when reading as non-canonical mode
attr.Cc[syscall.VTIME] = 0
// reflects the changed settings
termios.Tcsetattr(f.Fd(), termios.TCSANOW, &attr)
}
func checkFileType(infile string) (string, error) {
file, err := os.Open(infile)
if err != nil {
return "", err
}
defer file.Close()
b := make([]byte, 512)
if _, err = file.Read(b); err != nil {
return "", err
}
return http.DetectContentType(b), nil
}

View File

@@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
package util

View File

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

21
src/cmd/linuxkit/vendor/github.com/Code-Hex/vz/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 codehex
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

@@ -0,0 +1,52 @@
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

@@ -0,0 +1,89 @@
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

@@ -0,0 +1,137 @@
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

@@ -0,0 +1,123 @@
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 (
"os"
"runtime"
)
// 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
serialPortAttachment()
}
type baseSerialPortAttachment struct{}
func (*baseSerialPortAttachment) serialPortAttachment() {}
var _ SerialPortAttachment = (*FileHandleSerialPortAttachment)(nil)
// FileHandleSerialPortAttachment defines a serial port attachment from a file handle.
//
// 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
*baseSerialPortAttachment
}
// NewFileHandleSerialPortAttachment intialize the FileHandleSerialPortAttachment from file handles.
//
// 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 {
attachment := &FileHandleSerialPortAttachment{
pointer: pointer{
ptr: C.newVZFileHandleSerialPortAttachment(
C.int(read.Fd()),
C.int(write.Fd()),
),
},
}
runtime.SetFinalizer(attachment, func(self *FileHandleSerialPortAttachment) {
self.Release()
})
return attachment
}
var _ SerialPortAttachment = (*FileSerialPortAttachment)(nil)
// FileSerialPortAttachment defines a serial port attachment from a file.
//
// Any data sent by the guest on the serial interface is written to the file.
// 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
*baseSerialPortAttachment
}
// 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.
func NewFileSerialPortAttachment(path string, shouldAppend bool) (*FileSerialPortAttachment, error) {
cpath := charWithGoString(path)
defer cpath.Free()
nserr := newNSErrorAsNil()
nserrPtr := nserr.Ptr()
attachment := &FileSerialPortAttachment{
pointer: pointer{
ptr: 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()
})
return attachment, nil
}
// VirtioConsoleDeviceSerialPortConfiguration represents Virtio Console Serial Port Device.
//
// 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.
// see: https://developer.apple.com/documentation/virtualization/vzvirtioconsoledeviceserialportconfiguration?language=objc
type VirtioConsoleDeviceSerialPortConfiguration struct {
pointer
}
// NewVirtioConsoleDeviceSerialPortConfiguration creates a new NewVirtioConsoleDeviceSerialPortConfiguration.
func NewVirtioConsoleDeviceSerialPortConfiguration(attachment SerialPortAttachment) *VirtioConsoleDeviceSerialPortConfiguration {
config := &VirtioConsoleDeviceSerialPortConfiguration{
pointer: pointer{
ptr: C.newVZVirtioConsoleDeviceSerialPortConfiguration(
attachment.Ptr(),
),
},
}
runtime.SetFinalizer(config, func(self *VirtioConsoleDeviceSerialPortConfiguration) {
self.Release()
})
return config
}

View File

@@ -0,0 +1,31 @@
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"
// 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
// entropy device. The guest operating system uses this device as a seed to generate random numbers.
//
// see: https://developer.apple.com/documentation/virtualization/vzvirtioentropydeviceconfiguration?language=objc
type VirtioEntropyDeviceConfiguration struct {
pointer
}
// NewVirtioEntropyDeviceConfiguration creates a new Virtio Entropy Device confiuration.
func NewVirtioEntropyDeviceConfiguration() *VirtioEntropyDeviceConfiguration {
config := &VirtioEntropyDeviceConfiguration{
pointer: pointer{
ptr: C.newVZVirtioEntropyDeviceConfiguration(),
},
}
runtime.SetFinalizer(config, func(self *VirtioEntropyDeviceConfiguration) {
self.Release()
})
return config
}

View File

@@ -0,0 +1,5 @@
module github.com/Code-Hex/vz
go 1.16
require github.com/rs/xid v1.2.1

View File

@@ -0,0 +1,2 @@
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=

View File

@@ -0,0 +1,44 @@
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"
// MemoryBalloonDeviceConfiguration for a memory balloon device configuration.
type MemoryBalloonDeviceConfiguration interface {
NSObject
memoryBalloonDeviceConfiguration()
}
type baseMemoryBalloonDeviceConfiguration struct{}
func (*baseMemoryBalloonDeviceConfiguration) memoryBalloonDeviceConfiguration() {}
var _ MemoryBalloonDeviceConfiguration = (*VirtioTraditionalMemoryBalloonDeviceConfiguration)(nil)
// VirtioTraditionalMemoryBalloonDeviceConfiguration is a configuration of the Virtio traditional memory balloon device.
//
// see: https://developer.apple.com/documentation/virtualization/vzvirtiotraditionalmemoryballoondeviceconfiguration?language=objc
type VirtioTraditionalMemoryBalloonDeviceConfiguration struct {
pointer
*baseMemoryBalloonDeviceConfiguration
}
// NewVirtioTraditionalMemoryBalloonDeviceConfiguration creates a new VirtioTraditionalMemoryBalloonDeviceConfiguration.
func NewVirtioTraditionalMemoryBalloonDeviceConfiguration() *VirtioTraditionalMemoryBalloonDeviceConfiguration {
config := &VirtioTraditionalMemoryBalloonDeviceConfiguration{
pointer: pointer{
ptr: C.newVZVirtioTraditionalMemoryBalloonDeviceConfiguration(),
},
}
runtime.SetFinalizer(config, func(self *VirtioTraditionalMemoryBalloonDeviceConfiguration) {
self.Release()
})
return config
}

View File

@@ -0,0 +1,211 @@
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 (
"net"
"os"
"runtime"
)
// BridgedNetwork defines a network interface that bridges a physical interface with a virtual machine.
//
// A bridged interface is shared between the virtual machine and the host system. Both host and
// virtual machine send and receive packets on the same physical interface but have distinct network layers.
//
// The BridgedNetwork can be used with a BridgedNetworkDeviceAttachment to set up a network device NetworkDeviceConfiguration.
// TODO(codehex): implement...
// see: https://developer.apple.com/documentation/virtualization/vzbridgednetworkinterface?language=objc
type BridgedNetwork interface {
NSObject
// NetworkInterfaces returns the list of network interfaces available for bridging.
NetworkInterfaces() []BridgedNetwork
// Identifier returns the unique identifier for this interface.
// The identifier is the BSD name associated with the interface (e.g. "en0").
Identifier() string
// LocalizedDisplayName returns a display name if available (e.g. "Ethernet").
LocalizedDisplayName() string
}
// Network device attachment using network address translation (NAT) with outside networks.
//
// Using the NAT attachment type, the host serves as router and performs network address translation
// for accesses to outside networks.
// see: https://developer.apple.com/documentation/virtualization/vznatnetworkdeviceattachment?language=objc
type NATNetworkDeviceAttachment struct {
pointer
*baseNetworkDeviceAttachment
}
var _ NetworkDeviceAttachment = (*NATNetworkDeviceAttachment)(nil)
// NewNATNetworkDeviceAttachment creates a new NATNetworkDeviceAttachment.
func NewNATNetworkDeviceAttachment() *NATNetworkDeviceAttachment {
attachment := &NATNetworkDeviceAttachment{
pointer: pointer{
ptr: C.newVZNATNetworkDeviceAttachment(),
},
}
runtime.SetFinalizer(attachment, func(self *NATNetworkDeviceAttachment) {
self.Release()
})
return attachment
}
// BridgedNetworkDeviceAttachment represents a physical interface on the host computer.
//
// Use this struct when configuring a network interface for your virtual machine.
// A bridged network device sends and receives packets on the same physical interface
// as the host computer, but does so using a different network layer.
//
// To use this attachment, your app must have the com.apple.vm.networking entitlement.
// If it doesnt, the use of this attachment point results in an invalid VZVirtualMachineConfiguration object in objective-c.
//
// see: https://developer.apple.com/documentation/virtualization/vzbridgednetworkdeviceattachment?language=objc
type BridgedNetworkDeviceAttachment struct {
pointer
*baseNetworkDeviceAttachment
}
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(),
),
},
}
runtime.SetFinalizer(attachment, func(self *BridgedNetworkDeviceAttachment) {
self.Release()
})
return attachment
}
// FileHandleNetworkDeviceAttachment sending raw network packets over a file handle.
//
// 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.
// see: https://developer.apple.com/documentation/virtualization/vzfilehandlenetworkdeviceattachment?language=objc
type FileHandleNetworkDeviceAttachment struct {
pointer
*baseNetworkDeviceAttachment
}
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 {
attachment := &FileHandleNetworkDeviceAttachment{
pointer: pointer{
ptr: C.newVZFileHandleNetworkDeviceAttachment(
C.int(file.Fd()),
),
},
}
runtime.SetFinalizer(attachment, func(self *FileHandleNetworkDeviceAttachment) {
self.Release()
})
return attachment
}
// NetworkDeviceAttachment for a network device attachment.
// see: https://developer.apple.com/documentation/virtualization/vznetworkdeviceattachment?language=objc
type NetworkDeviceAttachment interface {
NSObject
networkDeviceAttachment()
}
type baseNetworkDeviceAttachment struct{}
func (*baseNetworkDeviceAttachment) networkDeviceAttachment() {}
// VirtioNetworkDeviceConfiguration is configuration of a paravirtualized network device of type Virtio Network Device.
//
// The communication channel used on the host is defined through the attachment.
// It is set with the VZNetworkDeviceConfiguration.attachment property in objective-c.
//
// The configuration is only valid with valid MACAddress and attachment.
//
// see: https://developer.apple.com/documentation/virtualization/vzvirtionetworkdeviceconfiguration?language=objc
type VirtioNetworkDeviceConfiguration struct {
pointer
}
// NewVirtioNetworkDeviceConfiguration creates a new VirtioNetworkDeviceConfiguration with NetworkDeviceAttachment.
func NewVirtioNetworkDeviceConfiguration(attachment NetworkDeviceAttachment) *VirtioNetworkDeviceConfiguration {
config := &VirtioNetworkDeviceConfiguration{
pointer: pointer{
ptr: C.newVZVirtioNetworkDeviceConfiguration(
attachment.Ptr(),
),
},
}
runtime.SetFinalizer(config, func(self *VirtioNetworkDeviceConfiguration) {
self.Release()
})
return config
}
func (v *VirtioNetworkDeviceConfiguration) SetMacAddress(macAddress *MACAddress) {
C.setNetworkDevicesVZMACAddress(v.Ptr(), macAddress.Ptr())
}
// 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
}
// NewMACAddress creates a new MACAddress with net.HardwareAddr (MAC address).
func NewMACAddress(macAddr net.HardwareAddr) *MACAddress {
macAddrChar := charWithGoString(macAddr.String())
defer macAddrChar.Free()
ma := &MACAddress{
pointer: pointer{
ptr: C.newVZMACAddress(macAddrChar.CString()),
},
}
runtime.SetFinalizer(ma, func(self *MACAddress) {
self.Release()
})
return ma
}
// NewRandomLocallyAdministeredMACAddress creates a valid, random, unicast, locally administered address.
func NewRandomLocallyAdministeredMACAddress() *MACAddress {
ma := &MACAddress{
pointer: pointer{
ptr: C.newRandomLocallyAdministeredVZMACAddress(),
},
}
runtime.SetFinalizer(ma, func(self *MACAddress) {
self.Release()
})
return ma
}
func (m *MACAddress) String() string {
cstring := (*char)(C.getVZMACAddressString(m.Ptr()))
return cstring.String()
}
func (m *MACAddress) HardwareAddr() net.HardwareAddr {
hw, _ := net.ParseMAC(m.String())
return hw
}

View File

@@ -0,0 +1,206 @@
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

@@ -0,0 +1,46 @@
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

@@ -0,0 +1,109 @@
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,283 @@
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

@@ -0,0 +1,74 @@
//
// virtualization.h
//
// Created by codehex.
//
#pragma once
#import <Foundation/Foundation.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);
@interface Observer : NSObject
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
@end
/* BootLoader */
void *newVZLinuxBootLoader(const char *kernelPath);
void setCommandLineVZLinuxBootLoader(void *bootLoaderPtr, const char *commandLine);
void setInitialRamdiskURLVZLinuxBootLoader(void *bootLoaderPtr, const char *ramdiskPath);
/* VirtualMachineConfiguration */
bool validateVZVirtualMachineConfiguration(void *config, void **error);
void *newVZVirtualMachineConfiguration(void *bootLoader,
unsigned int CPUCount,
unsigned long long memorySize);
void setEntropyDevicesVZVirtualMachineConfiguration(void *config,
void *entropyDevices);
void setMemoryBalloonDevicesVZVirtualMachineConfiguration(void *config,
void *memoryBalloonDevices);
void setNetworkDevicesVZVirtualMachineConfiguration(void *config,
void *networkDevices);
void setSerialPortsVZVirtualMachineConfiguration(void *config,
void *serialPorts);
void setSocketDevicesVZVirtualMachineConfiguration(void *config,
void *socketDevices);
void setStorageDevicesVZVirtualMachineConfiguration(void *config,
void *storageDevices);
/* Configurations */
void *newVZFileHandleSerialPortAttachment(int readFileDescriptor, int writeFileDescriptor);
void *newVZFileSerialPortAttachment(const char *filePath, bool shouldAppend, void **error);
void *newVZVirtioConsoleDeviceSerialPortConfiguration(void *attachment);
void *newVZBridgedNetworkDeviceAttachment(void *networkInterface);
void *newVZNATNetworkDeviceAttachment(void);
void *newVZFileHandleNetworkDeviceAttachment(int fileDescriptor);
void *newVZVirtioNetworkDeviceConfiguration(void *attachment);
void setNetworkDevicesVZMACAddress(void *config, void *macAddress);
void *newVZVirtioEntropyDeviceConfiguration(void);
void *newVZVirtioBlockDeviceConfiguration(void *attachment);
void *newVZDiskImageStorageDeviceAttachment(const char *diskPath, bool readOnly, void **error);
void *newVZVirtioTraditionalMemoryBalloonDeviceConfiguration();
void *newVZVirtioSocketDeviceConfiguration();
void *newVZMACAddress(const char *macAddress);
void *newRandomLocallyAdministeredVZMACAddress();
const char *getVZMACAddressString(void *macAddress);
/* VirtualMachine */
void *newVZVirtualMachineWithDispatchQueue(void *config, void *queue, const char *vmid);
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);
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);

View File

@@ -0,0 +1,570 @@
//
// 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

10
src/cmd/linuxkit/vendor/github.com/pkg/term/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,10 @@
Copyright (c) 2014, David Cheney
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. 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.
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 HOLDER 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.

View File

@@ -0,0 +1,4 @@
// Package termios implements the low level termios(3) terminal line discipline facilities.
//
// For a higher level interface please use the github.com/pkg/term package.
package termios

View File

@@ -0,0 +1,16 @@
// +build !windows,!solaris
package termios
import (
"syscall"
"golang.org/x/sys/unix"
)
func ioctl(fd, request, argp uintptr) error {
if _, _, e := unix.Syscall6(syscall.SYS_IOCTL, fd, request, argp, 0, 0, 0); e != 0 {
return e
}
return nil
}

View File

@@ -0,0 +1,10 @@
package termios
const (
_IOC_PARAM_SHIFT = 13
_IOC_PARAM_MASK = (1 << _IOC_PARAM_SHIFT) - 1
)
func _IOC_PARM_LEN(ioctl uintptr) uintptr {
return (ioctl >> 16) & _IOC_PARAM_MASK
}

View File

@@ -0,0 +1,7 @@
package termios
import "golang.org/x/sys/unix"
func ioctl(fd, request, argp uintptr) error {
return unix.IoctlSetInt(int(fd), uint(request), int(argp))
}

View File

@@ -0,0 +1,48 @@
// +build !windows
package termios
import (
"fmt"
"os"
"golang.org/x/sys/unix"
)
func open_device(path string) (uintptr, error) {
fd, err := unix.Open(path, unix.O_NOCTTY|unix.O_RDWR|unix.O_CLOEXEC, 0666)
if err != nil {
return 0, fmt.Errorf("unable to open %q: %v", path, err)
}
return uintptr(fd), nil
}
// Pty returns a UNIX 98 pseudoterminal device.
// Pty returns a pair of fds representing the master and slave pair.
func Pty() (*os.File, *os.File, error) {
ptm, err := open_pty_master()
if err != nil {
return nil, nil, err
}
sname, err := Ptsname(ptm)
if err != nil {
return nil, nil, err
}
err = grantpt(ptm)
if err != nil {
return nil, nil, err
}
err = unlockpt(ptm)
if err != nil {
return nil, nil, err
}
pts, err := open_device(sname)
if err != nil {
return nil, nil, err
}
return os.NewFile(uintptr(ptm), "ptm"), os.NewFile(uintptr(pts), sname), nil
}

View File

@@ -0,0 +1,37 @@
// +build dragonfly openbsd
package termios
// #include<stdlib.h>
import "C"
import "syscall"
func open_pty_master() (uintptr, error) {
rc := C.posix_openpt(syscall.O_NOCTTY | syscall.O_RDWR)
if rc < 0 {
return 0, syscall.Errno(rc)
}
return uintptr(rc), nil
}
func Ptsname(fd uintptr) (string, error) {
slavename := C.GoString(C.ptsname(C.int(fd)))
return slavename, nil
}
func grantpt(fd uintptr) error {
rc := C.grantpt(C.int(fd))
if rc == 0 {
return nil
}
return syscall.Errno(rc)
}
func unlockpt(fd uintptr) error {
rc := C.unlockpt(C.int(fd))
if rc == 0 {
return nil
}
return syscall.Errno(rc)
}

View File

@@ -0,0 +1,36 @@
package termios
import (
"errors"
"unsafe"
"golang.org/x/sys/unix"
)
func open_pty_master() (uintptr, error) {
return open_device("/dev/ptmx")
}
func Ptsname(fd uintptr) (string, error) {
n := make([]byte, _IOC_PARM_LEN(unix.TIOCPTYGNAME))
err := ioctl(fd, unix.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0])))
if err != nil {
return "", err
}
for i, c := range n {
if c == 0 {
return string(n[:i]), nil
}
}
return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
}
func grantpt(fd uintptr) error {
return unix.IoctlSetInt(int(fd), unix.TIOCPTYGRANT, 0)
}
func unlockpt(fd uintptr) error {
return unix.IoctlSetInt(int(fd), unix.TIOCPTYUNLK, 0)
}

View File

@@ -0,0 +1,38 @@
package termios
import (
"fmt"
"unsafe"
)
func posix_openpt(oflag int) (fd uintptr, err error) {
// Copied from debian-golang-pty/pty_freebsd.go.
r0, _, e1 := unix.Syscall(unix.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0)
fd = uintptr(r0)
if e1 != 0 {
err = e1
}
return
}
func open_pty_master() (uintptr, error) {
return posix_openpt(unix.O_NOCTTY | unix.O_RDWR | unix.O_CLOEXEC)
}
func Ptsname(fd uintptr) (string, error) {
var n uintptr
err := ioctl(fd, unix.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
if err != nil {
return "", err
}
return fmt.Sprintf("/dev/pts/%d", n), nil
}
func grantpt(fd uintptr) error {
var n uintptr
return ioctl(fd, unix.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
}
func unlockpt(fd uintptr) error {
return nil
}

View File

@@ -0,0 +1,31 @@
package termios
import (
"fmt"
"unsafe"
"golang.org/x/sys/unix"
)
func open_pty_master() (uintptr, error) {
return open_device("/dev/ptmx")
}
func Ptsname(fd uintptr) (string, error) {
var n uintptr
err := ioctl(fd, unix.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
if err != nil {
return "", err
}
return fmt.Sprintf("/dev/pts/%d", n), nil
}
func grantpt(fd uintptr) error {
var n uintptr
return ioctl(fd, unix.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
}
func unlockpt(fd uintptr) error {
var n uintptr
return ioctl(fd, unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&n)))
}

View File

@@ -0,0 +1,31 @@
package termios
import (
"bytes"
"golang.org/x/sys/unix"
)
func open_pty_master() (uintptr, error) {
fd, err := unix.Open("/dev/ptmx", unix.O_NOCTTY|unix.O_RDWR, 0666)
if err != nil {
return 0, err
}
return uintptr(fd), nil
}
func Ptsname(fd uintptr) (string, error) {
ptm, err := unix.IoctlGetPtmget(int(fd), unix.TIOCPTSNAME)
if err != nil {
return "", err
}
return string(ptm.Sn[:bytes.IndexByte(ptm.Sn[:], 0)]), nil
}
func grantpt(fd uintptr) error {
return unix.IoctlSetInt(int(fd), unix.TIOCGRANTPT, 0)
}
func unlockpt(fd uintptr) error {
return nil
}

View File

@@ -0,0 +1,33 @@
package termios
// #include<stdlib.h>
import "C"
import "syscall"
func open_pty_master() (uintptr, error) {
return open_device("/dev/ptmx")
}
func Ptsname(fd uintptr) (string, error) {
slavename := C.GoString(C.ptsname(C.int(fd)))
return slavename, nil
}
func grantpt(fd uintptr) error {
rc := C.grantpt(C.int(fd))
if rc == 0 {
return nil
} else {
return syscall.Errno(rc)
}
}
func unlockpt(fd uintptr) error {
rc := C.unlockpt(C.int(fd))
if rc == 0 {
return nil
} else {
return syscall.Errno(rc)
}
}

View File

@@ -0,0 +1,45 @@
// +build !windows
package termios
import (
"golang.org/x/sys/unix"
)
// Tiocmget returns the state of the MODEM bits.
func Tiocmget(fd uintptr) (int, error) {
return unix.IoctlGetInt(int(fd), unix.TIOCMGET)
}
// Tiocmset sets the state of the MODEM bits.
func Tiocmset(fd uintptr, status int) error {
return unix.IoctlSetInt(int(fd), unix.TIOCMSET, status)
}
// Tiocmbis sets the indicated modem bits.
func Tiocmbis(fd uintptr, status int) error {
return unix.IoctlSetPointerInt(int(fd), unix.TIOCMBIS, status)
}
// Tiocmbic clears the indicated modem bits.
func Tiocmbic(fd uintptr, status int) error {
return unix.IoctlSetPointerInt(int(fd), unix.TIOCMBIC, status)
}
// Cfmakecbreak modifies attr for cbreak mode.
func Cfmakecbreak(attr *unix.Termios) {
attr.Lflag &^= unix.ECHO | unix.ICANON
attr.Cc[unix.VMIN] = 1
attr.Cc[unix.VTIME] = 0
}
// Cfmakeraw modifies attr for raw mode.
func Cfmakeraw(attr *unix.Termios) {
attr.Iflag &^= unix.BRKINT | unix.ICRNL | unix.INPCK | unix.ISTRIP | unix.IXON
attr.Oflag &^= unix.OPOST
attr.Cflag &^= unix.CSIZE | unix.PARENB
attr.Cflag |= unix.CS8
attr.Lflag &^= unix.ECHO | unix.ICANON | unix.IEXTEN | unix.ISIG
attr.Cc[unix.VMIN] = 1
attr.Cc[unix.VTIME] = 0
}

View File

@@ -0,0 +1,89 @@
// +build darwin freebsd openbsd netbsd dragonfly
package termios
import (
"time"
"golang.org/x/sys/unix"
)
const (
FREAD = 0x0001
FWRITE = 0x0002
IXON = 0x00000200
IXOFF = 0x00000400
IXANY = 0x00000800
CCTS_OFLOW = 0x00010000
CRTS_IFLOW = 0x00020000
CRTSCTS = CCTS_OFLOW | CRTS_IFLOW
)
// Tcgetattr gets the current serial port settings.
func Tcgetattr(fd uintptr, argp *unix.Termios) error {
return unix.IoctlSetTermios(int(fd), unix.TIOCGETA, argp)
}
// Tcsetattr sets the current serial port settings.
func Tcsetattr(fd, opt uintptr, argp *unix.Termios) error {
switch opt {
case TCSANOW:
opt = unix.TIOCSETA
case TCSADRAIN:
opt = unix.TIOCSETAW
case TCSAFLUSH:
opt = unix.TIOCSETAF
default:
return unix.EINVAL
}
return unix.IoctlSetTermios(int(fd), uint(opt), argp)
}
// Tcsendbreak function transmits a continuous stream of zero-valued bits for
// four-tenths of a second to the terminal referenced by fildes. The duration
// parameter is ignored in this implementation.
func Tcsendbreak(fd, duration uintptr) error {
if err := unix.IoctlSetInt(int(fd), unix.TIOCSBRK, 0); err != nil {
return err
}
time.Sleep(4 / 10 * time.Second)
return unix.IoctlSetInt(int(fd), unix.TIOCCBRK, 0)
}
// Tcdrain waits until all output written to the terminal referenced by fd has been transmitted to the terminal.
func Tcdrain(fd uintptr) error {
return unix.IoctlSetInt(int(fd), unix.TIOCDRAIN, 0)
}
// Tcflush discards data written to the object referred to by fd but not transmitted, or data received but not read, depending on the value of which.
func Tcflush(fd, which uintptr) error {
var com int
switch which {
case unix.TCIFLUSH:
com = FREAD
case unix.TCOFLUSH:
com = FWRITE
case unix.TCIOFLUSH:
com = FREAD | FWRITE
default:
return unix.EINVAL
}
return unix.IoctlSetPointerInt(int(fd), unix.TIOCFLUSH, com)
}
// Cfgetispeed returns the input baud rate stored in the termios structure.
func Cfgetispeed(attr *unix.Termios) uint32 { return uint32(attr.Ispeed) }
// Cfgetospeed returns the output baud rate stored in the termios structure.
func Cfgetospeed(attr *unix.Termios) uint32 { return uint32(attr.Ospeed) }
// Tiocinq returns the number of bytes in the input buffer.
func Tiocinq(fd uintptr) (int, error) {
return 0, nil
}
// Tiocoutq return the number of bytes in the output buffer.
func Tiocoutq(fd uintptr) (int, error) {
return unix.IoctlGetInt(int(fd), unix.TIOCOUTQ)
}

View File

@@ -0,0 +1,13 @@
// +build !windows,!solaris
package termios
const (
TCIFLUSH = 0
TCOFLUSH = 1
TCIOFLUSH = 2
TCSANOW = 0
TCSADRAIN = 1
TCSAFLUSH = 2
)

View File

@@ -0,0 +1,14 @@
package termios
// #include<termios.h>
import "C"
const (
TCIFLUSH = C.TCIFLUSH
TCOFLUSH = C.TCOFLUSH
TCIOFLUSH = C.TCIOFLUSH
TCSANOW = C.TCSANOW
TCSADRAIN = C.TCSADRAIN
TCSAFLUSH = C.TCSAFLUSH
)

View File

@@ -0,0 +1,80 @@
package termios
import (
"golang.org/x/sys/unix"
)
const (
TCSETS = 0x5402
TCSETSW = 0x5403
TCSETSF = 0x5404
TCFLSH = 0x540B
TCSBRK = 0x5409
TCSBRKP = 0x5425
IXON = 0x00000400
IXANY = 0x00000800
IXOFF = 0x00001000
CRTSCTS = 0x80000000
)
// Tcgetattr gets the current serial port settings.
func Tcgetattr(fd uintptr, argp *unix.Termios) error {
return unix.IoctlSetTermios(int(fd), unix.TCGETS, argp)
}
// Tcsetattr sets the current serial port settings.
func Tcsetattr(fd, action uintptr, argp *unix.Termios) error {
var request uintptr
switch action {
case TCSANOW:
request = TCSETS
case TCSADRAIN:
request = TCSETSW
case TCSAFLUSH:
request = TCSETSF
default:
return unix.EINVAL
}
return unix.IoctlSetTermios(int(fd), uint(request), argp)
}
// Tcsendbreak transmits a continuous stream of zero-valued bits for a specific
// duration, if the terminal is using asynchronous serial data transmission. If
// duration is zero, it transmits zero-valued bits for at least 0.25 seconds, and not more that 0.5 seconds.
// If duration is not zero, it sends zero-valued bits for some
// implementation-defined length of time.
func Tcsendbreak(fd, duration uintptr) error {
return ioctl(fd, TCSBRKP, duration)
}
// Tcdrain waits until all output written to the object referred to by fd has been transmitted.
func Tcdrain(fd uintptr) error {
// simulate drain with TCSADRAIN
var attr unix.Termios
if err := Tcgetattr(fd, &attr); err != nil {
return err
}
return Tcsetattr(fd, TCSADRAIN, &attr)
}
// Tcflush discards data written to the object referred to by fd but not transmitted, or data received but not read, depending on the value of selector.
func Tcflush(fd, selector uintptr) error {
return ioctl(fd, TCFLSH, selector)
}
// Tiocinq returns the number of bytes in the input buffer.
func Tiocinq(fd uintptr) (int, error) {
return unix.IoctlGetInt(int(fd), unix.TIOCINQ)
}
// Tiocoutq return the number of bytes in the output buffer.
func Tiocoutq(fd uintptr) (int, error) {
return unix.IoctlGetInt(int(fd), unix.TIOCOUTQ)
}
// Cfgetispeed returns the input baud rate stored in the termios structure.
func Cfgetispeed(attr *unix.Termios) uint32 { return attr.Ispeed }
// Cfgetospeed returns the output baud rate stored in the termios structure.
func Cfgetospeed(attr *unix.Termios) uint32 { return attr.Ospeed }

View File

@@ -0,0 +1,124 @@
package termios
// #include <termios.h>
// typedef struct termios termios_t;
import "C"
import (
"golang.org/x/sys/unix"
"unsafe"
)
const (
TCSETS = 0x5402
TCSETSW = 0x5403
TCSETSF = 0x5404
TCFLSH = 0x540B
TCSBRK = 0x5409
TCSBRKP = 0x5425
IXON = 0x00000400
IXANY = 0x00000800
IXOFF = 0x00001000
CRTSCTS = 0x80000000
)
// See /usr/include/sys/termios.h
const FIORDCHK = C.FIORDCHK
// Tcgetattr gets the current serial port settings.
func Tcgetattr(fd uintptr, argp *unix.Termios) error {
termios, err := unix.IoctlGetTermios(int(fd), unix.TCGETS)
*argp = *(tiosTounix(termios))
return err
}
// Tcsetattr sets the current serial port settings.
func Tcsetattr(fd, action uintptr, argp *unix.Termios) error {
return unix.IoctlSetTermios(int(fd), uint(action), tiosToUnix(argp))
}
// Tcsendbreak transmits a continuous stream of zero-valued bits for a specific
// duration, if the terminal is using asynchronous serial data transmission. If
// duration is zero, it transmits zero-valued bits for at least 0.25 seconds, and not more that 0.5 seconds.
// If duration is not zero, it sends zero-valued bits for some
// implementation-defined length of time.
func Tcsendbreak(fd, duration uintptr) error {
return ioctl(fd, TCSBRKP, duration)
}
// Tcdrain waits until all output written to the object referred to by fd has been transmitted.
func Tcdrain(fd uintptr) error {
// simulate drain with TCSADRAIN
var attr unix.Termios
if err := Tcgetattr(fd, &attr); err != nil {
return err
}
return Tcsetattr(fd, TCSADRAIN, &attr)
}
// Tcflush discards data written to the object referred to by fd but not transmitted, or data received but not read, depending on the value of selector.
func Tcflush(fd, selector uintptr) error {
return ioctl(fd, TCFLSH, selector)
}
// Tiocinq returns the number of bytes in the input buffer.
func Tiocinq(fd uintptr, argp *int) (err error) {
*argp, err = unix.IoctlGetInt(int(fd), FIORDCHK)
return err
}
// Tiocoutq return the number of bytes in the output buffer.
func Tiocoutq(fd uintptr, argp *int) error {
return ioctl(fd, unix.TIOCOUTQ, uintptr(unsafe.Pointer(argp)))
}
// Cfgetispeed returns the input baud rate stored in the termios structure.
func Cfgetispeed(attr *unix.Termios) uint32 {
solTermios := tiosToUnix(attr)
return uint32(C.cfgetispeed((*C.termios_t)(unsafe.Pointer(solTermios))))
}
// Cfsetispeed sets the input baud rate stored in the termios structure.
func Cfsetispeed(attr *unix.Termios, speed uintptr) error {
solTermios := tiosToUnix(attr)
_, err := C.cfsetispeed((*C.termios_t)(unsafe.Pointer(solTermios)), C.speed_t(speed))
return err
}
// Cfgetospeed returns the output baud rate stored in the termios structure.
func Cfgetospeed(attr *unix.Termios) uint32 {
solTermios := tiosToUnix(attr)
return uint32(C.cfgetospeed((*C.termios_t)(unsafe.Pointer(solTermios))))
}
// Cfsetospeed sets the output baud rate stored in the termios structure.
func Cfsetospeed(attr *unix.Termios, speed uintptr) error {
solTermios := tiosToUnix(attr)
_, err := C.cfsetospeed((*C.termios_t)(unsafe.Pointer(solTermios)), C.speed_t(speed))
return err
}
// tiosToUnix copies a unix.Termios to a x/sys/unix.Termios.
// This is needed since type conversions between the two fail due to
// more recent x/sys/unix.Termios renaming the padding field.
func tiosToUnix(st *unix.Termios) *unix.Termios {
return &unix.Termios{
Iflag: st.Iflag,
Oflag: st.Oflag,
Cflag: st.Cflag,
Lflag: st.Lflag,
Cc: st.Cc,
}
}
// tiosTounix copies a x/sys/unix.Termios to a unix.Termios.
func tiosTounix(ut *unix.Termios) *unix.Termios {
return &unix.Termios{
Iflag: ut.Iflag,
Oflag: ut.Oflag,
Cflag: ut.Cflag,
Lflag: ut.Lflag,
Cc: ut.Cc,
}
}

View File

@@ -0,0 +1 @@
package termios

View File

@@ -0,0 +1,27 @@
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

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

19
src/cmd/linuxkit/vendor/github.com/rs/xid/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,19 @@
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.

116
src/cmd/linuxkit/vendor/github.com/rs/xid/README.md generated vendored Normal file
View File

@@ -0,0 +1,116 @@
# 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
- Dart port by [Peter Bwire](https://github.com/pitabwire): https://pub.dev/packages/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).

11
src/cmd/linuxkit/vendor/github.com/rs/xid/error.go generated vendored Normal file
View File

@@ -0,0 +1,11 @@
package xid
const (
// ErrInvalidID is returned when trying to unmarshal an invalid ID.
ErrInvalidID strErr = "xid: invalid ID"
)
// strErr allows declaring errors as constants.
type strErr string
func (err strErr) Error() string { return string(err) }

3
src/cmd/linuxkit/vendor/github.com/rs/xid/go.mod generated vendored Normal file
View File

@@ -0,0 +1,3 @@
module github.com/rs/xid
go 1.12

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
// +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

@@ -0,0 +1,38 @@
// +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
}

392
src/cmd/linuxkit/vendor/github.com/rs/xid/id.go generated vendored Normal file
View File

@@ -0,0 +1,392 @@
// 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"
"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 (
// 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
}
}
if !decode(id, text) {
return ErrInvalidID
}
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
}
// Check the slice length to prevent panic on passing it to UnmarshalText()
if len(b) < 2 {
return ErrInvalidID
}
return id.UnmarshalText(b[1 : len(b)-1])
}
// decode by unrolling the stdlib base32 algorithm + customized safe check.
func decode(id *ID, src []byte) bool {
_ = 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
// Validate that there are no discarer bits (padding) in src that would
// cause the string-encoded id not to equal src.
var check [4]byte
check[3] = encoding[(id[11]<<4)&0x1F]
check[2] = encoding[(id[11]>>1)&0x1F]
check[1] = encoding[(id[11]>>6)&0x1F|(id[10]<<2)&0x1F]
check[0] = encoding[id[10]>>3]
return bytes.Equal([]byte(src[16:20]), check[:])
}
// 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

@@ -37,6 +37,9 @@ github.com/Azure/go-autorest/autorest/validation
github.com/Azure/go-autorest/logger
# github.com/Azure/go-autorest/tracing v0.6.0
github.com/Azure/go-autorest/tracing
# github.com/Code-Hex/vz v0.0.4
## explicit
github.com/Code-Hex/vz
# github.com/Microsoft/go-winio v0.5.2
## explicit
github.com/Microsoft/go-winio
@@ -427,6 +430,9 @@ github.com/opencontainers/runtime-spec/specs-go
github.com/packethost/packngo
# github.com/pkg/errors v0.9.1
github.com/pkg/errors
# github.com/pkg/term v1.1.0
## explicit
github.com/pkg/term/termios
# github.com/pmezard/go-difflib v1.0.0
github.com/pmezard/go-difflib/difflib
# github.com/prometheus/client_golang v1.12.1
@@ -468,6 +474,8 @@ github.com/radu-matei/azure-vhd-utils/vhdcore/writer
# github.com/rn/iso9660wrap v0.0.0-20171120145750-baf8d62ad315
## explicit
github.com/rn/iso9660wrap
# github.com/rs/xid v1.4.0
github.com/rs/xid
# github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6
## explicit
github.com/scaleway/scaleway-sdk-go/api/instance/v1
@@ -610,6 +618,7 @@ golang.org/x/oauth2/jwt
golang.org/x/sync/errgroup
golang.org/x/sync/semaphore
# golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
## explicit
golang.org/x/sys/cpu
golang.org/x/sys/execabs
golang.org/x/sys/internal/unsafeheader