提交 caa8a540 编写于 作者: M Matt Rickard 提交者: GitHub

Merge pull request #1776 from dlorenc/hyperkit

WIP: Initial hyperkit driver implementation.
......@@ -300,6 +300,10 @@
"Comment": "v1.3.0",
"Rev": "583e8937c61f1af6513608ccc75c97b6abdf4ff9"
},
{
"ImportPath": "github.com/c4milo/gotoolkit",
"Rev": "bcc06269efa974c4f098619d9aae436846e83d84"
},
{
"ImportPath": "github.com/cloudflare/cfssl/auth",
"Comment": "1.2.0",
......@@ -383,32 +387,32 @@
},
{
"ImportPath": "github.com/containernetworking/cni/libcni",
"Comment": "v0.5.2",
"Comment": "spec-v0.3.1",
"Rev": "137b4975ecab6e1f0c24c1e3c228a50a3cfba75e"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/invoke",
"Comment": "v0.5.2",
"Comment": "spec-v0.3.1",
"Rev": "137b4975ecab6e1f0c24c1e3c228a50a3cfba75e"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/types",
"Comment": "v0.5.2",
"Comment": "spec-v0.3.1",
"Rev": "137b4975ecab6e1f0c24c1e3c228a50a3cfba75e"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/types/020",
"Comment": "v0.5.2",
"Comment": "spec-v0.3.1",
"Rev": "137b4975ecab6e1f0c24c1e3c228a50a3cfba75e"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/types/current",
"Comment": "v0.5.2",
"Comment": "spec-v0.3.1",
"Rev": "137b4975ecab6e1f0c24c1e3c228a50a3cfba75e"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/version",
"Comment": "v0.5.2",
"Comment": "spec-v0.3.1",
"Rev": "137b4975ecab6e1f0c24c1e3c228a50a3cfba75e"
},
{
......@@ -776,12 +780,12 @@
},
{
"ImportPath": "github.com/docker/distribution/digest",
"Comment": "v2.4.0-rc.1-38-gcd27f17",
"Comment": "v2.4.0-rc.1-38-gcd27f179",
"Rev": "cd27f179f2c10c5d300e6d09025b538c475b0d51"
},
{
"ImportPath": "github.com/docker/distribution/reference",
"Comment": "v2.4.0-rc.1-38-gcd27f17",
"Comment": "v2.4.0-rc.1-38-gcd27f179",
"Rev": "cd27f179f2c10c5d300e6d09025b538c475b0d51"
},
{
......@@ -1664,6 +1668,11 @@
"Comment": "v4.0.0-22-g7a54b6f",
"Rev": "7a54b6fc903feab1e7cb6573177ca09b544eb1e2"
},
{
"ImportPath": "github.com/hooklift/iso9660",
"Comment": "v1.0.0-6-g1cf07e5",
"Rev": "1cf07e5970d810f027bfbdfa2e9ad86db479c53a"
},
{
"ImportPath": "github.com/howeyc/gopass",
"Rev": "bf9dde6d0d2c004a008c27aaee91170c786f6db8"
......@@ -1675,6 +1684,7 @@
},
{
"ImportPath": "github.com/inconshreveable/mousetrap",
"Comment": "v1.0",
"Rev": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
},
{
......@@ -1807,6 +1817,10 @@
"Comment": "v2.1.1-5-g1b4ae6f",
"Rev": "1b4ae6fb4e77b095934d4430860ff202060169f8"
},
{
"ImportPath": "github.com/mitchellh/go-ps",
"Rev": "4fdf99ab29366514c69ccccddab5dc58b8d84062"
},
{
"ImportPath": "github.com/mitchellh/go-wordwrap",
"Rev": "ad45545899c7b13c020ea92b2072220eefad42b8"
......@@ -1815,6 +1829,11 @@
"ImportPath": "github.com/mitchellh/mapstructure",
"Rev": "53818660ed4955e899c0bcafa97299a388bd7c8e"
},
{
"ImportPath": "github.com/moby/hyperkit/go",
"Comment": "v0.20170425-38-g62fb993",
"Rev": "62fb993cc65090d3efe29e9b1ed871b017eeaa4d"
},
{
"ImportPath": "github.com/mreiferson/go-httpclient",
"Rev": "31f0106b4474f14bc441575c19d3a5fa21aa1f6c"
......@@ -1829,82 +1848,82 @@
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer",
"Comment": "v1.0.0-rc2-49-gd223e2a",
"Comment": "v1.0.0-rc2-49-gd223e2ad",
"Rev": "d223e2adae83f62d58448a799a5da05730228089"
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer/apparmor",
"Comment": "v1.0.0-rc2-49-gd223e2a",
"Comment": "v1.0.0-rc2-49-gd223e2ad",
"Rev": "d223e2adae83f62d58448a799a5da05730228089"
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer/cgroups",
"Comment": "v1.0.0-rc2-49-gd223e2a",
"Comment": "v1.0.0-rc2-49-gd223e2ad",
"Rev": "d223e2adae83f62d58448a799a5da05730228089"
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer/cgroups/fs",
"Comment": "v1.0.0-rc2-49-gd223e2a",
"Comment": "v1.0.0-rc2-49-gd223e2ad",
"Rev": "d223e2adae83f62d58448a799a5da05730228089"
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer/cgroups/systemd",
"Comment": "v1.0.0-rc2-49-gd223e2a",
"Comment": "v1.0.0-rc2-49-gd223e2ad",
"Rev": "d223e2adae83f62d58448a799a5da05730228089"
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer/configs",
"Comment": "v1.0.0-rc2-49-gd223e2a",
"Comment": "v1.0.0-rc2-49-gd223e2ad",
"Rev": "d223e2adae83f62d58448a799a5da05730228089"
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer/configs/validate",
"Comment": "v1.0.0-rc2-49-gd223e2a",
"Comment": "v1.0.0-rc2-49-gd223e2ad",
"Rev": "d223e2adae83f62d58448a799a5da05730228089"
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer/criurpc",
"Comment": "v1.0.0-rc2-49-gd223e2a",
"Comment": "v1.0.0-rc2-49-gd223e2ad",
"Rev": "d223e2adae83f62d58448a799a5da05730228089"
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer/keys",
"Comment": "v1.0.0-rc2-49-gd223e2a",
"Comment": "v1.0.0-rc2-49-gd223e2ad",
"Rev": "d223e2adae83f62d58448a799a5da05730228089"
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer/label",
"Comment": "v1.0.0-rc2-49-gd223e2a",
"Comment": "v1.0.0-rc2-49-gd223e2ad",
"Rev": "d223e2adae83f62d58448a799a5da05730228089"
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer/seccomp",
"Comment": "v1.0.0-rc2-49-gd223e2a",
"Comment": "v1.0.0-rc2-49-gd223e2ad",
"Rev": "d223e2adae83f62d58448a799a5da05730228089"
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer/selinux",
"Comment": "v1.0.0-rc2-49-gd223e2a",
"Comment": "v1.0.0-rc2-49-gd223e2ad",
"Rev": "d223e2adae83f62d58448a799a5da05730228089"
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer/stacktrace",
"Comment": "v1.0.0-rc2-49-gd223e2a",
"Comment": "v1.0.0-rc2-49-gd223e2ad",
"Rev": "d223e2adae83f62d58448a799a5da05730228089"
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer/system",
"Comment": "v1.0.0-rc2-49-gd223e2a",
"Comment": "v1.0.0-rc2-49-gd223e2ad",
"Rev": "d223e2adae83f62d58448a799a5da05730228089"
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer/user",
"Comment": "v1.0.0-rc2-49-gd223e2a",
"Comment": "v1.0.0-rc2-49-gd223e2ad",
"Rev": "d223e2adae83f62d58448a799a5da05730228089"
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer/utils",
"Comment": "v1.0.0-rc2-49-gd223e2a",
"Comment": "v1.0.0-rc2-49-gd223e2ad",
"Rev": "d223e2adae83f62d58448a799a5da05730228089"
},
{
......@@ -1913,6 +1932,7 @@
},
{
"ImportPath": "github.com/pelletier/go-buffruneio",
"Comment": "v0.1.0",
"Rev": "df1e16fde7fc330a0ca68167c23bf7ed6ac31d6d"
},
{
......@@ -1979,17 +1999,17 @@
},
{
"ImportPath": "github.com/r2d4/external-storage/lib/controller",
"Comment": "v1.0.0-20-g066bf161",
"Comment": "v1.0.0-20-g066bf16",
"Rev": "066bf1613ae2b6e7024ba94fffca35f8b3c83f5b"
},
{
"ImportPath": "github.com/r2d4/external-storage/lib/leaderelection",
"Comment": "v1.0.0-20-g066bf161",
"Comment": "v1.0.0-20-g066bf16",
"Rev": "066bf1613ae2b6e7024ba94fffca35f8b3c83f5b"
},
{
"ImportPath": "github.com/r2d4/external-storage/lib/leaderelection/resourcelock",
"Comment": "v1.0.0-20-g066bf161",
"Comment": "v1.0.0-20-g066bf16",
"Rev": "066bf1613ae2b6e7024ba94fffca35f8b3c83f5b"
},
{
......@@ -2338,8 +2358,13 @@
},
{
"ImportPath": "github.com/xiang90/probing",
"Comment": "0.0.1",
"Rev": "07dd2e8dfe18522e9c447ba95f2fe95262f63bb2"
},
{
"ImportPath": "github.com/zchee/go-vmnet",
"Rev": "97ebf91740978f1e665defc0a960fb997ebe282b"
},
{
"ImportPath": "go.pedge.io/pb/go/google/protobuf",
"Rev": "f3c84f58974dc53d460d0855337cad85843bf0df"
......
......@@ -51,6 +51,7 @@ LOCALKUBE_LDFLAGS := "$(K8S_VERSION_LDFLAGS) $(MINIKUBE_LDFLAGS) -s -w -extldfla
LOCALKUBEFILES := GOPATH=$(GOPATH) go list -f '{{join .Deps "\n"}}' ./cmd/localkube/ | grep k8s.io | GOPATH=$(GOPATH) xargs go list -f '{{ range $$file := .GoFiles }} {{$$.Dir}}/{{$$file}}{{"\n"}}{{end}}'
MINIKUBEFILES := GOPATH=$(GOPATH) go list -f '{{join .Deps "\n"}}' ./cmd/minikube/ | grep k8s.io | GOPATH=$(GOPATH) xargs go list -f '{{ range $$file := .GoFiles }} {{$$.Dir}}/{{$$file}}{{"\n"}}{{end}}'
HYPERKIT_FILES := GOPATH=$(GOPATH) go list -f '{{join .Deps "\n"}}' k8s.io/minikube/cmd/drivers/hyperkit | grep k8s.io | GOPATH=$(GOPATH) xargs go list -f '{{ range $$file := .GoFiles }} {{$$.Dir}}/{{$$file}}{{"\n"}}{{end}}'
MINIKUBE_ENV_linux := CGO_ENABLED=1 GOARCH=amd64 GOOS=linux
MINIKUBE_ENV_darwin := CGO_ENABLED=1 GOARCH=amd64 GOOS=darwin
......@@ -162,7 +163,7 @@ $(GOPATH)/bin/go-bindata:
GOBIN=$(GOPATH)/bin go get github.com/jteeuwen/go-bindata/...
.PHONY: cross
cross: out/localkube out/minikube-linux-amd64 out/minikube-darwin-amd64 out/minikube-windows-amd64.exe
cross: out/localkube out/minikube-linux-amd64 out/minikube-darwin-amd64 out/minikube-windows-amd64.exe out/docker-machine-driver-hyperkit
.PHONY: e2e-cross
e2e-cross: e2e-linux-amd64 e2e-darwin-amd64 e2e-windows-amd64.exe
......@@ -217,6 +218,19 @@ out/minikube-installer.exe: out/minikube-windows-amd64.exe
mv out/windows_tmp/minikube-installer.exe out/minikube-installer.exe
rm -rf out/windows_tmp
out/docker-machine-driver-hyperkit: $(shell $(HYPERKIT_FILES))
ifeq ($(MINIKUBE_BUILD_IN_DOCKER),y)
$(MINIKUBE_DOCKER_CMD) '$(MINIKUBE_ENV_darwin_DOCKER) $(MINIKUBE_ENV_darwin) go build -o $(BUILD_DIR)/docker-machine-driver-hyperkit k8s.io/minikube/cmd/drivers/hyperkit'
else
$(MINIKUBE_ENV_darwin) go build -o $(BUILD_DIR)/docker-machine-driver-hyperkit k8s.io/minikube/cmd/drivers/hyperkit
endif
.PHONY: install-hyperkit-driver
install-hyperkit-driver: out/docker-machine-driver-hyperkit
sudo cp out/docker-machine-driver-hyperkit $(HOME)/bin/docker-machine-driver-hyperkit
sudo chown root:wheel $(HOME)/bin/docker-machine-driver-hyperkit
sudo chmod u+s $(HOME)/bin/docker-machine-driver-hyperkit
.PHONY: check-release
check-release:
go test -v ./deploy/minikube/release_sanity_test.go -tags=release
......
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"github.com/docker/machine/libmachine/drivers/plugin"
"k8s.io/minikube/pkg/minikube/drivers/hyperkit"
)
func main() {
plugin.RegisterDriver(hyperkit.NewDriver("", ""))
}
......@@ -27,11 +27,15 @@
# Copy only the files we need to this workspace
mkdir -p out/ testdata/
gsutil cp gs://minikube-builds/${MINIKUBE_LOCATION}/minikube-${OS_ARCH} out/
gsutil cp gs://minikube-builds/${MINIKUBE_LOCATION}/docker-machine-driver-* out/
gsutil cp gs://minikube-builds/${MINIKUBE_LOCATION}/e2e-${OS_ARCH} out/
gsutil cp gs://minikube-builds/${MINIKUBE_LOCATION}/testdata/busybox.yaml testdata/
gsutil cp gs://minikube-builds/${MINIKUBE_LOCATION}/testdata/pvc.yaml testdata/
gsutil cp gs://minikube-builds/${MINIKUBE_LOCATION}/testdata/busybox-mount-test.yaml testdata/
# Add the out/ directory to the PATH, for using new drivers.
export PATH="$(pwd)/out/":$PATH
# Linux cleanup
virsh -c qemu:///system list --all \
| sed -n '3,$ p' \
......@@ -59,6 +63,12 @@ pgrep xhyve | xargs kill || true
# Set the executable bit on the e2e binary and out binary
chmod +x out/e2e-${OS_ARCH}
chmod +x out/minikube-${OS_ARCH}
chmod +x out/docker-machine-driver-*
if [ -e out/docker-machine-driver-hyperkit ]; then
sudo chown root:wheel out/docker-machine-driver-hyperkit || true
sudo chmod u+s out/docker-machine-driver-hyperkit || true
fi
MINIKUBE_WANTREPORTERRORPROMPT=False sudo ./out/minikube-${OS_ARCH} delete \
|| MINIKUBE_WANTREPORTERRORPROMPT=False ./out/minikube-${OS_ARCH} delete \
......
......@@ -27,7 +27,7 @@
set -e
set +x
for job in "OSX-Virtualbox" "OSX-XHyve" "Linux-Virtualbox" "Linux-KVM" "Linux-KVM-Alt" "Linux-None" "Windows-HyperV"; do
for job in "OSX-Virtualbox" "OSX-XHyve" "OSX-Hyperkit" "Linux-Virtualbox" "Linux-KVM" "Linux-KVM-Alt" "Linux-None" "Windows-HyperV"; do
target_url="https://storage.googleapis.com/minikube-builds/logs/${ghprbPullId}/${job}.txt"
curl "https://api.github.com/repos/kubernetes/minikube/statuses/${ghprbActualCommit}?access_token=$access_token" \
-H "Content-Type: application/json" \
......
#!/bin/bash
# Copyright 2016 The Kubernetes Authors All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This script runs the integration tests on an OSX machine for the Hyperkit Driver
# The script expects the following env variables:
# MINIKUBE_LOCATION: GIT_COMMIT from upstream build.
# COMMIT: Actual commit ID from upstream build
# EXTRA_BUILD_ARGS (optional): Extra args to be passed into the minikube integrations tests
# access_token: The Github API access token. Injected by the Jenkins credential provider.
set -e
OS_ARCH="darwin-amd64"
VM_DRIVER="hyperkit"
JOB_NAME="OSX-Hyperkit"
# Download files and set permissions
source common.sh
......@@ -31,6 +31,7 @@ journalctl -u localkube -n 500
${SUDO_PREFIX}cat $KUBECONFIG
cat $HOME/.kube/config
echo $PATH
docker ps
......
......@@ -391,6 +391,8 @@ func createHost(api libmachine.API, config MachineConfig) (*host.Host, error) {
driver = createHypervHost(config)
case "none":
driver = createNoneHost(config)
case "hyperkit":
driver = createHyperkitHost(config)
default:
glog.Exitf("Unsupported driver: %s\n", config.VMDriver)
}
......
......@@ -23,6 +23,7 @@ import (
"github.com/docker/machine/libmachine/drivers"
cfg "k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/minikube/drivers/hyperkit"
)
func createVMwareFusionHost(config MachineConfig) drivers.Driver {
......@@ -57,6 +58,21 @@ type xhyveDriver struct {
RawDisk bool
}
func createHyperkitHost(config MachineConfig) *hyperkit.Driver {
return &hyperkit.Driver{
BaseDriver: &drivers.BaseDriver{
MachineName: cfg.GetMachineName(),
StorePath: constants.GetMinipath(),
SSHUser: "docker",
},
Boot2DockerURL: config.Downloader.GetISOFileURI(config.MinikubeISO),
DiskSize: config.DiskSize,
Memory: config.Memory,
CPU: config.CPUs,
Cmdline: "loglevel=3 user=docker console=ttyS0 console=tty0 noembed nomodeset norestore waitusb=10 systemd.legacy_systemd_cgroup_controller=yes base host=" + cfg.GetMachineName(),
}
}
func createXhyveHost(config MachineConfig) *xhyveDriver {
useVirtio9p := !config.DisableDriverMounts
return &xhyveDriver{
......
......@@ -27,3 +27,7 @@ func createVMwareFusionHost(config MachineConfig) drivers.Driver {
func createXhyveHost(config MachineConfig) drivers.Driver {
panic("xhyve not supported")
}
func createHyperkitHost(config MachineConfig) drivers.Driver {
panic("hyperkit not supported")
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package hyperkit
import (
"io/ioutil"
"os"
"path/filepath"
"syscall"
"github.com/cloudflare/cfssl/log"
"github.com/docker/machine/libmachine/mcnutils"
)
func createDiskImage(sshKeyPath, diskPath string, diskSizeMb int) error {
tarBuf, err := mcnutils.MakeDiskImage(sshKeyPath)
if err != nil {
return err
}
file, err := os.OpenFile(diskPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer file.Close()
file.Seek(0, os.SEEK_SET)
if _, err := file.Write(tarBuf.Bytes()); err != nil {
return err
}
file.Close()
if err := os.Truncate(diskPath, int64(diskSizeMb*1000000)); err != nil {
return err
}
return nil
}
func fixPermissions(path string) error {
os.Chown(path, syscall.Getuid(), syscall.Getegid())
files, _ := ioutil.ReadDir(path)
for _, f := range files {
fp := filepath.Join(path, f.Name())
log.Debugf(fp)
if err := os.Chown(fp, syscall.Getuid(), syscall.Getegid()); err != nil {
return err
}
}
return nil
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package hyperkit
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"k8s.io/minikube/pkg/minikube/tests"
)
func Test_createDiskImage(t *testing.T) {
tmpdir := tests.MakeTempDir()
defer os.RemoveAll(tmpdir)
sshPath := filepath.Join(tmpdir, "ssh")
ioutil.WriteFile(sshPath, []byte("mysshkey"), 0644)
diskPath := filepath.Join(tmpdir, "disk")
sizeInMb := 100
sizeInBytes := int64(sizeInMb) * 1000000
if err := createDiskImage(sshPath, diskPath, sizeInMb); err != nil {
t.Errorf("createDiskImage() error = %v", err)
}
fi, err := os.Lstat(diskPath)
if err != nil {
t.Errorf("Lstat() error = %v", err)
}
if fi.Size() != sizeInBytes {
t.Errorf("Disk size is %v, want %v", fi.Size(), sizeInBytes)
}
}
// +build darwin
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package hyperkit
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"syscall"
"time"
"github.com/docker/machine/libmachine/drivers"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/mcnflag"
"github.com/docker/machine/libmachine/mcnutils"
"github.com/docker/machine/libmachine/ssh"
"github.com/docker/machine/libmachine/state"
hyperkit "github.com/moby/hyperkit/go"
"github.com/pborman/uuid"
vmnet "github.com/zchee/go-vmnet"
commonutil "k8s.io/minikube/pkg/util"
)
const (
isoFilename = "boot2docker.iso"
pidFileName = "hyperkit.pid"
machineFileName = "hyperkit.json"
)
type Driver struct {
*drivers.BaseDriver
Boot2DockerURL string
DiskSize int
CPU int
Memory int
Cmdline string
}
func NewDriver(hostName, storePath string) *Driver {
return &Driver{
BaseDriver: &drivers.BaseDriver{
SSHUser: "docker",
},
}
}
func (d *Driver) Create() error {
b2dutils := mcnutils.NewB2dUtils(d.StorePath)
if err := ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
return err
}
if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil {
return err
}
isoPath := d.ResolveStorePath(isoFilename)
if err := d.extractKernel(isoPath); err != nil {
return err
}
return d.Start()
}
// DriverName returns the name of the driver
func (d *Driver) DriverName() string {
return "hyperkit"
}
// GetCreateFlags returns the mcnflag.Flag slice representing the flags
// that can be set, their descriptions and defaults.
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
return nil
}
// GetSSHHostname returns hostname for use with ssh
func (d *Driver) GetSSHHostname() (string, error) {
return d.IPAddress, nil
}
// GetURL returns a Docker compatible host URL for connecting to this host
// e.g. tcp://1.2.3.4:2376
func (d *Driver) GetURL() (string, error) {
ip, err := d.GetIP()
if err != nil {
return "", err
}
return fmt.Sprintf("tcp://%s:2376", ip), nil
}
// GetState returns the state that the host is in (running, stopped, etc)
func (d *Driver) GetState() (state.State, error) {
pid := d.getPid()
if pid == 0 {
return state.Stopped, nil
}
p, err := os.FindProcess(pid)
if err != nil {
return state.Error, err
}
// Sending a signal of 0 can be used to check the existence of a process.
if err := p.Signal(syscall.Signal(0)); err != nil {
return state.Stopped, nil
}
if p == nil {
return state.Stopped, nil
}
return state.Running, nil
}
// Kill stops a host forcefully
func (d *Driver) Kill() error {
return d.sendSignal(syscall.SIGKILL)
}
// PreCreateCheck allows for pre-create operations to make sure a driver is ready for creation
func (d *Driver) PreCreateCheck() error {
return nil
}
// Remove a host
func (d *Driver) Remove() error {
s, err := d.GetState()
if err != nil || s == state.Error {
log.Infof("Error checking machine status: %s, assuming it has been removed already", err)
}
if s == state.Running {
if err := d.Stop(); err != nil {
return err
}
}
return nil
}
// Restart a host. This may just call Stop(); Start() if the provider does not
// have any special restart behaviour.
func (d *Driver) Restart() error {
for _, f := range []func() error{d.Stop, d.Start} {
if err := f(); err != nil {
return err
}
}
return nil
}
// SetConfigFromFlags configures the driver with the object that was returned
// by RegisterCreateFlags
func (d *Driver) SetConfigFromFlags(opts drivers.DriverOptions) error {
return nil
}
// Start a host
func (d *Driver) Start() error {
// TODO: handle different disk types.
diskPath := filepath.Join(d.ResolveStorePath("."), d.MachineName+".rawdisk")
if _, err := os.Stat(diskPath); os.IsNotExist(err) {
if err := createDiskImage(d.publicSSHKeyPath(), diskPath, d.DiskSize); err != nil {
return err
}
if err := fixPermissions(d.ResolveStorePath(".")); err != nil {
return err
}
}
h, err := hyperkit.New("", "", filepath.Join(d.StorePath, "machines", d.MachineName))
if err != nil {
return err
}
// TODO: handle the rest of our settings.
h.Kernel = d.ResolveStorePath("bzimage")
h.Initrd = d.ResolveStorePath("initrd")
h.VMNet = true
h.ISOImage = d.ResolveStorePath(isoFilename)
h.Console = hyperkit.ConsoleFile
h.CPUs = d.CPU
h.Memory = d.Memory
// Set UUID
h.UUID = uuid.NewUUID().String()
log.Infof("Generated UUID %s", h.UUID)
mac, err := vmnet.GetMACAddressFromUUID(h.UUID)
if err != nil {
return err
}
// Need to strip 0's
mac = trimMacAddress(mac)
log.Infof("Generated MAC %s", mac)
h.Disks = []hyperkit.DiskConfig{
{
Path: diskPath,
Size: d.DiskSize,
Driver: "virtio-blk",
},
}
log.Infof("Starting with cmdline: %s", d.Cmdline)
if err := h.Start(d.Cmdline); err != nil {
return err
}
getIP := func() error {
var err error
d.IPAddress, err = GetIPAddressByMACAddress(mac)
if err != nil {
return &commonutil.RetriableError{Err: err}
}
return nil
}
if err := commonutil.RetryAfter(30, getIP, 2*time.Second); err != nil {
return fmt.Errorf("IP address never found in dhcp leases file %v", err)
}
return nil
}
// Stop a host gracefully
func (d *Driver) Stop() error {
return d.sendSignal(syscall.SIGTERM)
}
func (d *Driver) extractKernel(isoPath string) error {
for _, f := range []struct {
pathInIso string
destPath string
}{
{"/boot/bzimage", "bzimage"},
{"/boot/initrd", "initrd"},
{"/isolinux/isolinux.cfg", "isolinux.cfg"},
} {
fullDestPath := d.ResolveStorePath(f.destPath)
if err := ExtractFile(isoPath, f.pathInIso, fullDestPath); err != nil {
return err
}
}
return nil
}
func (d *Driver) publicSSHKeyPath() string {
return d.GetSSHKeyPath() + ".pub"
}
func (d *Driver) sendSignal(s os.Signal) error {
pid := d.getPid()
proc, err := os.FindProcess(pid)
if err != nil {
return err
}
return proc.Signal(s)
}
func (d *Driver) getPid() int {
pidPath := d.ResolveStorePath(machineFileName)
f, err := os.Open(pidPath)
if err != nil {
log.Warnf("Error reading pid file: %s", err)
return 0
}
dec := json.NewDecoder(f)
config := hyperkit.HyperKit{}
if err := dec.Decode(&config); err != nil {
log.Warnf("Error decoding pid file: %s", err)
return 0
}
return config.Pid
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package hyperkit
import (
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"github.com/hooklift/iso9660"
)
func ExtractFile(isoPath, srcPath, destPath string) error {
iso, err := os.Open(isoPath)
defer iso.Close()
if err != nil {
return err
}
r, err := iso9660.NewReader(iso)
if err != nil {
return err
}
f, err := findFile(r, srcPath)
if err != nil {
return err
}
dst, err := os.Create(destPath)
defer dst.Close()
if err != nil {
return err
}
_, err = io.Copy(dst, f.Sys().(io.Reader))
return err
}
func ReadFile(isoPath, srcPath string) (string, error) {
iso, err := os.Open(isoPath)
defer iso.Close()
if err != nil {
return "", err
}
r, err := iso9660.NewReader(iso)
if err != nil {
return "", err
}
f, err := findFile(r, srcPath)
if err != nil {
return "", err
}
contents, err := ioutil.ReadAll(f.Sys().(io.Reader))
return string(contents), err
}
func findFile(r *iso9660.Reader, path string) (os.FileInfo, error) {
// Look through the ISO for a file with a matching path.
for f, err := r.Next(); err != io.EOF; f, err = r.Next() {
// For some reason file paths in the ISO sometimes contain a '.' character at the end, so strip that off.
if strings.TrimSuffix(f.Name(), ".") == path {
return f, nil
}
}
return nil, fmt.Errorf("unable to find file %s", path)
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package hyperkit
import (
"bufio"
"fmt"
"io"
"os"
"regexp"
"strings"
)
const (
DHCPLeasesFile = "/var/db/dhcpd_leases"
)
type DHCPEntry struct {
Name string
IPAddress string
HWAddress string
ID string
Lease string
}
func GetIPAddressByMACAddress(mac string) (string, error) {
return getIpAddressFromFile(mac, DHCPLeasesFile)
}
func getIpAddressFromFile(mac, path string) (string, error) {
file, err := os.Open(path)
if err != nil {
return "", err
}
defer file.Close()
dhcpEntries, err := parseDHCPdLeasesFile(file)
if err != nil {
return "", err
}
for _, dhcpEntry := range dhcpEntries {
if dhcpEntry.HWAddress == mac {
return dhcpEntry.IPAddress, nil
}
}
return "", fmt.Errorf("Could not find an IP address for %s", mac)
}
func parseDHCPdLeasesFile(file io.Reader) ([]DHCPEntry, error) {
var (
dhcpEntry *DHCPEntry
dhcpEntries []DHCPEntry
)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "{" {
dhcpEntry = new(DHCPEntry)
continue
} else if line == "}" {
dhcpEntries = append(dhcpEntries, *dhcpEntry)
continue
}
split := strings.SplitN(line, "=", 2)
if len(split) != 2 {
return nil, fmt.Errorf("invalid line in dhcp leases file: %s", line)
}
key, val := split[0], split[1]
switch key {
case "name":
dhcpEntry.Name = val
case "ip_address":
dhcpEntry.IPAddress = val
case "hw_address":
// The mac addresses have a '1,' at the start.
dhcpEntry.HWAddress = val[2:]
case "identifier":
dhcpEntry.ID = val
case "lease":
dhcpEntry.Lease = val
default:
return dhcpEntries, fmt.Errorf("Unable to parse line: %s", line)
}
}
return dhcpEntries, scanner.Err()
}
// trimMacAddress trimming "0" of the ten's digit
func trimMacAddress(rawUUID string) string {
re := regexp.MustCompile(`0([A-Fa-f0-9](:|$))`)
mac := re.ReplaceAllString(rawUUID, "$1")
return mac
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package hyperkit
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"k8s.io/minikube/pkg/minikube/tests"
)
var validLeases = []byte(`{
name=foo
ip_address=1.2.3.4
hw_address=1,a1:b2:c3:d4:e5:f6
identifier=1,a2:b3:c4:d5:e6:f7
lease=0x597e1267
}
{
name=bar
ip_address=192.168.64.3
hw_address=1,a4:b5:c6:d7:e8:f9
identifier=1,a0:b0:c0:d0:e0:f0
lease=0x597e1267
}
{
name=bar
ip_address=192.168.64.4
hw_address=1,a5:b6:c7:d8:e9:f1
identifier=1,a5:b6:c7:d8:e9:f1
lease=0x597e1268
}`)
func Test_getIpAddressFromFile(t *testing.T) {
tmpdir := tests.MakeTempDir()
defer os.RemoveAll(tmpdir)
dhcpFile := filepath.Join(tmpdir, "dhcp")
ioutil.WriteFile(dhcpFile, validLeases, 0644)
invalidFile := filepath.Join(tmpdir, "invalid")
ioutil.WriteFile(invalidFile, []byte("foo"), 0644)
type args struct {
mac string
path string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
"valid",
args{"a1:b2:c3:d4:e5:f6", dhcpFile},
"1.2.3.4",
false,
},
{
"duplicate",
args{"a4:b5:c6:d7:e8:f9", dhcpFile},
"192.168.64.3",
false,
},
{
"invalid",
args{"a1:b2:c3:d4:e5:f6", invalidFile},
"",
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := getIpAddressFromFile(tt.args.mac, tt.args.path)
if (err != nil) != tt.wantErr {
t.Errorf("getIpAddressFromFile() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("getIpAddressFromFile() = %v, want %v", got, tt.want)
}
})
}
}
language: go
go:
- 1.4
- 1.5
- 1.8
- tip
matrix:
allow_failures:
- go: tip
### Thanks for your interest in contributing to this project and for taking the time to read this guide.
## Code of conduct
*Taken from http://libvirt.org/governance.html with minor adjustments.*
The open source community covers people from a wide variety of countries, backgrounds and positions. This global diversity is a great strength for this project, but can also lead to communication issues, which may in turn cause unhappiness. To maximize happiness of the project community taken as a whole, all members (whether users, contributors or committers) are expected to abide by the project's code of conduct. At a high level the code can be summarized as "be excellent to each other". Expanding on this:
**Be respectful:** disagreements between people are to be expected and are usually the sign of healthy debate and engagement. Disagreements can lead to frustration and even anger for some members. Turning to personal insults, intimidation or threatening behavior does not improve the situation. Participants should thus take care to ensure all communications / interactions stay professional at all times.
**Be considerate:** remember that the community has members with a diverse background many of whom have English as a second language. What might appear impolite, may simply be a result of a lack of knowledge of the English language. Bear in mind that actions will have an impact on other community members and the project as a whole, so take potential consequences into account before pursuing a course of action.
**Be forgiving:** humans are fallible and as such prone to make mistakes and inexplicably change their positions at times. Don't assume that other members are acting with malicious intent. Be prepared to forgive people who make mistakes and assist each other in learning from them. Playing a blame game doesn't help anyone.
## Issues
* Before reporting an issue make sure you search first if anybody has already reported a similar issue and whether or not it has been fixed.
* Make sure your issue report sufficiently details the problem.
* Include code samples reproducing the issue.
* Please do not derail or troll issues. Keep the discussion on topic and respect the Code of conduct.
* Please do not open issues for personal support requests, use the mailing list instead.
* If you want to tackle any open issue, make sure you let people know you are working on it.
## Development workflow
Go is unlike any other language in that it forces a specific development workflow and project structure. Trying to fight it is useless, frustrating and time consuming. So, you better be prepare to adapt your workflow when contributing to Go projects.
### Prerequisites
* **Go**: To install Go please follow its installation guide at https://golang.org/doc/install
* **Git:**
* **Debian-based distros:** `apt-get install git-core`
* **OSX:** `brew install git`
### Pull Requests
* Please be generous describing your changes.
* Although it is highly suggested to include tests, they are not a hard requirement in order to get your contributions accepted.
* Keep pull requests small so core developers can review them quickly.
### Workflow for third-party code contributions
* In Github, fork `https://github.com/c4milo/gotoolkit` to your own account
* Get the package using "go get": `go get github.com/c4milo/gotoolkit`
* Move to where the package was cloned: `cd $GOPATH/src/github.com/c4milo/gotoolkit/`
* Add a git remote pointing to your own fork: `git remote add downstream git@github.com:<your_account>/gotoolkit.git`
* Create a branch for making your changes, then commit them.
* Push changes to downstream repository, this is your own fork: `git push <mybranch> downstream`
* In Github, from your fork, create the Pull Request and send it upstream.
* You are done.
### Workflow for core developers
Since core developers usually have access to the upstream repository, there is no need for having a workflow like the one for thid-party contributors.
* Get the package using "go get": `go get github.com/c4milo/gotoolkit`
* Create a branch for making your changes, then commit them.
* Push changes to the repository: `git push origin <mybranch>`
* In Github, create the Pull Request from your branch to master.
* Before merging into master, wait for at least two developers to code review your contribution.
## Resources
* **Art of Computer Programming:** http://www-cs-faculty.stanford.edu/~uno/news.html
* **Algorithm Design Manual (2nd edition):** http://www.amazon.com/Algorithm-Design-Manual-Steve-Skiena/dp/0387948600
* **Algorithms (4th Edition):** http://www.amazon.com/Algorithms-4th-Robert-Sedgewick/dp/032157351X/ref=sr_1_2?s=books&ie=UTF8&qid=1410027698&sr=1-2
* **Introduction to Algorithms, 3rd Edition:** http://www.amazon.com/Introduction-Algorithms-3rd-Thomas-Cormen/dp/0262033844/ref=sr_1_1?s=books&ie=UTF8&qid=1410027698&sr=1-1
* **Cracking the Coding Interview: 150 Programming Questions and Solutions:** http://www.amazon.com/Cracking-Coding-Interview-Programming-Questions/dp/098478280X/ref=pd_bxgy_b_img_y
* **CS 97SI: Introduction to Competitive Programming Contests:** http://web.stanford.edu/class/cs97si/
* **Algorithm tutorials at Topcoder:** http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=alg_index
* **CS 161 - Design and Analysis of Algorithms:** http://openclassroom.stanford.edu/MainFolder/CoursePage.php?course=IntroToAlgorithms
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
# Go Toolkit
[![GoDoc](https://godoc.org/github.com/c4milo/gotoolkit?status.svg)](https://godoc.org/github.com/c4milo/gotoolkit)
[![Build Status](https://travis-ci.org/c4milo/gotoolkit.svg?branch=master)](https://travis-ci.org/c4milo/gotoolkit)
A set of algorithms and data structures that are not part of the standard Go packages and that we need
on a daily basis.
## Algorithms
* **SliceStack**
* **ListStack**
* **ListQueue**
* **SliceQueue**
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package gotoolkit
import "errors"
// Queue defines an interface for implementing Queue data structures.
type Queue interface {
Enqueue(item interface{})
Dequeue() (interface{}, error)
Peek() (interface{}, error)
IsEmpty() bool
Size() uint64
}
// ListQueue implements a queue backed by a linked list, it may be faster than SliceQueue
// but consumes more memory and has worse cache locality than SliceQueue
type ListQueue struct {
first *node
last *node
size uint64
}
// Enqueue adds an element to the end of the queue.
func (q *ListQueue) Enqueue(item interface{}) {
last := new(node)
last.item = item
if q.IsEmpty() {
q.first = last
} else {
oldLast := q.last
oldLast.next = last
}
q.last = last
q.size++
}
// Dequeue removes the first element from the queue.
func (q *ListQueue) Dequeue() (interface{}, error) {
if q.IsEmpty() {
q.last = nil
return nil, errors.New("unable to dequeue element, queue is empty")
}
item := q.first.item
q.first = q.first.next
q.size--
return item, nil
}
// Peek returns the first element in the queue without removing it.
func (q *ListQueue) Peek() (interface{}, error) {
if q.IsEmpty() {
return nil, errors.New("unable to peek element, queue is empty")
}
return q.first.item, nil
}
// IsEmpty returns whether the queue is empty or not.
func (q *ListQueue) IsEmpty() bool {
return q.first == nil
}
// Size returns the number of elements in the queue.
func (q *ListQueue) Size() uint64 {
return q.size
}
// SliceQueue implements a queue backed by a growing slice. Useful for memory
// constrained environments. It also has better cache locality than ListQueue
type SliceQueue struct {
size uint64
backing []interface{}
}
// Enqueue adds an element to the end of the queue.
func (s *SliceQueue) Enqueue(item interface{}) {
s.size++
s.backing = append(s.backing, item)
}
// Peek returns the first element of the queue without removing it from the queue.
func (s *SliceQueue) Peek() (interface{}, error) {
if s.IsEmpty() {
return nil, errors.New("unable to peek element, queue is empty")
}
return s.backing[0], nil
}
// Dequeue removes and return the first element from the queue.
func (s *SliceQueue) Dequeue() (interface{}, error) {
if s.IsEmpty() {
return nil, errors.New("unable to dequeue element, queue is empty")
}
item := s.backing[0]
// https://github.com/golang/go/wiki/SliceTricks
s.backing = append(s.backing[:0], s.backing[1:]...)
s.size--
return item, nil
}
// IsEmpty returns whether or not the queue is empty.
func (s *SliceQueue) IsEmpty() bool {
return len(s.backing) == 0
}
// Size returns the number of elements in the queue.
func (s *SliceQueue) Size() uint64 {
return s.size
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package gotoolkit
import "errors"
// Stack defines an interface for implementing Stack data structures.
type Stack interface {
Push(item interface{})
Pop() (interface{}, error)
Peek() (interface{}, error)
IsEmpty() bool
Size() uint64
}
type node struct {
item interface{}
next *node
}
// ListStack is a stack backed by a linked list, it may be faster than SliceStack
// but consumes more memory and has worse cache locality than SliceQueue.
type ListStack struct {
first *node
size uint64
}
// Push adds an element to the top of the stack.
func (s *ListStack) Push(item interface{}) {
oldFirst := s.first
s.first = new(node)
s.first.item = item
s.first.next = oldFirst
s.size++
}
// Peek returns the latest added element without removing it from the stack.
func (s *ListStack) Peek() (interface{}, error) {
if s.IsEmpty() {
return nil, errors.New("unable to peek element, stack is empty")
}
return s.first.item, nil
}
// Pop removes and return the latest added element from the stack.
func (s *ListStack) Pop() (interface{}, error) {
if s.IsEmpty() {
return nil, errors.New("unable to pop element, stack is empty")
}
item := s.first.item
s.first = s.first.next
s.size--
return item, nil
}
// IsEmpty returns whether or not the stack is empty.
func (s *ListStack) IsEmpty() bool {
return s.first == nil
}
// Size returns the number of elements in the stack.
func (s *ListStack) Size() uint64 {
return s.size
}
// SliceStack implements a stack backed by a growing slice. Useful for memory
// constrained environments. It also has better cache locality.
type SliceStack struct {
size uint64
backing []interface{}
}
// Push adds an element to the top of the stack.
func (s *SliceStack) Push(item interface{}) {
s.size++
s.backing = append(s.backing, item)
}
// Peek returns the latest added element without removing it from the stack.
func (s *SliceStack) Peek() (interface{}, error) {
if s.IsEmpty() {
return nil, errors.New("unable to peek element, stack is empty")
}
return s.backing[s.size-1], nil
}
// Pop removes and return the latest added element from the stack.
func (s *SliceStack) Pop() (interface{}, error) {
if s.IsEmpty() {
return nil, errors.New("unable to pop element, stack is empty")
}
s.size--
item := s.backing[s.size]
s.backing = s.backing[0:s.size]
return item, nil
}
// IsEmpty returns whether or not the stack is empty.
func (s *SliceStack) IsEmpty() bool {
return len(s.backing) == 0
}
// Size returns the number of elements in the stack.
func (s *SliceStack) Size() uint64 {
return s.size
}
sudo: false
language: go
go:
- 1.4
- 1.5
- 1.8
- tip
matrix:
allow_failures:
- go: tip
install:
- make deps
### Thanks for your interest in contributing to this project and for taking the time to read this guide.
## Code of conduct
*Taken from http://libvirt.org/governance.html with minor adjustments.*
The open source community covers people from a wide variety of countries, backgrounds and positions. This global diversity is a great strength for this project, but can also lead to communication issues, which may in turn cause unhappiness. To maximize happiness of the project community taken as a whole, all members (whether users, contributors or committers) are expected to abide by the project's code of conduct. At a high level the code can be summarized as "be excellent to each other". Expanding on this:
**Be respectful:** disagreements between people are to be expected and are usually the sign of healthy debate and engagement. Disagreements can lead to frustration and even anger for some members. Turning to personal insults, intimidation or threatening behavior does not improve the situation. Participants should thus take care to ensure all communications / interactions stay professional at all times.
**Be considerate:** remember that the community has members with a diverse background many of whom have English as a second language. What might appear impolite, may simply be a result of a lack of knowledge of the English language. Bear in mind that actions will have an impact on other community members and the project as a whole, so take potential consequences into account before pursuing a course of action.
**Be forgiving:** humans are fallible and as such prone to make mistakes and inexplicably change their positions at times. Don't assume that other members are acting with malicious intent. Be prepared to forgive people who make mistakes and assist each other in learning from them. Playing a blame game doesn't help anyone.
## Issues
* Before reporting an issue make sure you search first if anybody has already reported a similar issue and whether or not it has been fixed.
* Make sure your issue report sufficiently details the problem.
* Include code samples reproducing the issue.
* Please do not derail or troll issues. Keep the discussion on topic and respect the Code of conduct.
* If you want to tackle any open issue, make sure you let people know you are working on it.
## Development workflow
Go is unlike any other language in that it forces a specific development workflow and project structure. Trying to fight it is useless, frustrating and time consuming. So, you better be prepare to adapt your workflow when contributing to Go projects.
### Prerequisites
* **Go**: To install Go please follow its installation guide at https://golang.org/doc/install
* **Git:** brew install git
### Pull Requests
* Please be generous describing your changes.
* Although it is highly suggested to include tests, they are not a hard requirement in order to get your contributions accepted.
* Keep pull requests small so core developers can review them quickly.
### Workflow for third-party code contributions
* In Github, fork `https://github.com/hooklift/iso9660.git` to your own account
* Get the package using "go get": `go get github.com/hooklift/iso9660`
* Move to where the package was cloned: `cd $GOPATH/src/github.com/hooklift/iso9660`
* Add a git remote pointing to your own fork: `git remote add downstream git@github.com:<your_account>/iso9660.git`
* Create a branch for making your changes, then commit them.
* Push changes to downstream repository, this is your own fork: `git push <mybranch> downstream`
* In Github, from your fork, create the Pull Request and send it upstream.
* You are done.
### Workflow for core developers
Since core developers usually have access to the upstream repository, there is no need for having a workflow like the one for thid-party contributors.
* Get the package using "go get": `go get github.com/hooklift/iso9660`
* Create a branch for making your changes, then commit them.
* Push changes to the repository: `git push origin <mybranch>`
* In Github, create the Pull Request from your branch to master.
* Before merging into master, wait for at least two developers to code review your contribution.
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
GHACCOUNT := hooklift
NAME := iso9660
VERSION := v1.0.0
include common.mk
deps:
go get github.com/c4milo/github-release
go get github.com/mitchellh/gox
go get github.com/docopt/docopt-go
go get github.com/c4milo/gotoolkit
go get github.com/hooklift/assert
# iso9660
[![GoDoc](https://godoc.org/github.com/hooklift/iso9660?status.svg)](https://godoc.org/github.com/hooklift/iso9660)
[![Build Status](https://travis-ci.org/hooklift/iso9660.svg?branch=master)](https://travis-ci.org/hooklift/iso9660)
Go library and CLI to extract data from ISO9660 images.
### CLI
```
$ ./iso9660
Usage:
iso9660 <image-path> <destination-path>
iso9660 -h | --help
iso9660 --version
```
### Library
An example on how to use the library to extract files and directories from an ISO image can be found in our CLI source code at:
https://github.com/hooklift/iso9660/blob/master/cmd/iso9660/main.go
### Not supported
* Reading files recorded in interleave mode
* Multi-extent or reading individual files larger than 4GB
* Joliet extensions, meaning that file names longer than 32 characters are going to be truncated. Unicode characters are not going to be properly decoded either.
* Multisession extension
* Rock Ridge extension, making unable to return recorded POSIX permissions, timestamps as well as owner and group for files and directories.
PLATFORM := $(shell go env | grep GOHOSTOS | cut -d '"' -f 2)
ARCH := $(shell go env | grep GOARCH | cut -d '"' -f 2)
BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
LDFLAGS := -ldflags "-X main.Version=$(VERSION) -X main.Name=$(NAME)"
test:
go test ./...
build:
go build -o build/$(NAME) $(LDFLAGS) cmd/$(NAME)/main.go
install:
go install $(LDFLAGS)
compile:
@rm -rf build/
@gox $(LDFLAGS) \
-osarch="darwin/amd64" \
-os="linux" \
-os="solaris" \
-os="freebsd" \
-os="windows" \
-output "build/$(NAME)_$(VERSION)_{{.OS}}_{{.Arch}}/$(NAME)" \
./...
dist: compile
$(eval FILES := $(shell ls build))
@rm -rf dist && mkdir dist
@for f in $(FILES); do \
(cd $(shell pwd)/build/$$f && tar -cvzf ../../dist/$$f.tar.gz *); \
(cd $(shell pwd)/dist && shasum -a 512 $$f.tar.gz > $$f.sha512); \
echo $$f; \
done
release: dist
@latest_tag=$$(git describe --tags `git rev-list --tags --max-count=1`); \
comparison="$$latest_tag..HEAD"; \
if [ -z "$$latest_tag" ]; then comparison=""; fi; \
changelog=$$(git log $$comparison --oneline --no-merges); \
github-release $(GHACCOUNT)/$(NAME) $(VERSION) $(BRANCH) "**Changelog**<br/>$$changelog" 'dist/*'; \
git pull
.PHONY: test build install compile deps dist release
// Package iso9660 implements ECMA-119 standard, also known as ISO 9660.
//
// References:
//
// * https://en.wikipedia.org/wiki/ISO_9660
//
// * http://alumnus.caltech.edu/~pje/iso9660.html
//
// * http://users.telenet.be/it3.consultants.bvba/handouts/ISO9960.html
//
// * http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-119.pdf
//
// * http://www.drdobbs.com/database/inside-the-iso-9660-filesystem-format/184408899
//
// * http://www.cdfs.com
//
// * http://wiki.osdev.org/ISO_9660
package iso9660
import (
"io"
"os"
"strings"
"time"
)
// File represents a concrete implementation of os.FileInfo interface for
// accessing ISO 9660 file data
type File struct {
DirectoryRecord
fileID string
// We have the raw image here only to be able to access file extents
image *os.File
}
// Name returns the file's name.
func (f *File) Name() string {
name := strings.Split(f.fileID, ";")[0]
return strings.ToLower(name)
}
// Size returns the file size in bytes
func (f *File) Size() int64 {
return int64(f.ExtentLengthBE)
}
// Mode returns file's mode and permissions bits. Since we don't yet support
// Rock Ridge extensions we cannot extract POSIX permissions and the rest of the
// normal metadata. So, right we return 0740 for directories and 0640 for files.
func (f *File) Mode() os.FileMode {
if f.IsDir() {
return os.FileMode(0740)
}
return os.FileMode(0640)
}
// ModTime returns file's modification time.
func (f *File) ModTime() time.Time {
return time.Now()
}
// IsDir tells whether the file is a directory or not.
func (f *File) IsDir() bool {
if (f.FileFlags & 2) == 2 {
return true
}
return false
}
// Sys returns io.Reader instance pointing to the file's content if it is not a directory, nil otherwise.
func (f *File) Sys() interface{} {
if f.IsDir() {
return nil
}
return io.NewSectionReader(f.image, int64(f.ExtentLocationBE*sectorSize), int64(f.ExtentLengthBE))
}
const (
bootRecord = 0
primaryVol = 1
supplementaryVol = 2
volPartition = 3
volSetTerminator = 255
// System area goes from sectors 0x00 to 0x0F. Volume descriptors can be
// found starting at sector 0x10
dataAreaSector = 0x10
sectorSize = 2048
)
// VolumeDescriptor identify the volume, the partitions recorded on the volume,
// the volume creator(s), certain attributes of the volume, the location of
// other recorded descriptors and the version of the standard which applies
// to the volume descriptor.
//
// When preparing to mount a CD, your first action will be reading the volume
// descriptors (specifically, you will be looking for the Primary Volume Descriptor).
// Since sectors 0x00-0x0F of the CD are reserved as System Area, the Volume
// Descriptors can be found starting at sector 0x10.
type VolumeDescriptor struct {
// 0: BootRecord
// 1: Primary Volume Descriptor
// 2: Supplementary Volume Descriptor
// 3: Volume Partition Descriptor
// 4-254: Reserved
// 255: Volume Descriptor Set Terminator
Type byte
// Always "CD001".
StandardID [5]byte
//Volume Descriptor Version (0x01).
Version byte
}
// BootRecord identifies a system which can recognize and act upon the content
// of the field reserved for boot system use in the Boot Record, and shall
// contain information which is used to achieve a specific state for a system or
// for an application.
type BootRecord struct {
VolumeDescriptor
SystemID [32]byte
ID [32]byte
SystemUse [1977]byte
}
// Terminator indicates the termination of a Volume Descriptor Set.
type Terminator struct {
VolumeDescriptor
// All bytes of this field are set to (00).
Reserved [2041]byte
}
// PrimaryVolumePart1 represents the Primary Volume Descriptor first half, before the
// root directory record. We are only reading big-endian values so placeholders
// are used for little-endian ones.
type PrimaryVolumePart1 struct {
VolumeDescriptor
// Unused
_ byte // 00
// The name of the system that can act upon sectors 0x00-0x0F for the volume.
SystemID [32]byte
// Identification of this volume.
ID [32]byte
//Unused2
_ [8]byte
// Amount of data available on the CD-ROM. Ignores little-endian order.
// Takes big-endian encoded value.
VolumeSpaceSizeLE int32
VolumeSpaceSizeBE int32
Unused2 [32]byte
// The size of the set in this logical volume (number of disks). Ignores
// little-endian order. Takes big-endian encoded value.
VolumeSetSizeLE int16
VolumeSetSizeBE int16
// The number of this disk in the Volume Set. Ignores little-endian order.
// Takes big-endian encoded value.
VolumeSeqNumberLE int16
VolumeSeqNumberBE int16
// The size in bytes of a logical block. NB: This means that a logical block
// on a CD could be something other than 2 KiB!
LogicalBlkSizeLE int16
LogicalBlkSizeBE int16
// The size in bytes of the path table. Ignores little-endian order.
// Takes big-endian encoded value.
PathTableSizeLE int32
PathTableSizeBE int32
// LBA location of the path table. The path table pointed to contains only
// little-endian values.
LocPathTableLE int32
// LBA location of the optional path table. The path table pointed to contains
// only little-endian values. Zero means that no optional path table exists.
LocOptPathTableLE int32
// LBA location of the path table. The path table pointed to contains
// only big-endian values.
LocPathTableBE int32
// LBA location of the optional path table. The path table pointed to contains
// only big-endian values. Zero means that no optional path table exists.
LocOptPathTableBE int32
}
// DirectoryRecord describes the characteristics of a file or directory,
// beginning with a length octet describing the size of the entire entry.
// Entries themselves are of variable length, up to 255 octets in size.
// Attributes for the file described by the directory entry are stored in the
// directory entry itself (unlike UNIX).
// The root directory entry is a variable length object, so that the name can be of variable length.
//
// Important: before each entry there can be "fake entries" to support the Long File Name.
//
// Even if a directory spans multiple sectors, the directory entries are not
// permitted to cross the sector boundary (unlike the path table). Where there
// is not enough space to record an entire directory entry at the end of a sector,
// that sector is zero-padded and the next consecutive sector is used.
// Unfortunately, the date/time format is different from that used in the Primary
// Volume Descriptor.
type DirectoryRecord struct {
// Extended Attribute Record length, stored at the beginning of
// the file's extent.
ExtendedAttrLen byte
// Location of extent (Logical Block Address) in both-endian format.
ExtentLocationLE uint32
ExtentLocationBE uint32
// Data length (size of extent) in both-endian format.
ExtentLengthLE uint32
ExtentLengthBE uint32
// Date and the time of the day at which the information in the Extent
// described by the Directory Record was recorded.
RecordedTime [7]byte
// If this Directory Record identifies a directory then bit positions 2, 3
// and 7 shall be set to ZERO. If no Extended Attribute Record is associated
// with the File Section identified by this Directory Record then bit
// positions 3 and 4 shall be set to ZERO. -- 9.1.6
FileFlags byte
// File unit size for files recorded in interleaved mode, zero otherwise.
FileUnitSize byte
// Interleave gap size for files recorded in interleaved mode, zero otherwise.
InterleaveGapSize byte
// Volume sequence number - the volume that this extent is recorded on, in
// 16 bit both-endian format.
VolumeSeqNumberLE uint16
VolumeSeqNumberBE uint16
// Length of file identifier (file name). This terminates with a ';'
// character followed by the file ID number in ASCII coded decimal ('1').
FileIDLength byte
// The interpretation of this field depends as follows on the setting of the
// Directory bit of the File Flags field. If set to ZERO, it shall mean:
//
// − The field shall specify an identification for the file.
// − The characters in this field shall be d-characters or d1-characters, SEPARATOR 1, SEPARATOR 2.
// − The field shall be recorded as specified in 7.5. If set to ONE, it shall mean:
// − The field shall specify an identification for the directory.
// − The characters in this field shall be d-characters or d1-characters, or only a (00) byte, or only a (01) byte.
// − The field shall be recorded as specified in 7.6.
// fileID string
}
// PrimaryVolumePart2 represents the Primary Volume Descriptor half after the
// root directory record.
type PrimaryVolumePart2 struct {
// Identifier of the volume set of which this volume is a member.
VolumeSetID [128]byte
// The volume publisher. For extended publisher information, the first byte
// should be 0x5F, followed by the filename of a file in the root directory.
// If not specified, all bytes should be 0x20.
PublisherID [128]byte
// The identifier of the person(s) who prepared the data for this volume.
// For extended preparation information, the first byte should be 0x5F,
// followed by the filename of a file in the root directory. If not specified,
// all bytes should be 0x20.
DataPreparerID [128]byte
// Identifies how the data are recorded on this volume. For extended information,
// the first byte should be 0x5F, followed by the filename of a file in the root
// directory. If not specified, all bytes should be 0x20.
AppID [128]byte
// Filename of a file in the root directory that contains copyright
// information for this volume set. If not specified, all bytes should be 0x20.
CopyrightFileID [37]byte
// Filename of a file in the root directory that contains abstract information
// for this volume set. If not specified, all bytes should be 0x20.
AbstractFileID [37]byte
// Filename of a file in the root directory that contains bibliographic
// information for this volume set. If not specified, all bytes should be 0x20.
BibliographicFileID [37]byte
// The date and time of when the volume was created.
CreationTime [17]byte
// The date and time of when the volume was modified.
ModificationTime [17]byte
// The date and time after which this volume is considered to be obsolete.
// If not specified, then the volume is never considered to be obsolete.
ExpirationTime [17]byte
// The date and time after which the volume may be used. If not specified,
// the volume may be used immediately.
EffectiveTime [17]byte
// The directory records and path table version (always 0x01).
FileStructVersion byte
// Reserved. Always 0x00.
_ byte
// Contents not defined by ISO 9660.
AppUse [512]byte
// Reserved by ISO.
_ [653]byte
}
// PrimaryVolume descriptor acts much like the superblock of the UNIX filesystem, providing
// details on the ISO-9660 compliant portions of the disk. While we can have
// many kinds of filesystems on a single ISO-9660 CD-ROM, we can have only one
// ISO-9660 file structure (found as the primary volume-descriptor type).
//
// Directory entries are successively stored within this region. Evaluation of
// the ISO 9660 filenames is begun at this location. The root directory is stored
// as an extent, or sequential series of sectors, that contains each of the
// directory entries appearing in the root.
//
// Since ISO 9660 works by segmenting the CD-ROM into logical blocks, the size
// of these blocks is found in the primary volume descriptor as well.
type PrimaryVolume struct {
PrimaryVolumePart1
DirectoryRecord DirectoryRecord
PrimaryVolumePart2
}
// SupplementaryVolume is used by Joliet.
type SupplementaryVolume struct {
VolumeDescriptor
Flags int `struc:"int8"`
SystemID string `struc:"[32]byte"`
ID string `struc:"[32]byte"`
Unused byte
VolumeSpaceSize int `struc:"int32"`
EscapeSequences string `struc:"[32]byte"`
VolumeSetSize int `struc:"int16"`
VolumeSeqNumber int `struc:"int16"`
LogicalBlkSize int `struc:"int16"`
PathTableSize int `struc:"int32"`
LocLPathTable int `struc:"int32"`
LocOptLPathTable int `struc:"int32"`
LocMPathTable int `struc:"int32"`
LocOptMPathTable int `struc:"int32"`
RootDirRecord DirectoryRecord
VolumeSetID string `struc:"[128]byte"`
PublisherID string `struc:"[128]byte"`
DataPreparerID string `struc:"[128]byte"`
AppID string `struc:"[128]byte"`
CopyrightFileID string `struc:"[37]byte"`
AbstractFileID string `struc:"[37]byte"`
BibliographicFileID string `struc:"[37]byte"`
CreationTime Timestamp
ModificationTime Timestamp
ExpirationTime Timestamp
EffectiveTime Timestamp
FileStructVersion int `struc:"int8"`
Reserved byte
AppData [512]byte
Reserved2 byte
}
// PartitionVolume ...
type PartitionVolume struct {
VolumeDescriptor
Unused byte
SystemID string `struc:"[32]byte"`
ID string `struc:"[32]byte"`
Location int `struc:"int8"`
Size int `struc:"int8"`
SystemUse [1960]byte
}
// Timestamp ...
type Timestamp struct {
Year int `struc:"[4]byte"`
Month int `struc:"[2]byte"`
DayOfMonth int `struc:"[2]byte"`
Hour int `struc:"[2]byte"`
Minute int `struc:"[2]byte"`
Second int `struc:"[2]byte"`
Millisecond int `struc:"[2]byte"`
GMTOffset int `struc:"uint8"`
}
// ExtendedAttrRecord are simply a way to extend the attributes of files.
// Since attributes vary according to the user, most everyone has a different
// opinion on what a file attribute should specify.
type ExtendedAttrRecord struct {
OwnerID int `struc:"int16"`
GroupID int `struc:"int16"`
Permissions int `struc:"int16"`
CreationTime Timestamp
ModificationTime Timestamp
ExpirationTime Timestamp
// Specifies the date and the time of the day at which the information in
// the file may be used. If the date and time are not specified then the
// information may be used at once.
EffectiveTime Timestamp
Format int `struc:"uint8"`
Attributes int `struc:"uint8"`
Length int `struc:"int16"`
SystemID string `struc:"[32]byte"`
SystemUse [64]byte
Version int `struc:"uint8"`
EscapeSeqLength int `struc:"uint8"`
Reserved [64]byte
AppUseLength int `struc:"int16,sizeof=AppUse"`
AppUse []byte
EscapeSequences []byte `struc:"sizefrom=AppUseLength"`
}
// PathTable contains a well-ordered sequence of records describing every
// directory extent on the CD. There are some exceptions with this: the Path
// Table can only contain 65536 records, due to the length of the "Parent Directory Number" field.
// If there are more than this number of directories on the disc, some CD
// authoring software will ignore this limit and create a non-compliant
// CD (this applies to some earlier versions of Nero, for example). If your
// file system uses the path table, you should be aware of this possibility.
// Windows uses the Path Table and will fail with such non-compliant
// CD's (additional nodes exist but appear as zero-byte). Linux, which uses
// the directory tables is not affected by this issue. The location of the path
// tables can be found in the Primary Volume Descriptor. There are two table types
// - the L-Path table (relevant to x86) and the M-Path table. The only
// difference between these two tables is that multi-byte values in the L-Table
// are LSB-first and the values in the M-Table are MSB-first.
//
// The path table is in ascending order of directory level and is alphabetically
// sorted within each directory level.
type PathTable struct {
DirIDLength int `struc:"uint8,sizeof=DirName"`
ExtendedAttrsLen int `struc:"uint8"`
// Number the Logical Block Number of the first Logical Block allocated to
// the Extent in which the directory is recorded.
// This is in a different format depending on whether this is the L-Table or
// M-Table (see explanation above).
ExtentLocation int `struc:"int32"`
// Number of record for parent directory (or 1 for the root directory), as a
// word; the first record is number 1, the second record is number 2, etc.
// Directory number of parent directory (an index in to the path table).
// This is the field that limits the table to 65536 records.
ParentDirNumber int `struc:"int16"`
// Directory Identifier (name) in d-characters.
DirName string
}
package iso9660
import (
"encoding/binary"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"github.com/c4milo/gotoolkit"
)
var (
// ErrInvalidImage is returned when an attempt to unpack the image Primary Volume Descriptor failed or
// when the end of the image was reached without finding a primary volume descriptor
ErrInvalidImage = func(err error) error { return fmt.Errorf("invalid-iso9660-image: %s", err) }
// ErrCorruptedImage is returned when a seek operation, on the image, failed.
ErrCorruptedImage = func(err error) error { return fmt.Errorf("corrupted-image: %s", err) }
)
// Reader defines the state of the ISO9660 image reader. It needs to be instantiated
// from its constructor.
type Reader struct {
// File descriptor to the opened ISO image
image *os.File
// Copy of unencoded Primary Volume Descriptor
pvd PrimaryVolume
// Queue used to walk through file system iteratively
queue gotoolkit.Queue
// Current sector
sector uint32
// Current bytes read from current sector.
read uint32
}
// NewReader creates a new ISO 9660 image reader.
func NewReader(rs *os.File) (*Reader, error) {
// Starts reading from image data area
sector := dataAreaSector
// Iterates over volume descriptors until it finds the primary volume descriptor
// or an error condition.
for {
offset, err := rs.Seek(int64(sector*sectorSize), os.SEEK_SET)
if err != nil {
return nil, ErrCorruptedImage(err)
}
var volDesc VolumeDescriptor
if err := binary.Read(rs, binary.BigEndian, &volDesc); err != nil {
return nil, ErrCorruptedImage(err)
}
if volDesc.Type == primaryVol {
// backs up to the beginning of the sector again in order to unpack
// the entire primary volume descriptor more easily.
if _, err := rs.Seek(offset, os.SEEK_SET); err != nil {
return nil, ErrCorruptedImage(err)
}
reader := new(Reader)
reader.image = rs
reader.queue = new(gotoolkit.SliceQueue)
if err := reader.unpackPVD(); err != nil {
return nil, ErrCorruptedImage(err)
}
return reader, nil
}
if volDesc.Type == volSetTerminator {
return nil, ErrInvalidImage(errors.New("Volume Set Terminator reached. A Primary Volume Descriptor was not found."))
}
sector++
}
}
// Skip skips the given number of directory records.
func (r *Reader) Skip(n int) error {
var drecord File
var len byte
var err error
for i := 0; i < n; i++ {
if len, err = r.unpackDRecord(&drecord); err != nil {
return err
}
r.read += uint32(len)
}
return nil
}
// Next moves onto the next directory record present in the image.
// It does not use the Path Table since the goal is to read everything
// from the ISO image.
func (r *Reader) Next() (os.FileInfo, error) {
if r.queue.IsEmpty() {
return nil, io.EOF
}
// We only dequeue the directory when it does not contain more children
// or when it is empty and there is no children to iterate over.
item, err := r.queue.Peek()
if err != nil {
panic(err)
}
f := item.(File)
if r.sector == 0 {
r.sector = f.ExtentLocationBE
_, err := r.image.Seek(int64(r.sector*sectorSize), os.SEEK_SET)
if err != nil {
return nil, ErrCorruptedImage(err)
}
// Skips . and .. directories
if err = r.Skip(2); err != nil {
return nil, ErrCorruptedImage(err)
}
}
var drecord File
var len byte
if (r.read % sectorSize) == 0 {
r.sector++
_, err := r.image.Seek(int64(r.sector*sectorSize), os.SEEK_SET)
if err != nil {
return nil, ErrCorruptedImage(err)
}
}
if len, err = r.unpackDRecord(&drecord); err != nil && err != io.EOF {
return nil, ErrCorruptedImage(err)
}
r.read += uint32(len)
if err == io.EOF {
// directory record is empty, sector space wasted, move onto next sector.
rsize := (sectorSize - (r.read % sectorSize))
buf := make([]byte, rsize)
if err := binary.Read(r.image, binary.BigEndian, buf); err != nil {
return nil, ErrCorruptedImage(err)
}
r.read += rsize
}
// If there is no more entries in the current directory, dequeue it.
if r.read >= f.ExtentLengthBE {
r.read = 0
r.sector = 0
r.queue.Dequeue()
}
// End of directory listing, drecord is empty so we don't bother
// to return it and keep iterating to look for the next actual
// directory or file.
if drecord.fileID == "" {
return r.Next()
}
parent := f.Name()
if parent == "\x00" {
parent = "/"
}
drecord.fileID = filepath.Join(parent, drecord.fileID)
if drecord.IsDir() {
r.queue.Enqueue(drecord)
} else {
drecord.image = r.image
}
return &drecord, nil
}
// unpackDRecord unpacks directory record bits into Go's struct
func (r *Reader) unpackDRecord(f *File) (byte, error) {
// Gets the directory record length
var len byte
if err := binary.Read(r.image, binary.BigEndian, &len); err != nil {
return len, ErrCorruptedImage(err)
}
if len == 0 {
return len + 1, io.EOF
}
// Reads directory record into Go struct
var drecord DirectoryRecord
if err := binary.Read(r.image, binary.BigEndian, &drecord); err != nil {
return len, ErrCorruptedImage(err)
}
f.DirectoryRecord = drecord
// Gets the name
name := make([]byte, drecord.FileIDLength)
if err := binary.Read(r.image, binary.BigEndian, name); err != nil {
return len, ErrCorruptedImage(err)
}
f.fileID = string(name)
// Padding field as per section 9.1.12 in ECMA-119
if (drecord.FileIDLength % 2) == 0 {
var zero byte
if err := binary.Read(r.image, binary.BigEndian, &zero); err != nil {
return len, ErrCorruptedImage(err)
}
}
// System use field as per section 9.1.13 in ECMA-119
// Directory record has 34 bytes in addition to the name's
// variable length and the padding field mentioned in section 9.1.12
totalLen := 34 + drecord.FileIDLength - (drecord.FileIDLength % 2)
sysUseLen := int64(len - totalLen)
if sysUseLen > 0 {
sysData := make([]byte, sysUseLen)
if err := binary.Read(r.image, binary.BigEndian, sysData); err != nil {
return len, ErrCorruptedImage(err)
}
}
return len, nil
}
// unpackPVD unpacks Primary Volume Descriptor in three phases. This is
// because the root directory record is a variable-length record and Go's binary
// package doesn't support unpacking variable-length structs easily.
func (r *Reader) unpackPVD() error {
// Unpack first half
var pvd1 PrimaryVolumePart1
if err := binary.Read(r.image, binary.BigEndian, &pvd1); err != nil {
return ErrCorruptedImage(err)
}
r.pvd.PrimaryVolumePart1 = pvd1
// Unpack root directory record
var drecord File
if _, err := r.unpackDRecord(&drecord); err != nil {
return ErrCorruptedImage(err)
}
r.pvd.DirectoryRecord = drecord.DirectoryRecord
r.queue.Enqueue(drecord)
// Unpack second half
var pvd2 PrimaryVolumePart2
if err := binary.Read(r.image, binary.BigEndian, &pvd2); err != nil {
return ErrCorruptedImage(err)
}
r.pvd.PrimaryVolumePart2 = pvd2
return nil
}
The MIT License (MIT)
Copyright (c) 2014 Mitchell Hashimoto
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.
# Process List Library for Go
go-ps is a library for Go that implements OS-specific APIs to list and
manipulate processes in a platform-safe way. The library can find and
list processes on Linux, Mac OS X, Solaris, and Windows.
If you're new to Go, this library has a good amount of advanced Go educational
value as well. It uses some advanced features of Go: build tags, accessing
DLL methods for Windows, cgo for Darwin, etc.
How it works:
* **Darwin** uses the `sysctl` syscall to retrieve the process table.
* **Unix** uses the procfs at `/proc` to inspect the process tree.
* **Windows** uses the Windows API, and methods such as
`CreateToolhelp32Snapshot` to get a point-in-time snapshot of
the process table.
## Installation
Install using standard `go get`:
```
$ go get github.com/mitchellh/go-ps
...
```
## TODO
Want to contribute? Here is a short TODO list of things that aren't
implemented for this library that would be nice:
* FreeBSD support
* Plan9 support
# -*- mode: ruby -*-
# vi: set ft=ruby :
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "chef/ubuntu-12.04"
config.vm.provision "shell", inline: $script
["vmware_fusion", "vmware_workstation"].each do |p|
config.vm.provider "p" do |v|
v.vmx["memsize"] = "1024"
v.vmx["numvcpus"] = "2"
v.vmx["cpuid.coresPerSocket"] = "1"
end
end
end
$script = <<SCRIPT
SRCROOT="/opt/go"
# Install Go
sudo apt-get update
sudo apt-get install -y build-essential mercurial
sudo hg clone -u release https://code.google.com/p/go ${SRCROOT}
cd ${SRCROOT}/src
sudo ./all.bash
# Setup the GOPATH
sudo mkdir -p /opt/gopath
cat <<EOF >/tmp/gopath.sh
export GOPATH="/opt/gopath"
export PATH="/opt/go/bin:\$GOPATH/bin:\$PATH"
EOF
sudo mv /tmp/gopath.sh /etc/profile.d/gopath.sh
sudo chmod 0755 /etc/profile.d/gopath.sh
# Make sure the gopath is usable by bamboo
sudo chown -R vagrant:vagrant $SRCROOT
sudo chown -R vagrant:vagrant /opt/gopath
SCRIPT
// ps provides an API for finding and listing processes in a platform-agnostic
// way.
//
// NOTE: If you're reading these docs online via GoDocs or some other system,
// you might only see the Unix docs. This project makes heavy use of
// platform-specific implementations. We recommend reading the source if you
// are interested.
package ps
// Process is the generic interface that is implemented on every platform
// and provides common operations for processes.
type Process interface {
// Pid is the process ID for this process.
Pid() int
// PPid is the parent process ID for this process.
PPid() int
// Executable name running this process. This is not a path to the
// executable.
Executable() string
}
// Processes returns all processes.
//
// This of course will be a point-in-time snapshot of when this method was
// called. Some operating systems don't provide snapshot capability of the
// process table, in which case the process table returned might contain
// ephemeral entities that happened to be running when this was called.
func Processes() ([]Process, error) {
return processes()
}
// FindProcess looks up a single process by pid.
//
// Process will be nil and error will be nil if a matching process is
// not found.
func FindProcess(pid int) (Process, error) {
return findProcess(pid)
}
// +build darwin
package ps
import (
"bytes"
"encoding/binary"
"syscall"
"unsafe"
)
type DarwinProcess struct {
pid int
ppid int
binary string
}
func (p *DarwinProcess) Pid() int {
return p.pid
}
func (p *DarwinProcess) PPid() int {
return p.ppid
}
func (p *DarwinProcess) Executable() string {
return p.binary
}
func findProcess(pid int) (Process, error) {
ps, err := processes()
if err != nil {
return nil, err
}
for _, p := range ps {
if p.Pid() == pid {
return p, nil
}
}
return nil, nil
}
func processes() ([]Process, error) {
buf, err := darwinSyscall()
if err != nil {
return nil, err
}
procs := make([]*kinfoProc, 0, 50)
k := 0
for i := _KINFO_STRUCT_SIZE; i < buf.Len(); i += _KINFO_STRUCT_SIZE {
proc := &kinfoProc{}
err = binary.Read(bytes.NewBuffer(buf.Bytes()[k:i]), binary.LittleEndian, proc)
if err != nil {
return nil, err
}
k = i
procs = append(procs, proc)
}
darwinProcs := make([]Process, len(procs))
for i, p := range procs {
darwinProcs[i] = &DarwinProcess{
pid: int(p.Pid),
ppid: int(p.PPid),
binary: darwinCstring(p.Comm),
}
}
return darwinProcs, nil
}
func darwinCstring(s [16]byte) string {
i := 0
for _, b := range s {
if b != 0 {
i++
} else {
break
}
}
return string(s[:i])
}
func darwinSyscall() (*bytes.Buffer, error) {
mib := [4]int32{_CTRL_KERN, _KERN_PROC, _KERN_PROC_ALL, 0}
size := uintptr(0)
_, _, errno := syscall.Syscall6(
syscall.SYS___SYSCTL,
uintptr(unsafe.Pointer(&mib[0])),
4,
0,
uintptr(unsafe.Pointer(&size)),
0,
0)
if errno != 0 {
return nil, errno
}
bs := make([]byte, size)
_, _, errno = syscall.Syscall6(
syscall.SYS___SYSCTL,
uintptr(unsafe.Pointer(&mib[0])),
4,
uintptr(unsafe.Pointer(&bs[0])),
uintptr(unsafe.Pointer(&size)),
0,
0)
if errno != 0 {
return nil, errno
}
return bytes.NewBuffer(bs[0:size]), nil
}
const (
_CTRL_KERN = 1
_KERN_PROC = 14
_KERN_PROC_ALL = 0
_KINFO_STRUCT_SIZE = 648
)
type kinfoProc struct {
_ [40]byte
Pid int32
_ [199]byte
Comm [16]byte
_ [301]byte
PPid int32
_ [84]byte
}
// +build freebsd,amd64
package ps
import (
"bytes"
"encoding/binary"
"syscall"
"unsafe"
)
// copied from sys/sysctl.h
const (
CTL_KERN = 1 // "high kernel": proc, limits
KERN_PROC = 14 // struct: process entries
KERN_PROC_PID = 1 // by process id
KERN_PROC_PROC = 8 // only return procs
KERN_PROC_PATHNAME = 12 // path to executable
)
// copied from sys/user.h
type Kinfo_proc struct {
Ki_structsize int32
Ki_layout int32
Ki_args int64
Ki_paddr int64
Ki_addr int64
Ki_tracep int64
Ki_textvp int64
Ki_fd int64
Ki_vmspace int64
Ki_wchan int64
Ki_pid int32
Ki_ppid int32
Ki_pgid int32
Ki_tpgid int32
Ki_sid int32
Ki_tsid int32
Ki_jobc [2]byte
Ki_spare_short1 [2]byte
Ki_tdev int32
Ki_siglist [16]byte
Ki_sigmask [16]byte
Ki_sigignore [16]byte
Ki_sigcatch [16]byte
Ki_uid int32
Ki_ruid int32
Ki_svuid int32
Ki_rgid int32
Ki_svgid int32
Ki_ngroups [2]byte
Ki_spare_short2 [2]byte
Ki_groups [64]byte
Ki_size int64
Ki_rssize int64
Ki_swrss int64
Ki_tsize int64
Ki_dsize int64
Ki_ssize int64
Ki_xstat [2]byte
Ki_acflag [2]byte
Ki_pctcpu int32
Ki_estcpu int32
Ki_slptime int32
Ki_swtime int32
Ki_cow int32
Ki_runtime int64
Ki_start [16]byte
Ki_childtime [16]byte
Ki_flag int64
Ki_kiflag int64
Ki_traceflag int32
Ki_stat [1]byte
Ki_nice [1]byte
Ki_lock [1]byte
Ki_rqindex [1]byte
Ki_oncpu [1]byte
Ki_lastcpu [1]byte
Ki_ocomm [17]byte
Ki_wmesg [9]byte
Ki_login [18]byte
Ki_lockname [9]byte
Ki_comm [20]byte
Ki_emul [17]byte
Ki_sparestrings [68]byte
Ki_spareints [36]byte
Ki_cr_flags int32
Ki_jid int32
Ki_numthreads int32
Ki_tid int32
Ki_pri int32
Ki_rusage [144]byte
Ki_rusage_ch [144]byte
Ki_pcb int64
Ki_kstack int64
Ki_udata int64
Ki_tdaddr int64
Ki_spareptrs [48]byte
Ki_spareint64s [96]byte
Ki_sflag int64
Ki_tdflags int64
}
// UnixProcess is an implementation of Process that contains Unix-specific
// fields and information.
type UnixProcess struct {
pid int
ppid int
state rune
pgrp int
sid int
binary string
}
func (p *UnixProcess) Pid() int {
return p.pid
}
func (p *UnixProcess) PPid() int {
return p.ppid
}
func (p *UnixProcess) Executable() string {
return p.binary
}
// Refresh reloads all the data associated with this process.
func (p *UnixProcess) Refresh() error {
mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PID, int32(p.pid)}
buf, length, err := call_syscall(mib)
if err != nil {
return err
}
proc_k := Kinfo_proc{}
if length != uint64(unsafe.Sizeof(proc_k)) {
return err
}
k, err := parse_kinfo_proc(buf)
if err != nil {
return err
}
p.ppid, p.pgrp, p.sid, p.binary = copy_params(&k)
return nil
}
func copy_params(k *Kinfo_proc) (int, int, int, string) {
n := -1
for i, b := range k.Ki_comm {
if b == 0 {
break
}
n = i + 1
}
comm := string(k.Ki_comm[:n])
return int(k.Ki_ppid), int(k.Ki_pgid), int(k.Ki_sid), comm
}
func findProcess(pid int) (Process, error) {
mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, int32(pid)}
_, _, err := call_syscall(mib)
if err != nil {
return nil, err
}
return newUnixProcess(pid)
}
func processes() ([]Process, error) {
results := make([]Process, 0, 50)
mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0}
buf, length, err := call_syscall(mib)
if err != nil {
return results, err
}
// get kinfo_proc size
k := Kinfo_proc{}
procinfo_len := int(unsafe.Sizeof(k))
count := int(length / uint64(procinfo_len))
// parse buf to procs
for i := 0; i < count; i++ {
b := buf[i*procinfo_len : i*procinfo_len+procinfo_len]
k, err := parse_kinfo_proc(b)
if err != nil {
continue
}
p, err := newUnixProcess(int(k.Ki_pid))
if err != nil {
continue
}
p.ppid, p.pgrp, p.sid, p.binary = copy_params(&k)
results = append(results, p)
}
return results, nil
}
func parse_kinfo_proc(buf []byte) (Kinfo_proc, error) {
var k Kinfo_proc
br := bytes.NewReader(buf)
err := binary.Read(br, binary.LittleEndian, &k)
if err != nil {
return k, err
}
return k, nil
}
func call_syscall(mib []int32) ([]byte, uint64, error) {
miblen := uint64(len(mib))
// get required buffer size
length := uint64(0)
_, _, err := syscall.RawSyscall6(
syscall.SYS___SYSCTL,
uintptr(unsafe.Pointer(&mib[0])),
uintptr(miblen),
0,
uintptr(unsafe.Pointer(&length)),
0,
0)
if err != 0 {
b := make([]byte, 0)
return b, length, err
}
if length == 0 {
b := make([]byte, 0)
return b, length, err
}
// get proc info itself
buf := make([]byte, length)
_, _, err = syscall.RawSyscall6(
syscall.SYS___SYSCTL,
uintptr(unsafe.Pointer(&mib[0])),
uintptr(miblen),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&length)),
0,
0)
if err != 0 {
return buf, length, err
}
return buf, length, nil
}
func newUnixProcess(pid int) (*UnixProcess, error) {
p := &UnixProcess{pid: pid}
return p, p.Refresh()
}
// +build linux
package ps
import (
"fmt"
"io/ioutil"
"strings"
)
// Refresh reloads all the data associated with this process.
func (p *UnixProcess) Refresh() error {
statPath := fmt.Sprintf("/proc/%d/stat", p.pid)
dataBytes, err := ioutil.ReadFile(statPath)
if err != nil {
return err
}
// First, parse out the image name
data := string(dataBytes)
binStart := strings.IndexRune(data, '(') + 1
binEnd := strings.IndexRune(data[binStart:], ')')
p.binary = data[binStart : binStart+binEnd]
// Move past the image name and start parsing the rest
data = data[binStart+binEnd+2:]
_, err = fmt.Sscanf(data,
"%c %d %d %d",
&p.state,
&p.ppid,
&p.pgrp,
&p.sid)
return err
}
// +build solaris
package ps
import (
"encoding/binary"
"fmt"
"os"
)
type ushort_t uint16
type id_t int32
type pid_t int32
type uid_t int32
type gid_t int32
type dev_t uint64
type size_t uint64
type uintptr_t uint64
type timestruc_t [16]byte
// This is copy from /usr/include/sys/procfs.h
type psinfo_t struct {
Pr_flag int32 /* process flags (DEPRECATED; do not use) */
Pr_nlwp int32 /* number of active lwps in the process */
Pr_pid pid_t /* unique process id */
Pr_ppid pid_t /* process id of parent */
Pr_pgid pid_t /* pid of process group leader */
Pr_sid pid_t /* session id */
Pr_uid uid_t /* real user id */
Pr_euid uid_t /* effective user id */
Pr_gid gid_t /* real group id */
Pr_egid gid_t /* effective group id */
Pr_addr uintptr_t /* address of process */
Pr_size size_t /* size of process image in Kbytes */
Pr_rssize size_t /* resident set size in Kbytes */
Pr_pad1 size_t
Pr_ttydev dev_t /* controlling tty device (or PRNODEV) */
// Guess this following 2 ushort_t values require a padding to properly
// align to the 64bit mark.
Pr_pctcpu ushort_t /* % of recent cpu time used by all lwps */
Pr_pctmem ushort_t /* % of system memory used by process */
Pr_pad64bit [4]byte
Pr_start timestruc_t /* process start time, from the epoch */
Pr_time timestruc_t /* usr+sys cpu time for this process */
Pr_ctime timestruc_t /* usr+sys cpu time for reaped children */
Pr_fname [16]byte /* name of execed file */
Pr_psargs [80]byte /* initial characters of arg list */
Pr_wstat int32 /* if zombie, the wait() status */
Pr_argc int32 /* initial argument count */
Pr_argv uintptr_t /* address of initial argument vector */
Pr_envp uintptr_t /* address of initial environment vector */
Pr_dmodel [1]byte /* data model of the process */
Pr_pad2 [3]byte
Pr_taskid id_t /* task id */
Pr_projid id_t /* project id */
Pr_nzomb int32 /* number of zombie lwps in the process */
Pr_poolid id_t /* pool id */
Pr_zoneid id_t /* zone id */
Pr_contract id_t /* process contract */
Pr_filler int32 /* reserved for future use */
Pr_lwp [128]byte /* information for representative lwp */
}
func (p *UnixProcess) Refresh() error {
var psinfo psinfo_t
path := fmt.Sprintf("/proc/%d/psinfo", p.pid)
fh, err := os.Open(path)
if err != nil {
return err
}
defer fh.Close()
err = binary.Read(fh, binary.LittleEndian, &psinfo)
if err != nil {
return err
}
p.ppid = int(psinfo.Pr_ppid)
p.binary = toString(psinfo.Pr_fname[:], 16)
return nil
}
func toString(array []byte, len int) string {
for i := 0; i < len; i++ {
if array[i] == 0 {
return string(array[:i])
}
}
return string(array[:])
}
// +build linux solaris
package ps
import (
"fmt"
"io"
"os"
"strconv"
)
// UnixProcess is an implementation of Process that contains Unix-specific
// fields and information.
type UnixProcess struct {
pid int
ppid int
state rune
pgrp int
sid int
binary string
}
func (p *UnixProcess) Pid() int {
return p.pid
}
func (p *UnixProcess) PPid() int {
return p.ppid
}
func (p *UnixProcess) Executable() string {
return p.binary
}
func findProcess(pid int) (Process, error) {
dir := fmt.Sprintf("/proc/%d", pid)
_, err := os.Stat(dir)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
return newUnixProcess(pid)
}
func processes() ([]Process, error) {
d, err := os.Open("/proc")
if err != nil {
return nil, err
}
defer d.Close()
results := make([]Process, 0, 50)
for {
fis, err := d.Readdir(10)
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
for _, fi := range fis {
// We only care about directories, since all pids are dirs
if !fi.IsDir() {
continue
}
// We only care if the name starts with a numeric
name := fi.Name()
if name[0] < '0' || name[0] > '9' {
continue
}
// From this point forward, any errors we just ignore, because
// it might simply be that the process doesn't exist anymore.
pid, err := strconv.ParseInt(name, 10, 0)
if err != nil {
continue
}
p, err := newUnixProcess(int(pid))
if err != nil {
continue
}
results = append(results, p)
}
}
return results, nil
}
func newUnixProcess(pid int) (*UnixProcess, error) {
p := &UnixProcess{pid: pid}
return p, p.Refresh()
}
// +build windows
package ps
import (
"fmt"
"syscall"
"unsafe"
)
// Windows API functions
var (
modKernel32 = syscall.NewLazyDLL("kernel32.dll")
procCloseHandle = modKernel32.NewProc("CloseHandle")
procCreateToolhelp32Snapshot = modKernel32.NewProc("CreateToolhelp32Snapshot")
procProcess32First = modKernel32.NewProc("Process32FirstW")
procProcess32Next = modKernel32.NewProc("Process32NextW")
)
// Some constants from the Windows API
const (
ERROR_NO_MORE_FILES = 0x12
MAX_PATH = 260
)
// PROCESSENTRY32 is the Windows API structure that contains a process's
// information.
type PROCESSENTRY32 struct {
Size uint32
CntUsage uint32
ProcessID uint32
DefaultHeapID uintptr
ModuleID uint32
CntThreads uint32
ParentProcessID uint32
PriorityClassBase int32
Flags uint32
ExeFile [MAX_PATH]uint16
}
// WindowsProcess is an implementation of Process for Windows.
type WindowsProcess struct {
pid int
ppid int
exe string
}
func (p *WindowsProcess) Pid() int {
return p.pid
}
func (p *WindowsProcess) PPid() int {
return p.ppid
}
func (p *WindowsProcess) Executable() string {
return p.exe
}
func newWindowsProcess(e *PROCESSENTRY32) *WindowsProcess {
// Find when the string ends for decoding
end := 0
for {
if e.ExeFile[end] == 0 {
break
}
end++
}
return &WindowsProcess{
pid: int(e.ProcessID),
ppid: int(e.ParentProcessID),
exe: syscall.UTF16ToString(e.ExeFile[:end]),
}
}
func findProcess(pid int) (Process, error) {
ps, err := processes()
if err != nil {
return nil, err
}
for _, p := range ps {
if p.Pid() == pid {
return p, nil
}
}
return nil, nil
}
func processes() ([]Process, error) {
handle, _, _ := procCreateToolhelp32Snapshot.Call(
0x00000002,
0)
if handle < 0 {
return nil, syscall.GetLastError()
}
defer procCloseHandle.Call(handle)
var entry PROCESSENTRY32
entry.Size = uint32(unsafe.Sizeof(entry))
ret, _, _ := procProcess32First.Call(handle, uintptr(unsafe.Pointer(&entry)))
if ret == 0 {
return nil, fmt.Errorf("Error retrieving process info.")
}
results := make([]Process, 0, 50)
for {
results = append(results, newWindowsProcess(&entry))
ret, _, _ := procProcess32Next.Call(handle, uintptr(unsafe.Pointer(&entry)))
if ret == 0 {
break
}
}
return results, nil
}
FROM golang:1.8-alpine
RUN apk update && apk add --no-cache make git
ENV GOPATH=/go
ENV PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# The project sources
VOLUME ["/go/src/github.com/moby/hyperkit/go"]
WORKDIR /go/src/github.com/moby/hyperkit/go
ENTRYPOINT ["make"]
.PHONY: build-in-container build-local
DEPS:=$(wildcard sample/*.go) $(wildcard *.go) Dockerfile.build Makefile
build-in-container: $(DEPS) clean
@echo "+ $@"
@docker build -t hyperkitgo-build -f ./Dockerfile.build .
@docker run --rm \
-v ${CURDIR}:/go/src/github.com/moby/hyperkit/go \
hyperkitgo-build build-local
build-local: build/hyperkitgo
build/hyperkitgo: $(DEPS)
@echo "+ $@"
GOOS=darwin GOARCH=amd64 \
go build -o $@ --ldflags '-extldflags "-fno-PIC"' \
sample/main.go
# The next two targets make sure we can cross-compile for Linux and Windows.
# While it makes little sense to run the sample code, it makes it easier for
# consumers of this package if it cross compiles.
build/hyperkitgo_linux: $(DEPS)
@echo "+ $@"
GOOS=linux GOARCH=amd64 \
go build -o $@ --ldflags '-extldflags "-fno-PIC"' \
sample/main.go
build/hyperkitgo.exe: $(DEPS)
@echo "+ $@"
GOOS=windows GOARCH=amd64 \
go build -o $@ --ldflags '-extldflags "-fno-PIC"' \
sample/main.go
clean:
rm -rf build
fmt:
@echo "+ $@"
@gofmt -s -l . 2>&1 | grep -v ^vendor/ | xargs gofmt -s -l -w
lint:
@echo "+ $@"
$(if $(shell which golint || echo ''), , \
$(error Please install golint))
@test -z "$$(golint ./... 2>&1 | grep -v ^vendor/ | grep -v mock/ | tee /dev/stderr)"
# this will blow away the vendored code and update it to latest
.PHONY: vendor vendor-local
vendor:
@echo "+ $@"
@rm -rf vendor.conf vendor
@docker build -t hyperkitgo-build -f ./Dockerfile.build .
@docker run --rm \
-v ${CURDIR}:/go/src/github.com/moby/hyperkit/go \
hyperkitgo-build vendor-local VERSION=${VERSION} REVISION=${REVISION}
vendor-local:
@echo "+ $@"
@go get github.com/LK4D4/vndr
@vndr init
.PHONY: ci setup
setup:
go get github.com/golang/lint/golint
ci: setup build/hyperkitgo build/hyperkitgo_linux build/hyperkitgo.exe
test -z "$$(gofmt -s -l . 2>&1 | grep -v ^vendor/ | tee /dev/stderr)"
test -z "$$(golint ./... 2>&1 | grep -v ^vendor/ | grep -v mock/ | tee /dev/stderr)"
// Package hyperkit provides a Go wrapper around the hyperkit
// command. It currently shells out to start hyperkit with the
// provided configuration.
//
// Most of the arguments should be self explanatory, but console
// handling deserves a mention. If the Console is configured with
// ConsoleStdio, the hyperkit is started with stdin, stdout, and
// stderr plumbed through to the VM console. If Console is set to
// ConsoleFile hyperkit the console output is redirected to a file and
// console input is disabled. For this mode StateDir has to be set and
// the interactive console is accessible via a 'tty' file created
// there.
//
// Currently this module has some limitations:
// - Only supports zero or one disk image
// - Only support zero or one network interface connected to VPNKit
// - Only kexec boot
//
// This package is currently implemented by shelling out a hyperkit
// process. In the future we may change this to become a wrapper
// around the hyperkit library.
package hyperkit
import (
"bufio"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"os/user"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/mitchellh/go-ps"
)
const (
// ConsoleStdio configures console to use Stdio
ConsoleStdio = iota
// ConsoleFile configures console to a tty and output to a file
ConsoleFile
defaultVPNKitSock = "Library/Containers/com.docker.docker/Data/s50"
defaultCPUs = 1
defaultMemory = 1024 // 1G
defaultVSockGuestCID = 3
jsonFile = "hyperkit.json"
pidFile = "hyperkit.pid"
)
var defaultHyperKits = []string{"hyperkit",
"com.docker.hyperkit",
"/usr/local/bin/hyperkit",
"/Applications/Docker.app/Contents/Resources/bin/hyperkit",
"/Applications/Docker.app/Contents/MacOS/com.docker.hyperkit"}
// Socket9P contains a unix domain socket path and 9p tag
type Socket9P struct {
Path string `json:"path"`
Tag string `json:"tag"`
}
// DiskConfig contains the path to a disk image and an optional size if the image needs to be created.
type DiskConfig struct {
Path string `json:"path"`
Size int `json:"size"`
Format string `json:"format"`
Driver string `json:"driver"`
}
// HyperKit contains the configuration of the hyperkit VM
type HyperKit struct {
// HyperKit is the path to the hyperkit binary
HyperKit string `json:"hyperkit"`
// StateDir is the directory where runtime state is kept. If left empty, no state will be kept.
StateDir string `json:"state_dir"`
// VPNKitSock is the location of the VPNKit socket used for networking.
VPNKitSock string `json:"vpnkit_sock"`
// VPNKitKey is a string containing a UUID, it can be used in conjunction with VPNKit to get consistent IP address.
VPNKitKey string `json:"vpnkit_key"`
// UUID is a string containing a UUID, it sets BIOS DMI UUID for the VM (as found in /sys/class/dmi/id/product_uuid on Linux).
UUID string `json:"uuid"`
// Disks contains disk images to use/create.
Disks []DiskConfig `json:"disks"`
// ISOImage is the (optional) path to a ISO image to attach
ISOImage string `json:"iso"`
// VSock enables the virtio-socket device and exposes it on the host
VSock bool `json:"vsock"`
// VSockPorts is a list of guest VSock ports that should be exposed as sockets on the host
VSockPorts []int `json:"vsock_ports"`
// VSock guest CID
VSockGuestCID int `json:"vsock_guest_cid"`
// VMNet whether to create vmnet network
VMNet bool `json:"vmnet"`
// 9P sockets
Sockets9P []Socket9P `json:"9p_sockets"`
// Kernel is the path to the kernel image to boot
Kernel string `json:"kernel"`
// Initrd is the path to the initial ramdisk to boot off
Initrd string `json:"initrd"`
// CPUs is the number CPUs to configure
CPUs int `json:"cpus"`
// Memory is the amount of megabytes of memory for the VM
Memory int `json:"memory"`
// Console defines where the console of the VM should be
// connected to. ConsoleStdio and ConsoleFile are supported.
Console int `json:"console"`
// Below here are internal members, but they are exported so
// that they are written to the state json file, if configured.
// Pid of the hyperkit process
Pid int `json:"pid"`
// Arguments used to execute the hyperkit process
Arguments []string `json:"arguments"`
// CmdLine is a single string of the command line
CmdLine string `json:"cmdline"`
process *os.Process
background bool
log *log.Logger
}
// New creates a template config structure.
// - If hyperkit can't be found an error is returned.
// - If vpnkitsock is empty no networking is configured. If it is set
// to "auto" it tries to re-use the Docker for Mac VPNKit connection.
// - If statedir is "" no state is written to disk.
func New(hyperkit, vpnkitsock, statedir string) (*HyperKit, error) {
h := HyperKit{}
var err error
h.HyperKit, err = checkHyperKit(hyperkit)
if err != nil {
return nil, err
}
h.StateDir = statedir
h.VPNKitSock, err = checkVPNKitSock(vpnkitsock)
if err != nil {
return nil, err
}
h.CPUs = defaultCPUs
h.Memory = defaultMemory
h.VSockGuestCID = defaultVSockGuestCID
h.Console = ConsoleStdio
return &h, nil
}
// FromState reads a json file from statedir and populates a HyperKit structure.
func FromState(statedir string) (*HyperKit, error) {
b, err := ioutil.ReadFile(filepath.Join(statedir, jsonFile))
if err != nil {
return nil, fmt.Errorf("Can't read json file: %s", err)
}
h := &HyperKit{}
err = json.Unmarshal(b, h)
if err != nil {
return nil, fmt.Errorf("Can't parse json file: %s", err)
}
// Make sure the pid written by hyperkit is the same as in the json
d, err := ioutil.ReadFile(filepath.Join(statedir, pidFile))
if err != nil {
return nil, err
}
pid, err := strconv.Atoi(string(d[:]))
if err != nil {
return nil, err
}
if h.Pid != pid {
return nil, fmt.Errorf("pids do not match %d != %d", h.Pid, pid)
}
h.process, err = os.FindProcess(h.Pid)
if err != nil {
return nil, err
}
return h, nil
}
// SetLogger sets the log instance to use for the output of the hyperkit process itself (not the console of the VM).
// This is only relevant when Console is set to ConsoleFile
func (h *HyperKit) SetLogger(logger *log.Logger) {
h.log = logger
}
// Run the VM with a given command line until it exits
func (h *HyperKit) Run(cmdline string) error {
h.background = false
return h.execute(cmdline)
}
// Start the VM with a given command line in the background
func (h *HyperKit) Start(cmdline string) error {
h.background = true
return h.execute(cmdline)
}
func (h *HyperKit) execute(cmdline string) error {
var err error
// Sanity checks on configuration
if h.Console == ConsoleFile && h.StateDir == "" {
return fmt.Errorf("If ConsoleFile is set, StateDir must be specified")
}
if h.Console == ConsoleStdio && !isTerminal(os.Stdout) && h.StateDir == "" {
return fmt.Errorf("If ConsoleStdio is set but stdio is not a terminal, StateDir must be specified")
}
if h.ISOImage != "" {
if _, err = os.Stat(h.ISOImage); os.IsNotExist(err) {
return fmt.Errorf("ISO %s does not exist", h.ISOImage)
}
}
if h.VSock && h.StateDir == "" {
return fmt.Errorf("If virtio-sockets are enabled, StateDir must be specified")
}
if !h.VSock && len(h.VSockPorts) > 0 {
return fmt.Errorf("To forward vsock ports vsock must be enabled")
}
if _, err = os.Stat(h.Kernel); os.IsNotExist(err) {
return fmt.Errorf("Kernel %s does not exist", h.Kernel)
}
if _, err = os.Stat(h.Initrd); os.IsNotExist(err) {
return fmt.Errorf("initrd %s does not exist", h.Initrd)
}
// Create files
if h.StateDir != "" {
err = os.MkdirAll(h.StateDir, 0755)
if err != nil {
return err
}
}
for idx, config := range h.Disks {
if config.Path == "" {
if h.StateDir == "" {
return fmt.Errorf("Unable to create disk image when neither path nor state dir is set")
}
if config.Size <= 0 {
return fmt.Errorf("Unable to create disk image when size is 0 or not set")
}
config.Path = fmt.Sprintf(filepath.Clean(filepath.Join(h.StateDir, "disk%02d.img")), idx)
h.Disks[idx] = config
}
if _, err = os.Stat(config.Path); os.IsNotExist(err) {
if config.Size != 0 {
err = CreateDiskImage(config.Path, config.Size)
if err != nil {
return err
}
} else {
return fmt.Errorf("Disk image %s not found and unable to create it as size is not specified", config.Path)
}
}
}
// Run
h.buildArgs(cmdline)
err = h.execHyperKit()
if err != nil {
return err
}
return nil
}
// Stop the running VM
func (h *HyperKit) Stop() error {
if h.process == nil {
return fmt.Errorf("hyperkit process not known")
}
if !h.IsRunning() {
return nil
}
err := h.process.Kill()
if err != nil {
return err
}
return nil
}
// IsRunning returns true if the hyperkit process is running.
func (h *HyperKit) IsRunning() bool {
// os.FindProcess on Unix always returns a process object even
// if the process does not exists. There does not seem to be
// a call to find out if the process is running either, so we
// use another package to find out.
proc, err := ps.FindProcess(h.Pid)
if err != nil {
return false
}
if proc == nil {
return false
}
return true
}
// isDisk checks if the specified path is used as a disk image
func (h *HyperKit) isDisk(path string) bool {
for _, config := range h.Disks {
if filepath.Clean(path) == filepath.Clean(config.Path) {
return true
}
}
return false
}
// Remove deletes all statefiles if present.
// This also removes the StateDir if empty.
// If keepDisk is set, the disks will not get removed.
func (h *HyperKit) Remove(keepDisk bool) error {
if h.IsRunning() {
return fmt.Errorf("Can't remove state as process is running")
}
if h.StateDir == "" {
// If there is not state directory we don't mess with the disk
return nil
}
if !keepDisk {
return os.RemoveAll(h.StateDir)
}
files, _ := ioutil.ReadDir(h.StateDir)
for _, f := range files {
fn := filepath.Clean(filepath.Join(h.StateDir, f.Name()))
if h.isDisk(fn) {
continue
}
err := os.Remove(fn)
if err != nil {
return err
}
}
return nil
}
// Convert to json string
func (h *HyperKit) String() string {
s, err := json.Marshal(h)
if err != nil {
return err.Error()
}
return string(s)
}
// CreateDiskImage creates a empty file suitable for use as a disk image for a hyperkit VM.
func CreateDiskImage(location string, sizeMB int) error {
diskDir := path.Dir(location)
if diskDir != "." {
if err := os.MkdirAll(diskDir, 0755); err != nil {
return err
}
}
f, err := os.Create(location)
if err != nil {
return err
}
defer f.Close()
buf := make([]byte, 1048676)
for i := 0; i < sizeMB; i++ {
f.Write(buf)
}
return nil
}
func intArrayToString(i []int, sep string) string {
if len(i) == 0 {
return ""
}
s := make([]string, len(i))
for idx := range i {
s[idx] = strconv.Itoa(i[idx])
}
return strings.Join(s, sep)
}
func (h *HyperKit) buildArgs(cmdline string) {
a := []string{"-A", "-u"}
if h.StateDir != "" {
a = append(a, "-F", filepath.Join(h.StateDir, pidFile))
}
a = append(a, "-c", fmt.Sprintf("%d", h.CPUs))
a = append(a, "-m", fmt.Sprintf("%dM", h.Memory))
a = append(a, "-s", "0:0,hostbridge")
a = append(a, "-s", "31,lpc")
nextSlot := 1
if h.VPNKitSock != "" {
if h.VPNKitKey == "" {
a = append(a, "-s", fmt.Sprintf("%d:0,virtio-vpnkit,path=%s", nextSlot, h.VPNKitSock))
} else {
a = append(a, "-s", fmt.Sprintf("%d:0,virtio-vpnkit,path=%s,uuid=%s", nextSlot, h.VPNKitSock, h.VPNKitKey))
}
nextSlot++
}
if h.VMNet {
a = append(a, "-s", fmt.Sprintf("%d:0,virtio-net", nextSlot))
nextSlot++
}
if h.UUID != "" {
a = append(a, "-U", h.UUID)
}
for _, p := range h.Disks {
// Default the driver to virtio-blk
driver := "virtio-blk"
if p.Driver != "" {
driver = p.Driver
}
arg := fmt.Sprintf("%d:0,%s,%s", nextSlot, driver, p.Path)
// Add on a format instruction if specified.
if p.Format != "" {
arg += ",format=" + p.Format
}
a = append(a, "-s", arg)
nextSlot++
}
if h.VSock {
l := fmt.Sprintf("%d,virtio-sock,guest_cid=%d,path=%s", nextSlot, h.VSockGuestCID, h.StateDir)
if len(h.VSockPorts) > 0 {
l = fmt.Sprintf("%s,guest_forwards=%s", l, intArrayToString(h.VSockPorts, ";"))
}
a = append(a, "-s", l)
nextSlot++
}
if h.ISOImage != "" {
a = append(a, "-s", fmt.Sprintf("%d,ahci-cd,%s", nextSlot, h.ISOImage))
nextSlot++
}
a = append(a, "-s", fmt.Sprintf("%d,virtio-rnd", nextSlot))
nextSlot++
for _, p := range h.Sockets9P {
a = append(a, "-s", fmt.Sprintf("%d,virtio-9p,path=%s,tag=%s", nextSlot, p.Path, p.Tag))
nextSlot++
}
if h.Console == ConsoleStdio && isTerminal(os.Stdout) {
a = append(a, "-l", "com1,stdio")
} else if h.StateDir != "" {
a = append(a, "-l", fmt.Sprintf("com1,autopty=%s/tty,log=%s/console-ring", h.StateDir, h.StateDir))
}
kernArgs := fmt.Sprintf("kexec,%s,%s,earlyprintk=serial %s", h.Kernel, h.Initrd, cmdline)
a = append(a, "-f", kernArgs)
h.Arguments = a
h.CmdLine = h.HyperKit + " " + strings.Join(a, " ")
}
// Execute hyperkit and plumb stdin/stdout/stderr.
func (h *HyperKit) execHyperKit() error {
cmd := exec.Command(h.HyperKit, h.Arguments...)
cmd.Env = os.Environ()
// Plumb in stdin/stdout/stderr.
//
// If ConsoleStdio is configured and we are on a terminal,
// just plugin stdio. If we are not on a terminal we have a
// StateDir (as per checks above) and have configured HyperKit
// to use a PTY in the statedir. In this case, we just open
// the PTY slave and copy it to stdout (and ignore
// stdin). This allows for redirecting of the VM output, used
// for testing.
//
// If a logger is specified, use it for stdout/stderr
// logging. Otherwise use the default /dev/null.
if h.Console == ConsoleStdio {
if isTerminal(os.Stdout) {
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
} else {
go func() {
ttyPath := fmt.Sprintf("%s/tty", h.StateDir)
var tty *os.File
var err error
for {
tty, err = os.OpenFile(ttyPath, os.O_RDONLY, 0)
if err != nil {
time.Sleep(10 * 1000 * 1000 * time.Nanosecond)
continue
} else {
break
}
}
saneTerminal(tty)
setRaw(tty)
io.Copy(os.Stdout, tty)
tty.Close()
}()
}
} else if h.log != nil {
stdoutChan := make(chan string)
stderrChan := make(chan string)
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return err
}
stream(stdout, stdoutChan)
stream(stderr, stderrChan)
done := make(chan struct{})
go func() {
for {
select {
case stderrl := <-stderrChan:
log.Printf("%s", stderrl)
case stdoutl := <-stdoutChan:
log.Printf("%s", stdoutl)
case <-done:
return
}
}
}()
}
err := cmd.Start()
if err != nil {
return err
}
h.Pid = cmd.Process.Pid
h.process = cmd.Process
err = h.writeState()
if err != nil {
h.process.Kill()
return err
}
if !h.background {
err = cmd.Wait()
if err != nil {
return err
}
} else {
// Make sure we reap the child when it exits
go cmd.Wait()
}
return nil
}
// writeState write the state to a JSON file
func (h *HyperKit) writeState() error {
if h.StateDir == "" {
// This is not an error
return nil
}
s, err := json.Marshal(h)
if err != nil {
return err
}
return ioutil.WriteFile(filepath.Join(h.StateDir, jsonFile), []byte(s), 0644)
}
func stream(r io.ReadCloser, dest chan<- string) {
go func() {
defer r.Close()
reader := bufio.NewReader(r)
for {
line, err := reader.ReadString('\n')
if err != nil {
return
}
dest <- line
}
}()
}
// checkHyperKit tries to find and/or validate the path of hyperkit
func checkHyperKit(hyperkit string) (string, error) {
if hyperkit != "" {
p, err := exec.LookPath(hyperkit)
if err != nil {
return "", fmt.Errorf("Could not find hyperkit executable %s: %s", hyperkit, err)
}
return p, nil
}
// Look in a number of default locations
for _, hyperkit := range defaultHyperKits {
p, err := exec.LookPath(hyperkit)
if err == nil {
return p, nil
}
}
return "", fmt.Errorf("Could not find hyperkit executable")
}
// checkVPNKitSock tries to find and/or validate the path of the VPNKit socket
func checkVPNKitSock(vpnkitsock string) (string, error) {
if vpnkitsock == "auto" {
vpnkitsock = filepath.Join(getHome(), defaultVPNKitSock)
}
if vpnkitsock == "" {
return "", nil
}
vpnkitsock = filepath.Clean(vpnkitsock)
_, err := os.Stat(vpnkitsock)
if err != nil {
return "", err
}
return vpnkitsock, nil
}
func getHome() string {
if usr, err := user.Current(); err == nil {
return usr.HomeDir
}
return os.Getenv("HOME")
}
package hyperkit
/*
Most of this code was copied and adjusted from:
https://github.com/containerd/console
which is under Apache License Version 2.0, January 2004
*/
import (
"os"
"unsafe"
"golang.org/x/sys/unix"
)
func tcget(fd uintptr, p *unix.Termios) error {
return ioctl(fd, unix.TIOCGETA, uintptr(unsafe.Pointer(p)))
}
func tcset(fd uintptr, p *unix.Termios) error {
return ioctl(fd, unix.TIOCSETA, uintptr(unsafe.Pointer(p)))
}
func ioctl(fd, flag, data uintptr) error {
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, flag, data); err != 0 {
return err
}
return nil
}
func saneTerminal(f *os.File) error {
// Go doesn't have a wrapper for any of the termios ioctls.
var termios unix.Termios
if err := tcget(f.Fd(), &termios); err != nil {
return err
}
// Set -onlcr so we don't have to deal with \r.
termios.Oflag &^= unix.ONLCR
return tcset(f.Fd(), &termios)
}
func setRaw(f *os.File) error {
var termios unix.Termios
if err := tcget(f.Fd(), &termios); err != nil {
return err
}
termios = cfmakeraw(termios)
termios.Oflag = termios.Oflag | unix.OPOST
return tcset(f.Fd(), &termios)
}
// isTerminal checks if the provided file is a terminal
func isTerminal(f *os.File) bool {
var termios unix.Termios
if tcget(f.Fd(), &termios) != nil {
return false
}
return true
}
func cfmakeraw(t unix.Termios) unix.Termios {
t.Iflag = uint64(uint32(t.Iflag) & ^uint32((unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON)))
t.Oflag = uint64(uint32(t.Oflag) & ^uint32(unix.OPOST))
t.Lflag = uint64(uint32(t.Lflag) & ^(uint32(unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN)))
t.Cflag = uint64(uint32(t.Cflag) & ^(uint32(unix.CSIZE | unix.PARENB)))
t.Cflag = t.Cflag | unix.CS8
t.Cc[unix.VMIN] = 1
t.Cc[unix.VTIME] = 0
return t
}
// +build !darwin
package hyperkit
import (
"log"
"os"
)
func saneTerminal(f *os.File) error {
log.Fatal("Function not supported on your OS")
return nil
}
func setRaw(f *os.File) error {
log.Fatal("Function not supported on your OS")
return nil
}
// isTerminal checks if the provided file is a terminal
func isTerminal(f *os.File) bool {
log.Fatal("Function not supported on your OS")
return false
}
github.com/mitchellh/go-ps 4fdf99ab29366514c69ccccddab5dc58b8d84062
golang.org/x/sys b90f89a1e7a9c1f6b918820b3daa7f08488c8594
# github/gitignore/Go.gitignore
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# github/gitignore/C.gitignore
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
modules.order
Module.symvers
Mkfile.old
dkms.conf
# binary
go-uuidgen
!cmd/go-uuidgen
Copyright (c) 2016, Koichi Shiraishi
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of que nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 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.
// github.com/mist64/xhyve/blob/master/include/xhyve/support/uuid.h
/*-
* Copyright (c) 2002,2005 Marcel Moolenaar
* Copyright (c) 2002 Hiten Mahesh Pandya
* 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 AUTHOR 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 AUTHOR 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.
*
* $FreeBSD$
*/
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define _UUID_NODE_LEN 6
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
struct uuid {
uint32_t time_low;
uint16_t time_mid;
uint16_t time_hi_and_version;
uint8_t clock_seq_hi_and_reserved;
uint8_t clock_seq_low;
uint8_t node[_UUID_NODE_LEN];
};
#pragma clang diagnostic pop
typedef struct uuid uuid_internal_t;
/*
* This implementation mostly conforms to the DCE 1.1 specification.
* See Also:
* uuidgen(1), uuidgen(2), uuid(3)
*/
/* Status codes returned by the functions. */
#define uuid_s_ok 0
#define uuid_s_bad_version 1
#define uuid_s_invalid_string_uuid 2
#define uuid_s_no_memory 3
/*
* uuid_create_nil() - create a nil UUID.
* See also:
* http://www.opengroup.org/onlinepubs/009629399/uuid_create_nil.htm
*/
static inline void
uuid_create_nil(uuid_t *u, uint32_t *status)
{
if (status)
*status = uuid_s_ok;
bzero(u, sizeof(*u));
}
static inline void
uuid_enc_le(void *buf, uuid_t *uuid)
{
uuid_internal_t *u = (uuid_internal_t *) ((void *) uuid);
uint8_t *p = buf;
int i;
memcpy(p, &u->time_low, 4);
memcpy(p, &u->time_mid, 2);
memcpy(p, &u->time_hi_and_version, 2);
p[8] = u->clock_seq_hi_and_reserved;
p[9] = u->clock_seq_low;
for (i = 0; i < _UUID_NODE_LEN; i++)
p[10 + i] = u->node[i];
}
/*
* uuid_from_string() - convert a string representation of an UUID into
* a binary representation.
* See also:
* http://www.opengroup.org/onlinepubs/009629399/uuid_from_string.htm
*
* NOTE: The sequence field is in big-endian, while the time fields are in
* native byte order.
*/
static inline void
uuid_from_string(const char *s, uuid_t *uuid, uint32_t *status)
{
uuid_internal_t *u = (uuid_internal_t *) ((void *) uuid);
int n;
/* Short-circuit 2 special cases: NULL pointer and empty string. */
if (s == NULL || *s == '\0') {
uuid_create_nil(((uuid_t *) u), status);
return;
}
/* Assume the worst. */
if (status != NULL)
*status = uuid_s_invalid_string_uuid;
/* The UUID string representation has a fixed length. */
if (strlen(s) != 36)
return;
/*
* We only work with "new" UUIDs. New UUIDs have the form:
* 01234567-89ab-cdef-0123-456789abcdef
* The so called "old" UUIDs, which we don't support, have the form:
* 0123456789ab.cd.ef.01.23.45.67.89.ab
*/
if (s[8] != '-')
return;
n = sscanf(s,
"%8x-%4hx-%4hx-%2hhx%2hhx-%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx",
&u->time_low, &u->time_mid, &u->time_hi_and_version,
&u->clock_seq_hi_and_reserved, &u->clock_seq_low, &u->node[0],
&u->node[1], &u->node[2], &u->node[3], &u->node[4], &u->node[5]);
/* Make sure we have all conversions. */
if (n != 11)
return;
/* We have a successful scan. Check semantics... */
n = u->clock_seq_hi_and_reserved;
if ((n & 0x80) != 0x00 && /* variant 0? */
(n & 0xc0) != 0x80 && /* variant 1? */
(n & 0xe0) != 0xc0) { /* variant 2? */
if (status != NULL)
*status = uuid_s_bad_version;
} else {
if (status != NULL)
*status = uuid_s_ok;
}
}
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册