From c5843b761596564ba05f1a7f7b70c2368baf93ab Mon Sep 17 00:00:00 2001 From: Kenfe-Mickael Laventure Date: Fri, 17 Mar 2017 16:09:06 -0700 Subject: [PATCH] Initial windows runtime work Signed-off-by: Kenfe-Mickael Laventure --- .travis.yml | 4 + Makefile | 75 +- cmd/containerd-shim/{main.go => main_unix.go} | 2 + cmd/containerd/builtins.go | 4 - cmd/containerd/builtins_linux.go | 8 + cmd/containerd/builtins_windows.go | 6 + cmd/containerd/config.go | 25 +- cmd/containerd/config_unix.go | 18 + cmd/containerd/config_windows.go | 21 + cmd/containerd/main.go | 48 +- cmd/containerd/main_unix.go | 48 + cmd/containerd/main_windows.go | 28 + cmd/ctr/exec.go | 63 +- cmd/ctr/exec_unix.go | 69 ++ cmd/ctr/exec_windows.go | 40 + cmd/ctr/kill.go | 60 - cmd/ctr/kill_unix.go | 66 ++ cmd/ctr/kill_windows.go | 7 + cmd/ctr/main.go | 10 +- cmd/ctr/main_unix.go | 7 + cmd/ctr/run.go | 319 +---- cmd/ctr/run_unix.go | 264 +++++ cmd/ctr/run_windows.go | 137 +++ cmd/ctr/shim.go | 2 + cmd/ctr/utils.go | 90 -- cmd/ctr/utils_unix.go | 102 ++ cmd/ctr/utils_windows.go | 121 ++ cmd/dist/common.go | 6 +- container.go | 3 +- content/store.go | 10 +- content/store_unix.go | 17 + content/store_windows.go | 10 + linux/container.go | 2 + linux/runtime.go | 2 + linux/shim.go | 2 + linux/shim/exec.go | 2 + linux/shim/init.go | 2 + linux/shim/io.go | 2 + linux/shim/process.go | 2 + linux/shim/service.go | 2 + mount_unix.go | 6 +- mount_windows.go | 12 +- reaper/reaper.go | 2 + services/execution/service.go | 7 +- services/rootfs/service.go | 4 +- snapshot/btrfs/btrfs.go | 2 + snapshot/btrfs/btrfs_test.go | 2 + snapshot/overlay/overlay.go | 2 + snapshot/overlay/overlay_test.go | 2 + snapshot/windows/windows.go | 76 ++ sys/{socket.go => socket_unix.go} | 17 + sys/socket_windows.go | 16 + vendor.conf | 4 +- vendor/github.com/Azure/go-ansiterm/LICENSE | 21 + vendor/github.com/Azure/go-ansiterm/README.md | 12 + .../github.com/Azure/go-ansiterm/constants.go | 188 +++ .../github.com/Azure/go-ansiterm/context.go | 7 + .../Azure/go-ansiterm/csi_entry_state.go | 49 + .../Azure/go-ansiterm/csi_param_state.go | 38 + .../go-ansiterm/escape_intermediate_state.go | 36 + .../Azure/go-ansiterm/escape_state.go | 47 + .../Azure/go-ansiterm/event_handler.go | 90 ++ .../Azure/go-ansiterm/ground_state.go | 24 + .../Azure/go-ansiterm/osc_string_state.go | 31 + vendor/github.com/Azure/go-ansiterm/parser.go | 136 +++ .../go-ansiterm/parser_action_helpers.go | 103 ++ .../Azure/go-ansiterm/parser_actions.go | 122 ++ vendor/github.com/Azure/go-ansiterm/states.go | 71 ++ .../github.com/Azure/go-ansiterm/utilities.go | 21 + .../Azure/go-ansiterm/winterm/ansi.go | 182 +++ .../Azure/go-ansiterm/winterm/api.go | 322 +++++ .../go-ansiterm/winterm/attr_translation.go | 100 ++ .../go-ansiterm/winterm/cursor_helpers.go | 101 ++ .../go-ansiterm/winterm/erase_helpers.go | 84 ++ .../go-ansiterm/winterm/scroll_helper.go | 118 ++ .../Azure/go-ansiterm/winterm/utilities.go | 9 + .../go-ansiterm/winterm/win_event_handler.go | 726 ++++++++++++ vendor/github.com/Microsoft/hcsshim/LICENSE | 21 + vendor/github.com/Microsoft/hcsshim/README.md | 12 + .../Microsoft/hcsshim/activatelayer.go | 28 + .../github.com/Microsoft/hcsshim/baselayer.go | 183 +++ .../github.com/Microsoft/hcsshim/callback.go | 79 ++ vendor/github.com/Microsoft/hcsshim/cgo.go | 7 + .../github.com/Microsoft/hcsshim/container.go | 750 ++++++++++++ .../Microsoft/hcsshim/createlayer.go | 27 + .../Microsoft/hcsshim/createsandboxlayer.go | 35 + .../Microsoft/hcsshim/deactivatelayer.go | 26 + .../Microsoft/hcsshim/destroylayer.go | 27 + vendor/github.com/Microsoft/hcsshim/errors.go | 239 ++++ .../Microsoft/hcsshim/expandsandboxsize.go | 26 + .../Microsoft/hcsshim/exportlayer.go | 158 +++ .../Microsoft/hcsshim/getlayermountpath.go | 55 + .../Microsoft/hcsshim/getsharedbaseimages.go | 22 + vendor/github.com/Microsoft/hcsshim/guid.go | 19 + .../github.com/Microsoft/hcsshim/hcsshim.go | 166 +++ .../github.com/Microsoft/hcsshim/hnsfuncs.go | 271 +++++ .../Microsoft/hcsshim/importlayer.go | 214 ++++ .../github.com/Microsoft/hcsshim/interface.go | 159 +++ .../Microsoft/hcsshim/layerexists.go | 30 + .../Microsoft/hcsshim/layerutils.go | 111 ++ vendor/github.com/Microsoft/hcsshim/legacy.go | 723 ++++++++++++ .../Microsoft/hcsshim/mksyscall_windows.go | 934 +++++++++++++++ .../Microsoft/hcsshim/nametoguid.go | 20 + .../Microsoft/hcsshim/preparelayer.go | 46 + .../github.com/Microsoft/hcsshim/process.go | 391 +++++++ .../Microsoft/hcsshim/processimage.go | 23 + .../Microsoft/hcsshim/unpreparelayer.go | 27 + vendor/github.com/Microsoft/hcsshim/utils.go | 33 + .../github.com/Microsoft/hcsshim/version.go | 7 + .../Microsoft/hcsshim/waithelper.go | 62 + .../github.com/Microsoft/hcsshim/zhcsshim.go | 1042 +++++++++++++++++ .../crosbymichael/console/console.go | 8 +- .../crosbymichael/console/console_linux.go | 6 + .../crosbymichael/console/console_windows.go | 187 +++ windows/container.go | 201 ++++ windows/hcs/hcs.go | 523 +++++++++ windows/hcs/process.go | 50 + windows/hcs/shimio.go | 77 ++ windows/hcs/types.go | 22 + windows/runtime.go | 211 ++++ 120 files changed, 11158 insertions(+), 596 deletions(-) rename cmd/containerd-shim/{main.go => main_unix.go} (99%) create mode 100644 cmd/containerd/builtins_linux.go create mode 100644 cmd/containerd/builtins_windows.go create mode 100644 cmd/containerd/config_unix.go create mode 100644 cmd/containerd/config_windows.go create mode 100644 cmd/containerd/main_unix.go create mode 100644 cmd/containerd/main_windows.go create mode 100644 cmd/ctr/exec_unix.go create mode 100644 cmd/ctr/exec_windows.go create mode 100644 cmd/ctr/kill_unix.go create mode 100644 cmd/ctr/kill_windows.go create mode 100644 cmd/ctr/main_unix.go create mode 100644 cmd/ctr/run_unix.go create mode 100644 cmd/ctr/run_windows.go create mode 100644 cmd/ctr/utils_unix.go create mode 100644 cmd/ctr/utils_windows.go create mode 100644 content/store_unix.go create mode 100644 content/store_windows.go create mode 100644 snapshot/windows/windows.go rename sys/{socket.go => socket_unix.go} (55%) create mode 100644 sys/socket_windows.go create mode 100644 vendor/github.com/Azure/go-ansiterm/LICENSE create mode 100644 vendor/github.com/Azure/go-ansiterm/README.md create mode 100644 vendor/github.com/Azure/go-ansiterm/constants.go create mode 100644 vendor/github.com/Azure/go-ansiterm/context.go create mode 100644 vendor/github.com/Azure/go-ansiterm/csi_entry_state.go create mode 100644 vendor/github.com/Azure/go-ansiterm/csi_param_state.go create mode 100644 vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go create mode 100644 vendor/github.com/Azure/go-ansiterm/escape_state.go create mode 100644 vendor/github.com/Azure/go-ansiterm/event_handler.go create mode 100644 vendor/github.com/Azure/go-ansiterm/ground_state.go create mode 100644 vendor/github.com/Azure/go-ansiterm/osc_string_state.go create mode 100644 vendor/github.com/Azure/go-ansiterm/parser.go create mode 100644 vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go create mode 100644 vendor/github.com/Azure/go-ansiterm/parser_actions.go create mode 100644 vendor/github.com/Azure/go-ansiterm/states.go create mode 100644 vendor/github.com/Azure/go-ansiterm/utilities.go create mode 100644 vendor/github.com/Azure/go-ansiterm/winterm/ansi.go create mode 100644 vendor/github.com/Azure/go-ansiterm/winterm/api.go create mode 100644 vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go create mode 100644 vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go create mode 100644 vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go create mode 100644 vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go create mode 100644 vendor/github.com/Azure/go-ansiterm/winterm/utilities.go create mode 100644 vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go create mode 100644 vendor/github.com/Microsoft/hcsshim/LICENSE create mode 100644 vendor/github.com/Microsoft/hcsshim/README.md create mode 100644 vendor/github.com/Microsoft/hcsshim/activatelayer.go create mode 100644 vendor/github.com/Microsoft/hcsshim/baselayer.go create mode 100644 vendor/github.com/Microsoft/hcsshim/callback.go create mode 100644 vendor/github.com/Microsoft/hcsshim/cgo.go create mode 100644 vendor/github.com/Microsoft/hcsshim/container.go create mode 100644 vendor/github.com/Microsoft/hcsshim/createlayer.go create mode 100644 vendor/github.com/Microsoft/hcsshim/createsandboxlayer.go create mode 100644 vendor/github.com/Microsoft/hcsshim/deactivatelayer.go create mode 100644 vendor/github.com/Microsoft/hcsshim/destroylayer.go create mode 100644 vendor/github.com/Microsoft/hcsshim/errors.go create mode 100644 vendor/github.com/Microsoft/hcsshim/expandsandboxsize.go create mode 100644 vendor/github.com/Microsoft/hcsshim/exportlayer.go create mode 100644 vendor/github.com/Microsoft/hcsshim/getlayermountpath.go create mode 100644 vendor/github.com/Microsoft/hcsshim/getsharedbaseimages.go create mode 100644 vendor/github.com/Microsoft/hcsshim/guid.go create mode 100644 vendor/github.com/Microsoft/hcsshim/hcsshim.go create mode 100644 vendor/github.com/Microsoft/hcsshim/hnsfuncs.go create mode 100644 vendor/github.com/Microsoft/hcsshim/importlayer.go create mode 100644 vendor/github.com/Microsoft/hcsshim/interface.go create mode 100644 vendor/github.com/Microsoft/hcsshim/layerexists.go create mode 100644 vendor/github.com/Microsoft/hcsshim/layerutils.go create mode 100644 vendor/github.com/Microsoft/hcsshim/legacy.go create mode 100644 vendor/github.com/Microsoft/hcsshim/mksyscall_windows.go create mode 100644 vendor/github.com/Microsoft/hcsshim/nametoguid.go create mode 100644 vendor/github.com/Microsoft/hcsshim/preparelayer.go create mode 100644 vendor/github.com/Microsoft/hcsshim/process.go create mode 100644 vendor/github.com/Microsoft/hcsshim/processimage.go create mode 100644 vendor/github.com/Microsoft/hcsshim/unpreparelayer.go create mode 100644 vendor/github.com/Microsoft/hcsshim/utils.go create mode 100644 vendor/github.com/Microsoft/hcsshim/version.go create mode 100644 vendor/github.com/Microsoft/hcsshim/waithelper.go create mode 100644 vendor/github.com/Microsoft/hcsshim/zhcsshim.go create mode 100644 vendor/github.com/crosbymichael/console/console_windows.go create mode 100644 windows/container.go create mode 100644 windows/hcs/hcs.go create mode 100644 windows/hcs/process.go create mode 100644 windows/hcs/shimio.go create mode 100644 windows/hcs/types.go create mode 100644 windows/runtime.go diff --git a/.travis.yml b/.travis.yml index 09a432c..91b8240 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,10 @@ addons: packages: - btrfs-tools +env: + - GOOS=windows + - GOOS=linux + install: - wget https://github.com/google/protobuf/releases/download/v3.1.0/protoc-3.1.0-linux-x86_64.zip -O /tmp/protoc-3.1.0-linux-x86_64.zip - unzip -o -d /tmp/protobuf /tmp/protoc-3.1.0-linux-x86_64.zip diff --git a/Makefile b/Makefile index 3fad2e9..d8a1941 100644 --- a/Makefile +++ b/Makefile @@ -9,14 +9,29 @@ VERSION=$(shell git describe --match 'v[0-9]*' --dirty='.m' --always) PKG=github.com/containerd/containerd +GOOS ?= $(shell go env GOOS) +WHALE = "🐳" +ONI = "👹" +ifeq ("$(OS)", "Windows_NT") + WHALE="+" + ONI="-" +endif + # Project packages. PACKAGES=$(shell go list ./... | grep -v /vendor/) INTEGRATION_PACKAGE=${PKG}/integration SNAPSHOT_PACKAGES=$(shell go list ./snapshot/...) # Project binaries. -COMMANDS=ctr containerd containerd-shim protoc-gen-gogoctrd dist ctrd-protobuild +COMMANDS=ctr containerd protoc-gen-gogoctrd dist ctrd-protobuild +ifneq ("$(GOOS)", "windows") + COMMANDS += containerd-shim +endif BINARIES=$(addprefix bin/,$(COMMANDS)) +ifeq ("$(GOOS)", "windows") + BINARY_SUFFIX=".exe" +endif + GO_LDFLAGS=-ldflags "-X $(PKG).Version=$(VERSION) -X $(PKG).Package=$(PKG)" @@ -36,45 +51,45 @@ AUTHORS: .mailmap .git/HEAD git log --format='%aN <%aE>' | sort -fu > $@ setup: ## install dependencies - @echo "🐳 $@" + @echo "$(WHALE) $@" # TODO(stevvooe): Install these from the vendor directory @go get -u github.com/golang/lint/golint #@go get -u github.com/kisielk/errcheck @go get -u github.com/gordonklaus/ineffassign generate: protos - @echo "🐳 $@" + @echo "$(WHALE) $@" @PATH=${ROOTDIR}/bin:${PATH} go generate -x ${PACKAGES} protos: bin/protoc-gen-gogoctrd bin/ctrd-protobuild ## generate protobuf - @echo "🐳 $@" + @echo "$(WHALE) $@" @PATH=${ROOTDIR}/bin:${PATH} ctrd-protobuild ${PACKAGES} checkprotos: protos ## check if protobufs needs to be generated again - @echo "🐳 $@" + @echo "$(WHALE) $@" @test -z "$$(git status --short | grep ".pb.go" | tee /dev/stderr)" || \ ((git diff | cat) && \ - (echo "👹 please run 'make generate' when making changes to proto files" && false)) + (echo "$(ONI) please run 'make generate' when making changes to proto files" && false)) # Depends on binaries because vet will silently fail if it can't load compiled # imports vet: binaries ## run go vet - @echo "🐳 $@" + @echo "$(WHALE) $@" @test -z "$$(go vet ${PACKAGES} 2>&1 | grep -v 'constant [0-9]* not a string in call to Errorf' | egrep -v '(timestamp_test.go|duration_test.go|exit status 1)' | tee /dev/stderr)" fmt: ## run go fmt - @echo "🐳 $@" + @echo "$(WHALE) $@" @test -z "$$(gofmt -s -l . | grep -v vendor/ | grep -v ".pb.go$$" | tee /dev/stderr)" || \ - (echo "👹 please format Go code with 'gofmt -s -w'" && false) + (echo "$(ONI) please format Go code with 'gofmt -s -w'" && false) @test -z "$$(find . -path ./vendor -prune -o ! -name timestamp.proto ! -name duration.proto -name '*.proto' -type f -exec grep -Hn -e "^ " {} \; | tee /dev/stderr)" || \ - (echo "👹 please indent proto files with tabs only" && false) + (echo "$(ONI) please indent proto files with tabs only" && false) @test -z "$$(find . -path ./vendor -prune -o -name '*.proto' -type f -exec grep -EHn "[_ ]id = " {} \; | grep -v gogoproto.customname | tee /dev/stderr)" || \ - (echo "👹 id fields in proto files must have a gogoproto.customname set" && false) + (echo "$(ONI) id fields in proto files must have a gogoproto.customname set" && false) @test -z "$$(find . -path ./vendor -prune -o -name '*.proto' -type f -exec grep -Hn "Meta meta = " {} \; | grep -v '(gogoproto.nullable) = false' | tee /dev/stderr)" || \ - (echo "👹 meta fields in proto files must have option (gogoproto.nullable) = false" && false) + (echo "$(ONI) meta fields in proto files must have option (gogoproto.nullable) = false" && false) lint: ## run go lint - @echo "🐳 $@" + @echo "$(WHALE) $@" @test -z "$$(golint ./... | grep -v vendor/ | grep -v ".pb.go:" | tee /dev/stderr)" dco: ## dco check @@ -86,27 +101,27 @@ else endif ineffassign: ## run ineffassign - @echo "🐳 $@" + @echo "$(WHALE) $@" @test -z "$$(ineffassign . | grep -v vendor/ | grep -v ".pb.go:" | tee /dev/stderr)" #errcheck: ## run go errcheck -# @echo "🐳 $@" +# @echo "$(WHALE) $@" # @test -z "$$(errcheck ./... | grep -v vendor/ | grep -v ".pb.go:" | tee /dev/stderr)" build: ## build the go packages - @echo "🐳 $@" + @echo "$(WHALE) $@" @go build -i -v ${GO_LDFLAGS} ${GO_GCFLAGS} ${PACKAGES} test: ## run tests, except integration tests and tests that require root - @echo "🐳 $@" + @echo "$(WHALE) $@" @go test ${TESTFLAGS} $(filter-out ${INTEGRATION_PACKAGE},${PACKAGES}) root-test: ## run tests, except integration tests - @echo "🐳 $@" + @echo "$(WHALE) $@" @go test ${TESTFLAGS} ${SNAPSHOT_PACKAGES} -test.root integration: ## run integration tests - @echo "🐳 $@" + @echo "$(WHALE) $@" @go test ${TESTFLAGS} ${INTEGRATION_PACKAGE} FORCE: @@ -114,29 +129,29 @@ FORCE: # Build a binary from a cmd. bin/%: cmd/% FORCE @test $$(go list) = "${PKG}" || \ - (echo "👹 Please correctly set up your Go build environment. This project must be located at /src/${PKG}" && false) - @echo "🐳 $@" - @go build -i -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./$< + (echo "$(ONI) Please correctly set up your Go build environment. This project must be located at /src/${PKG}" && false) + @echo "$(WHALE) $@${BINARY_SUFFIX}" + @go build -i -o $@${BINARY_SUFFIX} ${GO_LDFLAGS} ${GO_GCFLAGS} ./$< binaries: $(BINARIES) ## build binaries - @echo "🐳 $@" + @echo "$(WHALE) $@" clean: ## clean up binaries - @echo "🐳 $@" + @echo "$(WHALE) $@" @rm -f $(BINARIES) install: ## install binaries - @echo "🐳 $@ $(BINARIES)" + @echo "$(WHALE) $@ $(BINARIES)" @mkdir -p $(DESTDIR)/bin @install $(BINARIES) $(DESTDIR)/bin uninstall: - @echo "🐳 $@" + @echo "$(WHALE) $@" @rm -f $(addprefix $(DESTDIR)/bin/,$(notdir $(BINARIES))) coverage: ## generate coverprofiles from the unit tests, except tests that require root - @echo "🐳 $@" + @echo "$(WHALE) $@" @rm -f coverage.txt ( for pkg in $(filter-out ${INTEGRATION_PACKAGE},${PACKAGES}); do \ go test -i ${TESTFLAGS} -test.short -coverprofile=coverage.out -covermode=atomic $$pkg || exit; \ @@ -152,18 +167,18 @@ coverage: ## generate coverprofiles from the unit tests, except tests that requi done ) root-coverage: ## generae coverage profiles for the unit tests - @echo "🐳 $@" + @echo "$(WHALE) $@" @( for pkg in ${SNAPSHOT_PACKAGES}; do \ go test -i ${TESTFLAGS} -test.short -coverprofile="../../../$$pkg/coverage.txt" -covermode=atomic $$pkg -test.root || exit; \ go test ${TESTFLAGS} -test.short -coverprofile="../../../$$pkg/coverage.txt" -covermode=atomic $$pkg -test.root || exit; \ done ) coverage-integration: ## generate coverprofiles from the integration tests - @echo "🐳 $@" + @echo "$(WHALE) $@" go test ${TESTFLAGS} -test.short -coverprofile="../../../${INTEGRATION_PACKAGE}/coverage.txt" -covermode=atomic ${INTEGRATION_PACKAGE} vendor: - @echo "🐳 $@" + @echo "$(WHALE) $@" @vndr help: ## this help diff --git a/cmd/containerd-shim/main.go b/cmd/containerd-shim/main_unix.go similarity index 99% rename from cmd/containerd-shim/main.go rename to cmd/containerd-shim/main_unix.go index cfdac12..8079987 100644 --- a/cmd/containerd-shim/main.go +++ b/cmd/containerd-shim/main_unix.go @@ -1,3 +1,5 @@ +// +build !windows + package main import ( diff --git a/cmd/containerd/builtins.go b/cmd/containerd/builtins.go index 793e446..2ae7a09 100644 --- a/cmd/containerd/builtins.go +++ b/cmd/containerd/builtins.go @@ -2,14 +2,10 @@ package main // register containerd builtins here import ( - _ "github.com/containerd/containerd/linux" - _ "github.com/containerd/containerd/metrics/cgroups" _ "github.com/containerd/containerd/services/content" _ "github.com/containerd/containerd/services/execution" _ "github.com/containerd/containerd/services/healthcheck" _ "github.com/containerd/containerd/services/images" _ "github.com/containerd/containerd/services/metrics" _ "github.com/containerd/containerd/services/rootfs" - _ "github.com/containerd/containerd/snapshot/btrfs" - _ "github.com/containerd/containerd/snapshot/overlay" ) diff --git a/cmd/containerd/builtins_linux.go b/cmd/containerd/builtins_linux.go new file mode 100644 index 0000000..fa26830 --- /dev/null +++ b/cmd/containerd/builtins_linux.go @@ -0,0 +1,8 @@ +package main + +import ( + _ "github.com/containerd/containerd/linux" + _ "github.com/containerd/containerd/metrics/cgroups" + _ "github.com/containerd/containerd/snapshot/btrfs" + _ "github.com/containerd/containerd/snapshot/overlay" +) diff --git a/cmd/containerd/builtins_windows.go b/cmd/containerd/builtins_windows.go new file mode 100644 index 0000000..a1bfa0f --- /dev/null +++ b/cmd/containerd/builtins_windows.go @@ -0,0 +1,6 @@ +package main + +import ( + _ "github.com/containerd/containerd/snapshot/windows" + _ "github.com/containerd/containerd/windows" +) diff --git a/cmd/containerd/config.go b/cmd/containerd/config.go index 82187f7..b1ef45b 100644 --- a/cmd/containerd/config.go +++ b/cmd/containerd/config.go @@ -7,21 +7,6 @@ import ( "github.com/BurntSushi/toml" ) -func defaultConfig() *config { - return &config{ - Root: "/var/lib/containerd", - State: "/run/containerd", - GRPC: grpcConfig{ - Socket: "/run/containerd/containerd.sock", - }, - Debug: debug{ - Level: "info", - Socket: "/run/containerd/debug.sock", - }, - Snapshotter: "overlay", - } -} - // loadConfig loads the config from the provided path func loadConfig(path string) error { md, err := toml.DecodeFile(path, conf) @@ -73,14 +58,14 @@ func (c *config) WriteTo(w io.Writer) (int64, error) { } type grpcConfig struct { - Socket string `toml:"socket"` - Uid int `toml:"uid"` - Gid int `toml:"gid"` + Address string `toml:"address"` + Uid int `toml:"uid"` + Gid int `toml:"gid"` } type debug struct { - Socket string `toml:"socket"` - Level string `toml:"level"` + Address string `toml:"address"` + Level string `toml:"level"` } type metricsConfig struct { diff --git a/cmd/containerd/config_unix.go b/cmd/containerd/config_unix.go new file mode 100644 index 0000000..7cb0062 --- /dev/null +++ b/cmd/containerd/config_unix.go @@ -0,0 +1,18 @@ +// +build !windows + +package main + +func defaultConfig() *config { + return &config{ + Root: "/var/lib/containerd", + State: "/run/containerd", + GRPC: grpcConfig{ + Address: "/run/containerd/containerd.sock", + }, + Debug: debug{ + Level: "info", + Address: "/run/containerd/debug.sock", + }, + Snapshotter: "overlay", + } +} diff --git a/cmd/containerd/config_windows.go b/cmd/containerd/config_windows.go new file mode 100644 index 0000000..eed74d1 --- /dev/null +++ b/cmd/containerd/config_windows.go @@ -0,0 +1,21 @@ +package main + +import ( + "os" + "path/filepath" +) + +func defaultConfig() *config { + return &config{ + Root: filepath.Join(os.Getenv("programfiles"), "containerd", "root"), + State: filepath.Join(os.Getenv("programfiles"), "containerd", "state"), + GRPC: grpcConfig{ + Address: `\\.\pipe\containerd-containerd`, + }, + Debug: debug{ + Level: "info", + Address: `\\.\pipe\containerd-debug`, + }, + Snapshotter: "windows", + } +} diff --git a/cmd/containerd/main.go b/cmd/containerd/main.go index 962888b..c3f25d3 100644 --- a/cmd/containerd/main.go +++ b/cmd/containerd/main.go @@ -9,7 +9,6 @@ import ( "os" "os/signal" "path/filepath" - "syscall" "time" "github.com/boltdb/bolt" @@ -27,7 +26,6 @@ import ( "github.com/containerd/containerd/images" "github.com/containerd/containerd/log" "github.com/containerd/containerd/plugin" - "github.com/containerd/containerd/reaper" "github.com/containerd/containerd/snapshot" "github.com/containerd/containerd/sys" metrics "github.com/docker/go-metrics" @@ -65,7 +63,7 @@ func main() { cli.StringFlag{ Name: "config,c", Usage: "path to the configuration file (Use 'default' to output the default toml)", - Value: "/etc/containerd/config.toml", + Value: defaultConfigPath, }, cli.StringFlag{ Name: "log-level,l", @@ -76,8 +74,8 @@ func main() { Usage: "containerd state directory", }, cli.StringFlag{ - Name: "socket,s", - Usage: "socket path for containerd's GRPC server", + Name: "address,a", + Usage: "address for containerd's GRPC server", }, cli.StringFlag{ Name: "root", @@ -90,12 +88,9 @@ func main() { // start the signal handler as soon as we can to make sure that // we don't miss any signals during boot signals := make(chan os.Signal, 2048) - signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT, syscall.SIGUSR1, syscall.SIGCHLD) - if conf.Subreaper { - log.G(global).Info("setting subreaper...") - if err := sys.SetSubreaper(1); err != nil { - return err - } + signal.Notify(signals, handledSignals...) + if err := platformInit(context); err != nil { + return err } log.G(global).Info("starting containerd boot...") @@ -204,8 +199,8 @@ func before(context *cli.Context) error { d: &conf.State, }, { - name: "socket", - d: &conf.GRPC.Socket, + name: "address", + d: &conf.GRPC.Address, }, } { if s := context.GlobalString(v.name); s != "" { @@ -249,11 +244,11 @@ func newMetricsHandler() http.Handler { } func serveDebugAPI() error { - path := conf.Debug.Socket + path := conf.Debug.Address if path == "" { return errors.New("debug socket path cannot be empty") } - l, err := sys.CreateUnixSocket(path) + l, err := sys.GetLocalListener(path, conf.GRPC.Uid, conf.GRPC.Gid) if err != nil { return err } @@ -415,17 +410,14 @@ func loadServices(runtimes map[string]containerd.Runtime, store *content.Store, } func serveGRPC(server *grpc.Server) error { - path := conf.GRPC.Socket + path := conf.GRPC.Address if path == "" { return errors.New("--socket path cannot be empty") } - l, err := sys.CreateUnixSocket(path) + l, err := sys.GetLocalListener(path, conf.GRPC.Uid, conf.GRPC.Gid) if err != nil { return err } - if err := os.Chown(path, conf.GRPC.Uid, conf.GRPC.Gid); err != nil { - return err - } go func() { defer l.Close() if err := server.Serve(l); err != nil { @@ -455,19 +447,3 @@ func interceptor(ctx gocontext.Context, } return grpc_prometheus.UnaryServerInterceptor(ctx, req, info, handler) } - -func handleSignals(signals chan os.Signal, server *grpc.Server) error { - for s := range signals { - log.G(global).WithField("signal", s).Debug("received signal") - switch s { - case syscall.SIGCHLD: - if err := reaper.Reap(); err != nil { - log.G(global).WithError(err).Error("reap containerd processes") - } - default: - server.Stop() - return nil - } - } - return nil -} diff --git a/cmd/containerd/main_unix.go b/cmd/containerd/main_unix.go new file mode 100644 index 0000000..074d095 --- /dev/null +++ b/cmd/containerd/main_unix.go @@ -0,0 +1,48 @@ +// +build !windows + +package main + +import ( + "os" + "syscall" + + "github.com/containerd/containerd/log" + "github.com/containerd/containerd/reaper" + "github.com/containerd/containerd/sys" + "github.com/urfave/cli" + "google.golang.org/grpc" +) + +const ( + defaultConfigPath = "/etc/containerd/config.toml" +) + +var ( + handledSignals = []os.Signal{syscall.SIGTERM, syscall.SIGINT, syscall.SIGUSR1, syscall.SIGCHLD} +) + +func platformInit(context *cli.Context) error { + if conf.Subreaper { + log.G(global).Info("setting subreaper...") + if err := sys.SetSubreaper(1); err != nil { + return err + } + } + return nil +} + +func handleSignals(signals chan os.Signal, server *grpc.Server) error { + for s := range signals { + log.G(global).WithField("signal", s).Debug("received signal") + switch s { + case syscall.SIGCHLD: + if err := reaper.Reap(); err != nil { + log.G(global).WithError(err).Error("reap containerd processes") + } + default: + server.Stop() + return nil + } + } + return nil +} diff --git a/cmd/containerd/main_windows.go b/cmd/containerd/main_windows.go new file mode 100644 index 0000000..47ef177 --- /dev/null +++ b/cmd/containerd/main_windows.go @@ -0,0 +1,28 @@ +package main + +import ( + "os" + "path/filepath" + "syscall" + + "github.com/containerd/containerd/log" + "github.com/urfave/cli" + "google.golang.org/grpc" +) + +var ( + defaultConfigPath = filepath.Join(os.Getenv("programfiles"), "containerd", "config.toml") + handledSignals = []os.Signal{syscall.SIGTERM, syscall.SIGINT} +) + +func platformInit(context *cli.Context) error { + return nil +} + +func handleSignals(signals chan os.Signal, server *grpc.Server) error { + for s := range signals { + log.G(global).WithField("signal", s).Debug("received signal") + server.Stop() + } + return nil +} diff --git a/cmd/ctr/exec.go b/cmd/ctr/exec.go index 3d64993..527f801 100644 --- a/cmd/ctr/exec.go +++ b/cmd/ctr/exec.go @@ -2,14 +2,10 @@ package main import ( gocontext "context" - "encoding/json" "os" - "path/filepath" "github.com/containerd/containerd/api/services/execution" "github.com/crosbymichael/console" - protobuf "github.com/gogo/protobuf/types" - specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/urfave/cli" ) @@ -21,6 +17,10 @@ var execCommand = cli.Command{ Name: "id", Usage: "id of the container", }, + cli.StringFlag{ + Name: "cwd", + Usage: "working directory of the new process", + }, cli.BoolFlag{ Name: "tty,t", Usage: "allocate a TTY for the container", @@ -32,11 +32,6 @@ var execCommand = cli.Command{ ctx = gocontext.Background() ) - process := createProcess(context.Args(), "", context.Bool("tty")) - data, err := json.Marshal(process) - if err != nil { - return err - } containers, err := getExecutionService(context) if err != nil { return err @@ -50,16 +45,9 @@ var execCommand = cli.Command{ return err } defer os.RemoveAll(tmpDir) - request := &execution.ExecRequest{ - ID: id, - Spec: &protobuf.Any{ - TypeUrl: specs.Version, - Value: data, - }, - Terminal: context.Bool("tty"), - Stdin: filepath.Join(tmpDir, "stdin"), - Stdout: filepath.Join(tmpDir, "stdout"), - Stderr: filepath.Join(tmpDir, "stderr"), + request, err := newExecRequest(context, tmpDir, id) + if err != nil { + return err } if request.Terminal { con := console.Current() @@ -89,40 +77,3 @@ var execCommand = cli.Command{ return nil }, } - -func createProcess(args []string, cwd string, tty bool) specs.Process { - env := []string{ - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - } - if tty { - env = append(env, "TERM=xterm") - } - if cwd == "" { - cwd = "/" - } - return specs.Process{ - Args: args, - Env: env, - Terminal: tty, - Cwd: cwd, - NoNewPrivileges: true, - User: specs.User{ - UID: 0, - GID: 0, - }, - Capabilities: &specs.LinuxCapabilities{ - Bounding: capabilities, - Permitted: capabilities, - Inheritable: capabilities, - Effective: capabilities, - Ambient: capabilities, - }, - Rlimits: []specs.LinuxRlimit{ - { - Type: "RLIMIT_NOFILE", - Hard: uint64(1024), - Soft: uint64(1024), - }, - }, - } -} diff --git a/cmd/ctr/exec_unix.go b/cmd/ctr/exec_unix.go new file mode 100644 index 0000000..086cda0 --- /dev/null +++ b/cmd/ctr/exec_unix.go @@ -0,0 +1,69 @@ +// +build !windows + +package main + +import ( + "encoding/json" + "path/filepath" + + "github.com/containerd/containerd/api/services/execution" + protobuf "github.com/gogo/protobuf/types" + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/urfave/cli" +) + +func createProcessSpec(args []string, cwd string, tty bool) specs.Process { + env := []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + } + if tty { + env = append(env, "TERM=xterm") + } + if cwd == "" { + cwd = "/" + } + return specs.Process{ + Args: args, + Env: env, + Terminal: tty, + Cwd: cwd, + NoNewPrivileges: true, + User: specs.User{ + UID: 0, + GID: 0, + }, + Capabilities: &specs.LinuxCapabilities{ + Bounding: capabilities, + Permitted: capabilities, + Inheritable: capabilities, + Effective: capabilities, + Ambient: capabilities, + }, + Rlimits: []specs.LinuxRlimit{ + { + Type: "RLIMIT_NOFILE", + Hard: uint64(1024), + Soft: uint64(1024), + }, + }, + } +} + +func newExecRequest(context *cli.Context, tmpDir, id string) (*execution.ExecRequest, error) { + process := createProcessSpec(context.Args(), context.String("cwd"), context.Bool("tty")) + data, err := json.Marshal(process) + if err != nil { + return nil, err + } + return &execution.ExecRequest{ + ID: id, + Spec: &protobuf.Any{ + TypeUrl: specs.Version, + Value: data, + }, + Terminal: context.Bool("tty"), + Stdin: filepath.Join(tmpDir, "stdin"), + Stdout: filepath.Join(tmpDir, "stdout"), + Stderr: filepath.Join(tmpDir, "stderr"), + }, nil +} diff --git a/cmd/ctr/exec_windows.go b/cmd/ctr/exec_windows.go new file mode 100644 index 0000000..fcbd498 --- /dev/null +++ b/cmd/ctr/exec_windows.go @@ -0,0 +1,40 @@ +package main + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/containerd/containerd/api/services/execution" + protobuf "github.com/gogo/protobuf/types" + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/urfave/cli" +) + +func newExecRequest(context *cli.Context, tmpDir, id string) (*execution.ExecRequest, error) { + process := specs.Process{ + Args: context.Args(), + Terminal: context.Bool("tty"), + Cwd: context.String("cwd"), + } + data, err := json.Marshal(process) + if err != nil { + return nil, err + } + now := time.Now().UnixNano() + request := &execution.ExecRequest{ + ID: id, + Spec: &protobuf.Any{ + TypeUrl: specs.Version, + Value: data, + }, + Terminal: context.Bool("tty"), + Stdin: fmt.Sprintf(`%s\ctr-%s-stdin-`, pipeRoot, id, now), + Stdout: fmt.Sprintf(`%s\ctr-%s-stdout-`, pipeRoot, id, now), + } + if !request.Terminal { + request.Stderr = fmt.Sprintf(`%s\ctr-%s-stderr-`, pipeRoot, id, now) + } + + return request, nil +} diff --git a/cmd/ctr/kill.go b/cmd/ctr/kill.go index 1accd88..a6d7281 100644 --- a/cmd/ctr/kill.go +++ b/cmd/ctr/kill.go @@ -2,54 +2,12 @@ package main import ( gocontext "context" - "fmt" - "strconv" - "strings" - "syscall" "github.com/containerd/containerd/api/services/execution" "github.com/pkg/errors" "github.com/urfave/cli" ) -var signalMap = map[string]syscall.Signal{ - "ABRT": syscall.SIGABRT, - "ALRM": syscall.SIGALRM, - "BUS": syscall.SIGBUS, - "CHLD": syscall.SIGCHLD, - "CLD": syscall.SIGCLD, - "CONT": syscall.SIGCONT, - "FPE": syscall.SIGFPE, - "HUP": syscall.SIGHUP, - "ILL": syscall.SIGILL, - "INT": syscall.SIGINT, - "IO": syscall.SIGIO, - "IOT": syscall.SIGIOT, - "KILL": syscall.SIGKILL, - "PIPE": syscall.SIGPIPE, - "POLL": syscall.SIGPOLL, - "PROF": syscall.SIGPROF, - "PWR": syscall.SIGPWR, - "QUIT": syscall.SIGQUIT, - "SEGV": syscall.SIGSEGV, - "STKFLT": syscall.SIGSTKFLT, - "STOP": syscall.SIGSTOP, - "SYS": syscall.SIGSYS, - "TERM": syscall.SIGTERM, - "TRAP": syscall.SIGTRAP, - "TSTP": syscall.SIGTSTP, - "TTIN": syscall.SIGTTIN, - "TTOU": syscall.SIGTTOU, - "UNUSED": syscall.SIGUNUSED, - "URG": syscall.SIGURG, - "USR1": syscall.SIGUSR1, - "USR2": syscall.SIGUSR2, - "VTALRM": syscall.SIGVTALRM, - "WINCH": syscall.SIGWINCH, - "XCPU": syscall.SIGXCPU, - "XFSZ": syscall.SIGXFSZ, -} - var killCommand = cli.Command{ Name: "kill", Usage: "signal a container", @@ -95,21 +53,3 @@ var killCommand = cli.Command{ return nil }, } - -func parseSignal(rawSignal string) (syscall.Signal, error) { - s, err := strconv.Atoi(rawSignal) - if err == nil { - sig := syscall.Signal(s) - for _, msig := range signalMap { - if sig == msig { - return sig, nil - } - } - return -1, fmt.Errorf("unknown signal %q", rawSignal) - } - signal, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")] - if !ok { - return -1, fmt.Errorf("unknown signal %q", rawSignal) - } - return signal, nil -} diff --git a/cmd/ctr/kill_unix.go b/cmd/ctr/kill_unix.go new file mode 100644 index 0000000..3dee6e2 --- /dev/null +++ b/cmd/ctr/kill_unix.go @@ -0,0 +1,66 @@ +// +build !windows + +package main + +import ( + "fmt" + "strconv" + "strings" + "syscall" +) + +var signalMap = map[string]syscall.Signal{ + "ABRT": syscall.SIGABRT, + "ALRM": syscall.SIGALRM, + "BUS": syscall.SIGBUS, + "CHLD": syscall.SIGCHLD, + "CLD": syscall.SIGCLD, + "CONT": syscall.SIGCONT, + "FPE": syscall.SIGFPE, + "HUP": syscall.SIGHUP, + "ILL": syscall.SIGILL, + "INT": syscall.SIGINT, + "IO": syscall.SIGIO, + "IOT": syscall.SIGIOT, + "KILL": syscall.SIGKILL, + "PIPE": syscall.SIGPIPE, + "POLL": syscall.SIGPOLL, + "PROF": syscall.SIGPROF, + "PWR": syscall.SIGPWR, + "QUIT": syscall.SIGQUIT, + "SEGV": syscall.SIGSEGV, + "STKFLT": syscall.SIGSTKFLT, + "STOP": syscall.SIGSTOP, + "SYS": syscall.SIGSYS, + "TERM": syscall.SIGTERM, + "TRAP": syscall.SIGTRAP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "UNUSED": syscall.SIGUNUSED, + "URG": syscall.SIGURG, + "USR1": syscall.SIGUSR1, + "USR2": syscall.SIGUSR2, + "VTALRM": syscall.SIGVTALRM, + "WINCH": syscall.SIGWINCH, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, +} + +func parseSignal(rawSignal string) (syscall.Signal, error) { + s, err := strconv.Atoi(rawSignal) + if err == nil { + sig := syscall.Signal(s) + for _, msig := range signalMap { + if sig == msig { + return sig, nil + } + } + return -1, fmt.Errorf("unknown signal %q", rawSignal) + } + signal, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")] + if !ok { + return -1, fmt.Errorf("unknown signal %q", rawSignal) + } + return signal, nil +} diff --git a/cmd/ctr/kill_windows.go b/cmd/ctr/kill_windows.go new file mode 100644 index 0000000..a59618d --- /dev/null +++ b/cmd/ctr/kill_windows.go @@ -0,0 +1,7 @@ +package main + +import "syscall" + +func parseSignal(rawSignal string) (syscall.Signal, error) { + return syscall.SIGKILL, nil +} diff --git a/cmd/ctr/main.go b/cmd/ctr/main.go index c9197fe..7df917e 100644 --- a/cmd/ctr/main.go +++ b/cmd/ctr/main.go @@ -9,6 +9,10 @@ import ( "github.com/urfave/cli" ) +var ( + extraCmds = []cli.Command{} +) + func init() { cli.VersionPrinter = func(c *cli.Context) { fmt.Println(c.App.Name, containerd.Package, c.App.Version) @@ -34,8 +38,8 @@ containerd client Usage: "enable debug output in logs", }, cli.StringFlag{ - Name: "socket, s", - Usage: "socket path for containerd's GRPC server", + Name: "address, s", + Usage: "address for containerd's GRPC server", Value: "/run/containerd/containerd.sock", }, cli.StringFlag{ @@ -53,10 +57,10 @@ containerd client listCommand, infoCommand, killCommand, - shimCommand, pprofCommand, execCommand, } + app.Commands = append(app.Commands, extraCmds...) app.Before = func(context *cli.Context) error { if context.GlobalBool("debug") { logrus.SetLevel(logrus.DebugLevel) diff --git a/cmd/ctr/main_unix.go b/cmd/ctr/main_unix.go new file mode 100644 index 0000000..a8c5195 --- /dev/null +++ b/cmd/ctr/main_unix.go @@ -0,0 +1,7 @@ +// +build !windows + +package main + +func init() { + extraCmds = append(extraCmds, shimCommand) +} diff --git a/cmd/ctr/run.go b/cmd/ctr/run.go index 0da5d91..38235de 100644 --- a/cmd/ctr/run.go +++ b/cmd/ctr/run.go @@ -5,10 +5,7 @@ import ( "encoding/json" "fmt" "os" - "path/filepath" "runtime" - "strconv" - "strings" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -17,208 +14,12 @@ import ( rootfsapi "github.com/containerd/containerd/api/services/rootfs" "github.com/containerd/containerd/images" "github.com/crosbymichael/console" - protobuf "github.com/gogo/protobuf/types" "github.com/opencontainers/image-spec/identity" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/urfave/cli" ) -const ( - rwm = "rwm" - rootfsPath = "rootfs" -) - -var capabilities = []string{ - "CAP_CHOWN", - "CAP_DAC_OVERRIDE", - "CAP_FSETID", - "CAP_FOWNER", - "CAP_MKNOD", - "CAP_NET_RAW", - "CAP_SETGID", - "CAP_SETUID", - "CAP_SETFCAP", - "CAP_SETPCAP", - "CAP_NET_BIND_SERVICE", - "CAP_SYS_CHROOT", - "CAP_KILL", - "CAP_AUDIT_WRITE", -} - -func spec(id string, config *ocispec.ImageConfig, context *cli.Context) (*specs.Spec, error) { - env := []string{ - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - } - env = append(env, config.Env...) - cmd := config.Cmd - if v := context.Args().Tail(); len(v) > 0 { - cmd = v - } - var ( - // TODO: support overriding entrypoint - args = append(config.Entrypoint, cmd...) - tty = context.Bool("tty") - uid, gid uint32 - ) - if config.User != "" { - parts := strings.Split(config.User, ":") - switch len(parts) { - case 1: - v, err := strconv.ParseUint(parts[0], 0, 10) - if err != nil { - return nil, err - } - uid, gid = uint32(v), uint32(v) - case 2: - v, err := strconv.ParseUint(parts[0], 0, 10) - if err != nil { - return nil, err - } - uid = uint32(v) - if v, err = strconv.ParseUint(parts[1], 0, 10); err != nil { - return nil, err - } - gid = uint32(v) - default: - return nil, fmt.Errorf("invalid USER value %s", config.User) - } - } - if tty { - env = append(env, "TERM=xterm") - } - cwd := config.WorkingDir - if cwd == "" { - cwd = "/" - } - return &specs.Spec{ - Version: specs.Version, - Platform: specs.Platform{ - OS: runtime.GOOS, - Arch: runtime.GOARCH, - }, - Root: specs.Root{ - Path: rootfsPath, - Readonly: context.Bool("readonly"), - }, - Process: specs.Process{ - Args: args, - Env: env, - Terminal: tty, - Cwd: cwd, - NoNewPrivileges: true, - User: specs.User{ - UID: uid, - GID: gid, - }, - Capabilities: &specs.LinuxCapabilities{ - Bounding: capabilities, - Permitted: capabilities, - Inheritable: capabilities, - Effective: capabilities, - Ambient: capabilities, - }, - Rlimits: []specs.LinuxRlimit{ - { - Type: "RLIMIT_NOFILE", - Hard: uint64(1024), - Soft: uint64(1024), - }, - }, - }, - Mounts: []specs.Mount{ - { - Destination: "/proc", - Type: "proc", - Source: "proc", - }, - { - Destination: "/dev", - Type: "tmpfs", - Source: "tmpfs", - Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"}, - }, - { - Destination: "/dev/pts", - Type: "devpts", - Source: "devpts", - Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"}, - }, - { - Destination: "/dev/shm", - Type: "tmpfs", - Source: "shm", - Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"}, - }, - { - Destination: "/dev/mqueue", - Type: "mqueue", - Source: "mqueue", - Options: []string{"nosuid", "noexec", "nodev"}, - }, - { - Destination: "/sys", - Type: "sysfs", - Source: "sysfs", - Options: []string{"nosuid", "noexec", "nodev", "ro"}, - }, - { - Destination: "/run", - Type: "tmpfs", - Source: "tmpfs", - Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"}, - }, - { - Destination: "/etc/resolv.conf", - Type: "bind", - Source: "/etc/resolv.conf", - Options: []string{"rbind", "ro"}, - }, - { - Destination: "/etc/hosts", - Type: "bind", - Source: "/etc/hosts", - Options: []string{"rbind", "ro"}, - }, - { - Destination: "/etc/localtime", - Type: "bind", - Source: "/etc/localtime", - Options: []string{"rbind", "ro"}, - }, - }, - Hostname: id, - Linux: &specs.Linux{ - Resources: &specs.LinuxResources{ - Devices: []specs.LinuxDeviceCgroup{ - { - Allow: false, - Access: rwm, - }, - }, - }, - Namespaces: []specs.LinuxNamespace{ - { - Type: "pid", - }, - { - Type: "ipc", - }, - { - Type: "uts", - }, - { - Type: "mount", - }, - { - Type: "network", - }, - }, - }, - }, nil -} - var runCommand = cli.Command{ Name: "run", Usage: "run a container", @@ -232,10 +33,6 @@ var runCommand = cli.Command{ Name: "tty,t", Usage: "allocate a TTY for the container", }, - cli.StringFlag{ - Name: "rootfs,r", - Usage: "path to the container's root filesystem", - }, cli.StringFlag{ Name: "runtime", Usage: "runtime name (linux, windows, vmware-linux)", @@ -247,12 +44,17 @@ var runCommand = cli.Command{ }, }, Action: func(context *cli.Context) error { - ctx := gocontext.Background() - id := context.String("id") + var ( + err error + resp *rootfsapi.MountResponse + imageConfig ocispec.Image + + ctx = gocontext.Background() + id = context.String("id") + ) if id == "" { return errors.New("container id must be provided") } - containers, err := getExecutionService(context) if err != nil { return err @@ -282,76 +84,64 @@ var runCommand = cli.Command{ return errors.Wrap(err, "failed resolving image store") } - ref := context.Args().First() + if runtime.GOOS != "windows" { + ref := context.Args().First() - image, err := imageStore.Get(ctx, ref) - if err != nil { - return errors.Wrapf(err, "could not resolve %q", ref) - } - // let's close out our db and tx so we don't hold the lock whilst running. - - diffIDs, err := image.RootFS(ctx, provider) - if err != nil { - return err - } - - if _, err := rootfsClient.Prepare(gocontext.TODO(), &rootfsapi.PrepareRequest{ - Name: id, - ChainID: identity.ChainID(diffIDs), - }); err != nil { - if grpc.Code(err) != codes.AlreadyExists { - return err + image, err := imageStore.Get(ctx, ref) + if err != nil { + return errors.Wrapf(err, "could not resolve %q", ref) } - } + // let's close out our db and tx so we don't hold the lock whilst running. - resp, err := rootfsClient.Mounts(gocontext.TODO(), &rootfsapi.MountsRequest{ - Name: id, - }) - if err != nil { - return err - } - - ic, err := image.Config(ctx, provider) - if err != nil { - return err - } - var imageConfig ocispec.Image - switch ic.MediaType { - case ocispec.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config: - r, err := provider.Reader(ctx, ic.Digest) + diffIDs, err := image.RootFS(ctx, provider) if err != nil { return err } - if err := json.NewDecoder(r).Decode(&imageConfig); err != nil { - r.Close() + + if _, err := rootfsClient.Prepare(gocontext.TODO(), &rootfsapi.PrepareRequest{ + Name: id, + ChainID: identity.ChainID(diffIDs), + }); err != nil { + if grpc.Code(err) != codes.AlreadyExists { + return err + } + } + + resp, err = rootfsClient.Mounts(gocontext.TODO(), &rootfsapi.MountsRequest{ + Name: id, + }) + if err != nil { return err } - r.Close() - default: - return fmt.Errorf("unknown image config media type %s", ic.MediaType) + + ic, err := image.Config(ctx, provider) + if err != nil { + return err + } + switch ic.MediaType { + case ocispec.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config: + r, err := provider.Reader(ctx, ic.Digest) + if err != nil { + return err + } + if err := json.NewDecoder(r).Decode(&imageConfig); err != nil { + r.Close() + return err + } + r.Close() + default: + return fmt.Errorf("unknown image config media type %s", ic.MediaType) + } + } else { + // TODO: get the image / rootfs through the API once windows has a snapshotter } - rootfs := resp.Mounts - // generate the spec based on the image config - s, err := spec(id, &imageConfig.Config, context) + + create, err := newCreateRequest(context, &imageConfig.Config, id, tmpDir) if err != nil { return err } - data, err := json.Marshal(s) - if err != nil { - return err - } - create := &execution.CreateRequest{ - ID: id, - Spec: &protobuf.Any{ - TypeUrl: specs.Version, - Value: data, - }, - Rootfs: rootfs, - Runtime: context.String("runtime"), - Terminal: context.Bool("tty"), - Stdin: filepath.Join(tmpDir, "stdin"), - Stdout: filepath.Join(tmpDir, "stdout"), - Stderr: filepath.Join(tmpDir, "stderr"), + if resp != nil { + create.Rootfs = resp.Mounts } if create.Terminal { con := console.Current() @@ -360,6 +150,7 @@ var runCommand = cli.Command{ return err } } + fwg, err := prepareStdio(create.Stdin, create.Stdout, create.Stderr, create.Terminal) if err != nil { return err diff --git a/cmd/ctr/run_unix.go b/cmd/ctr/run_unix.go new file mode 100644 index 0000000..b9bdd22 --- /dev/null +++ b/cmd/ctr/run_unix.go @@ -0,0 +1,264 @@ +// +build !windows + +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "path/filepath" + "runtime" + "strconv" + "strings" + + "github.com/Sirupsen/logrus" + "github.com/containerd/containerd/api/services/execution" + protobuf "github.com/gogo/protobuf/types" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/urfave/cli" +) + +const ( + rwm = "rwm" + rootfsPath = "rootfs" +) + +var capabilities = []string{ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE", +} + +func spec(id string, config *ocispec.ImageConfig, context *cli.Context) (*specs.Spec, error) { + env := []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + } + env = append(env, config.Env...) + cmd := config.Cmd + if v := context.Args().Tail(); len(v) > 0 { + cmd = v + } + var ( + // TODO: support overriding entrypoint + args = append(config.Entrypoint, cmd...) + tty = context.Bool("tty") + uid, gid uint32 + ) + if config.User != "" { + parts := strings.Split(config.User, ":") + switch len(parts) { + case 1: + v, err := strconv.ParseUint(parts[0], 0, 10) + if err != nil { + return nil, err + } + uid, gid = uint32(v), uint32(v) + case 2: + v, err := strconv.ParseUint(parts[0], 0, 10) + if err != nil { + return nil, err + } + uid = uint32(v) + if v, err = strconv.ParseUint(parts[1], 0, 10); err != nil { + return nil, err + } + gid = uint32(v) + default: + return nil, fmt.Errorf("invalid USER value %s", config.User) + } + } + if tty { + env = append(env, "TERM=xterm") + } + cwd := config.WorkingDir + if cwd == "" { + cwd = "/" + } + return &specs.Spec{ + Version: specs.Version, + Platform: specs.Platform{ + OS: runtime.GOOS, + Arch: runtime.GOARCH, + }, + Root: specs.Root{ + Path: rootfsPath, + Readonly: context.Bool("readonly"), + }, + Process: specs.Process{ + Args: args, + Env: env, + Terminal: tty, + Cwd: cwd, + NoNewPrivileges: true, + User: specs.User{ + UID: uid, + GID: gid, + }, + Capabilities: &specs.LinuxCapabilities{ + Bounding: capabilities, + Permitted: capabilities, + Inheritable: capabilities, + Effective: capabilities, + Ambient: capabilities, + }, + Rlimits: []specs.LinuxRlimit{ + { + Type: "RLIMIT_NOFILE", + Hard: uint64(1024), + Soft: uint64(1024), + }, + }, + }, + Mounts: []specs.Mount{ + { + Destination: "/proc", + Type: "proc", + Source: "proc", + }, + { + Destination: "/dev", + Type: "tmpfs", + Source: "tmpfs", + Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"}, + }, + { + Destination: "/dev/pts", + Type: "devpts", + Source: "devpts", + Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"}, + }, + { + Destination: "/dev/shm", + Type: "tmpfs", + Source: "shm", + Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"}, + }, + { + Destination: "/dev/mqueue", + Type: "mqueue", + Source: "mqueue", + Options: []string{"nosuid", "noexec", "nodev"}, + }, + { + Destination: "/sys", + Type: "sysfs", + Source: "sysfs", + Options: []string{"nosuid", "noexec", "nodev", "ro"}, + }, + { + Destination: "/run", + Type: "tmpfs", + Source: "tmpfs", + Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"}, + }, + { + Destination: "/etc/resolv.conf", + Type: "bind", + Source: "/etc/resolv.conf", + Options: []string{"rbind", "ro"}, + }, + { + Destination: "/etc/hosts", + Type: "bind", + Source: "/etc/hosts", + Options: []string{"rbind", "ro"}, + }, + { + Destination: "/etc/localtime", + Type: "bind", + Source: "/etc/localtime", + Options: []string{"rbind", "ro"}, + }, + }, + Hostname: id, + Linux: &specs.Linux{ + Resources: &specs.LinuxResources{ + Devices: []specs.LinuxDeviceCgroup{ + { + Allow: false, + Access: rwm, + }, + }, + }, + Namespaces: []specs.LinuxNamespace{ + { + Type: "pid", + }, + { + Type: "ipc", + }, + { + Type: "uts", + }, + { + Type: "mount", + }, + { + Type: "network", + }, + }, + }, + }, nil +} + +func customSpec(configPath string) (*specs.Spec, error) { + b, err := ioutil.ReadFile(configPath) + if err != nil { + return nil, err + } + var s specs.Spec + if err := json.Unmarshal(b, &s); err != nil { + return nil, err + } + if s.Root.Path != rootfsPath { + logrus.Warnf("ignoring Root.Path %q, setting %q forcibly", s.Root.Path, rootfsPath) + s.Root.Path = rootfsPath + } + return &s, nil +} + +func getConfig(context *cli.Context, imageConfig *ocispec.ImageConfig) (*specs.Spec, error) { + config := context.String("runtime-config") + if config == "" { + return spec(context.String("id"), imageConfig, context) + } + + return customSpec(config) +} + +func newCreateRequest(context *cli.Context, imageConfig *ocispec.ImageConfig, id, tmpDir string) (*execution.CreateRequest, error) { + s, err := getConfig(context, imageConfig) + if err != nil { + return nil, err + } + data, err := json.Marshal(s) + if err != nil { + return nil, err + } + create := &execution.CreateRequest{ + ID: id, + Spec: &protobuf.Any{ + TypeUrl: specs.Version, + Value: data, + }, + Runtime: context.String("runtime"), + Terminal: context.Bool("tty"), + Stdin: filepath.Join(tmpDir, "stdin"), + Stdout: filepath.Join(tmpDir, "stdout"), + Stderr: filepath.Join(tmpDir, "stderr"), + } + + return create, nil +} diff --git a/cmd/ctr/run_windows.go b/cmd/ctr/run_windows.go new file mode 100644 index 0000000..ded8d1f --- /dev/null +++ b/cmd/ctr/run_windows.go @@ -0,0 +1,137 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "runtime" + + "github.com/Sirupsen/logrus" + "github.com/containerd/containerd/api/services/execution" + "github.com/containerd/containerd/windows" + "github.com/containerd/containerd/windows/hcs" + protobuf "github.com/gogo/protobuf/types" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/urfave/cli" +) + +const pipeRoot = `\\.\pipe` + +func init() { + runCommand.Flags = append(runCommand.Flags, cli.StringSliceFlag{ + Name: "layers", + Usage: "HCSSHIM Layers to be used", + }) +} + +func spec(id string, config *ocispec.ImageConfig, context *cli.Context) *specs.Spec { + cmd := config.Cmd + if a := context.Args().First(); a != "" { + cmd = context.Args() + } + + var ( + // TODO: support overriding entrypoint + args = append(config.Entrypoint, cmd...) + tty = context.Bool("tty") + cwd = config.WorkingDir + ) + + if cwd == "" { + cwd = `C:\` + } + + return &specs.Spec{ + Version: specs.Version, + Platform: specs.Platform{ + OS: runtime.GOOS, + Arch: runtime.GOARCH, + }, + Root: specs.Root{ + Readonly: context.Bool("readonly"), + }, + Process: specs.Process{ + Args: args, + Terminal: tty, + Cwd: cwd, + Env: config.Env, + User: specs.User{ + Username: config.User, + }, + ConsoleSize: specs.Box{ + Height: 20, + Width: 80, + }, + }, + Hostname: id, + } +} + +func customSpec(context *cli.Context, configPath string) (*specs.Spec, error) { + b, err := ioutil.ReadFile(configPath) + if err != nil { + return nil, err + } + + var s specs.Spec + if err := json.Unmarshal(b, &s); err != nil { + return nil, err + } + + rootfs := context.String("rootfs") + if rootfs != "" && s.Root.Path != rootfs { + logrus.Warnf("ignoring config Root.Path %q, setting %q forcibly", s.Root.Path, rootfs) + s.Root.Path = rootfs + } + return &s, nil +} + +func getConfig(context *cli.Context, imageConfig *ocispec.ImageConfig) (*specs.Spec, error) { + if config := context.String("runtime-config"); config != "" { + return customSpec(context, config) + } + + s := spec(context.String("id"), imageConfig, context) + if rootfs := context.String("rootfs"); rootfs != "" { + s.Root.Path = rootfs + } + + return s, nil +} + +func newCreateRequest(context *cli.Context, imageConfig *ocispec.ImageConfig, id, tmpDir string) (*execution.CreateRequest, error) { + spec, err := getConfig(context, imageConfig) + if err != nil { + return nil, err + } + + rtSpec := windows.RuntimeSpec{ + OCISpec: *spec, + Configuration: hcs.Configuration{ + Layers: context.StringSlice("layers"), + IgnoreFlushesDuringBoot: true, + AllowUnqualifiedDNSQuery: true}, + } + + data, err := json.Marshal(rtSpec) + if err != nil { + return nil, err + } + create := &execution.CreateRequest{ + ID: id, + Spec: &protobuf.Any{ + TypeUrl: specs.Version, + Value: data, + }, + Runtime: context.String("runtime"), + Terminal: context.Bool("tty"), + Stdin: fmt.Sprintf(`%s\ctr-%s-stdin`, pipeRoot, id), + Stdout: fmt.Sprintf(`%s\ctr-%s-stdout`, pipeRoot, id), + } + if !create.Terminal { + create.Stderr = fmt.Sprintf(`%s\ctr-%s-stderr`, pipeRoot, id) + } + + return create, nil +} diff --git a/cmd/ctr/shim.go b/cmd/ctr/shim.go index 9332087..44165b8 100644 --- a/cmd/ctr/shim.go +++ b/cmd/ctr/shim.go @@ -1,3 +1,5 @@ +// +build linux + package main import ( diff --git a/cmd/ctr/utils.go b/cmd/ctr/utils.go index e312c31..74bebdb 100644 --- a/cmd/ctr/utils.go +++ b/cmd/ctr/utils.go @@ -2,17 +2,9 @@ package main import ( "fmt" - "io" "io/ioutil" - "log" - "net" "os" "path/filepath" - "sync" - "syscall" - "time" - - gocontext "context" contentapi "github.com/containerd/containerd/api/services/content" "github.com/containerd/containerd/api/services/execution" @@ -23,94 +15,12 @@ import ( "github.com/containerd/containerd/images" contentservice "github.com/containerd/containerd/services/content" imagesservice "github.com/containerd/containerd/services/images" - "github.com/pkg/errors" - "github.com/tonistiigi/fifo" "github.com/urfave/cli" "google.golang.org/grpc" - "google.golang.org/grpc/grpclog" ) var grpcConn *grpc.ClientConn -func prepareStdio(stdin, stdout, stderr string, console bool) (*sync.WaitGroup, error) { - var wg sync.WaitGroup - ctx := gocontext.Background() - - f, err := fifo.OpenFifo(ctx, stdin, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700) - if err != nil { - return nil, err - } - defer func(c io.Closer) { - if err != nil { - c.Close() - } - }(f) - go func(w io.WriteCloser) { - io.Copy(w, os.Stdin) - w.Close() - }(f) - - f, err = fifo.OpenFifo(ctx, stdout, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700) - if err != nil { - return nil, err - } - defer func(c io.Closer) { - if err != nil { - c.Close() - } - }(f) - wg.Add(1) - go func(r io.ReadCloser) { - io.Copy(os.Stdout, r) - r.Close() - wg.Done() - }(f) - - f, err = fifo.OpenFifo(ctx, stderr, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700) - if err != nil { - return nil, err - } - defer func(c io.Closer) { - if err != nil { - c.Close() - } - }(f) - if !console { - wg.Add(1) - go func(r io.ReadCloser) { - io.Copy(os.Stderr, r) - r.Close() - wg.Done() - }(f) - } - - return &wg, nil -} - -func getGRPCConnection(context *cli.Context) (*grpc.ClientConn, error) { - if grpcConn != nil { - return grpcConn, nil - } - - bindSocket := context.GlobalString("socket") - // reset the logger for grpc to log to dev/null so that it does not mess with our stdio - grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags)) - dialOpts := []grpc.DialOption{grpc.WithInsecure(), grpc.WithTimeout(100 * time.Second)} - dialOpts = append(dialOpts, - grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { - return net.DialTimeout("unix", bindSocket, timeout) - }, - )) - - conn, err := grpc.Dial(fmt.Sprintf("unix://%s", bindSocket), dialOpts...) - if err != nil { - return nil, errors.Wrapf(err, "failed to dial %q", bindSocket) - } - - grpcConn = conn - return grpcConn, nil -} - func getExecutionService(context *cli.Context) (execution.ContainerServiceClient, error) { conn, err := getGRPCConnection(context) if err != nil { diff --git a/cmd/ctr/utils_unix.go b/cmd/ctr/utils_unix.go new file mode 100644 index 0000000..0bc62b6 --- /dev/null +++ b/cmd/ctr/utils_unix.go @@ -0,0 +1,102 @@ +// +build !windows + +package main + +import ( + "fmt" + "io" + "io/ioutil" + "log" + "net" + "os" + "sync" + "syscall" + "time" + + gocontext "context" + + "github.com/pkg/errors" + "github.com/tonistiigi/fifo" + "github.com/urfave/cli" + "google.golang.org/grpc" + "google.golang.org/grpc/grpclog" +) + +func prepareStdio(stdin, stdout, stderr string, console bool) (*sync.WaitGroup, error) { + var wg sync.WaitGroup + ctx := gocontext.Background() + + f, err := fifo.OpenFifo(ctx, stdin, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700) + if err != nil { + return nil, err + } + defer func(c io.Closer) { + if err != nil { + c.Close() + } + }(f) + go func(w io.WriteCloser) { + io.Copy(w, os.Stdin) + w.Close() + }(f) + + f, err = fifo.OpenFifo(ctx, stdout, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700) + if err != nil { + return nil, err + } + defer func(c io.Closer) { + if err != nil { + c.Close() + } + }(f) + wg.Add(1) + go func(r io.ReadCloser) { + io.Copy(os.Stdout, r) + r.Close() + wg.Done() + }(f) + + f, err = fifo.OpenFifo(ctx, stderr, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700) + if err != nil { + return nil, err + } + defer func(c io.Closer) { + if err != nil { + c.Close() + } + }(f) + if !console { + wg.Add(1) + go func(r io.ReadCloser) { + io.Copy(os.Stderr, r) + r.Close() + wg.Done() + }(f) + } + + return &wg, nil +} + +func getGRPCConnection(context *cli.Context) (*grpc.ClientConn, error) { + if grpcConn != nil { + return grpcConn, nil + } + + bindSocket := context.GlobalString("address") + // reset the logger for grpc to log to dev/null so that it does not mess with our stdio + grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags)) + dialOpts := []grpc.DialOption{grpc.WithInsecure(), grpc.WithTimeout(100 * time.Second)} + dialOpts = append(dialOpts, + grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { + return net.DialTimeout("unix", bindSocket, timeout) + }, + )) + + conn, err := grpc.Dial(fmt.Sprintf("unix://%s", bindSocket), dialOpts...) + if err != nil { + return nil, errors.Wrapf(err, "failed to dial %q", bindSocket) + } + + grpcConn = conn + return grpcConn, nil +} diff --git a/cmd/ctr/utils_windows.go b/cmd/ctr/utils_windows.go new file mode 100644 index 0000000..d456bf9 --- /dev/null +++ b/cmd/ctr/utils_windows.go @@ -0,0 +1,121 @@ +package main + +import ( + "io" + "io/ioutil" + "log" + "net" + "os" + "sync" + "time" + + "github.com/Microsoft/go-winio" + clog "github.com/containerd/containerd/log" + "github.com/pkg/errors" + "github.com/urfave/cli" + "google.golang.org/grpc" + "google.golang.org/grpc/grpclog" +) + +func getGRPCConnection(context *cli.Context) (*grpc.ClientConn, error) { + if grpcConn != nil { + return grpcConn, nil + } + + bindAddress := context.GlobalString("address") + // reset the logger for grpc to log to dev/null so that it does not mess with our stdio + grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags)) + dialOpts := []grpc.DialOption{grpc.WithInsecure(), grpc.WithTimeout(100 * time.Second)} + dialOpts = append(dialOpts, + grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { + return winio.DialPipe(bindAddress, &timeout) + }, + )) + + conn, err := grpc.Dial(bindAddress, dialOpts...) + if err != nil { + return nil, errors.Wrapf(err, "failed to dial %q", bindAddress) + } + + grpcConn = conn + return grpcConn, nil +} + +func prepareStdio(stdin, stdout, stderr string, console bool) (*sync.WaitGroup, error) { + var wg sync.WaitGroup + + if stdin != "" { + l, err := winio.ListenPipe(stdin, nil) + if err != nil { + return nil, errors.Wrapf(err, "failed to create stdin pipe %s", stdin) + } + defer func(l net.Listener) { + if err != nil { + l.Close() + } + }(l) + + go func() { + c, err := l.Accept() + if err != nil { + clog.L.WithError(err).Errorf("failed to accept stdin connection on %s", stdin) + return + } + io.Copy(c, os.Stdin) + c.Close() + l.Close() + }() + } + + if stdout != "" { + l, err := winio.ListenPipe(stdout, nil) + if err != nil { + return nil, errors.Wrapf(err, "failed to create stdin pipe %s", stdout) + } + defer func(l net.Listener) { + if err != nil { + l.Close() + } + }(l) + + wg.Add(1) + go func() { + defer wg.Done() + c, err := l.Accept() + if err != nil { + clog.L.WithError(err).Errorf("failed to accept stdout connection on %s", stdout) + return + } + io.Copy(os.Stdout, c) + c.Close() + l.Close() + }() + } + + if !console && stderr != "" { + l, err := winio.ListenPipe(stderr, nil) + if err != nil { + return nil, errors.Wrapf(err, "failed to create stderr pipe %s", stderr) + } + defer func(l net.Listener) { + if err != nil { + l.Close() + } + }(l) + + wg.Add(1) + go func() { + defer wg.Done() + c, err := l.Accept() + if err != nil { + clog.L.WithError(err).Errorf("failed to accept stderr connection on %s", stderr) + return + } + io.Copy(os.Stderr, c) + c.Close() + l.Close() + }() + } + + return &wg, nil +} diff --git a/cmd/dist/common.go b/cmd/dist/common.go index 1c819d0..7c8d41e 100644 --- a/cmd/dist/common.go +++ b/cmd/dist/common.go @@ -37,14 +37,14 @@ func resolveImageStore(clicontext *cli.Context) (images.Store, error) { } func connectGRPC(context *cli.Context) (*grpc.ClientConn, error) { - socket := context.GlobalString("socket") + address := context.GlobalString("address") timeout := context.GlobalDuration("connect-timeout") - return grpc.Dial(socket, + return grpc.Dial(address, grpc.WithTimeout(timeout), grpc.WithBlock(), grpc.WithInsecure(), grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { - return net.DialTimeout("unix", socket, timeout) + return net.DialTimeout("unix", address, timeout) }), ) } diff --git a/container.go b/container.go index 9c43065..cd0f098 100644 --- a/container.go +++ b/container.go @@ -16,6 +16,8 @@ type Container interface { State(context.Context) (State, error) // Kill signals a container Kill(context.Context, uint32, bool) error + // Exec adds a process into the container + Exec(context.Context, ExecOpts) (Process, error) } type LinuxContainer interface { @@ -23,7 +25,6 @@ type LinuxContainer interface { Pause(context.Context) error Resume(context.Context) error - Exec(context.Context, ExecOpts) (Process, error) } type ExecOpts struct { diff --git a/content/store.go b/content/store.go index 4a3a676..2bf3c54 100644 --- a/content/store.go +++ b/content/store.go @@ -8,7 +8,6 @@ import ( "os" "path/filepath" "strconv" - "syscall" "time" "github.com/containerd/containerd/log" @@ -156,19 +155,12 @@ func (s *Store) status(ingestPath string) (Status, error) { return Status{}, err } - var startedAt time.Time - if st, ok := fi.Sys().(*syscall.Stat_t); ok { - startedAt = time.Unix(int64(st.Ctim.Sec), int64(st.Ctim.Nsec)) - } else { - startedAt = fi.ModTime() - } - return Status{ Ref: ref, Offset: fi.Size(), Total: s.total(ingestPath), UpdatedAt: fi.ModTime(), - StartedAt: startedAt, + StartedAt: getStartTime(fi), }, nil } diff --git a/content/store_unix.go b/content/store_unix.go new file mode 100644 index 0000000..f79720d --- /dev/null +++ b/content/store_unix.go @@ -0,0 +1,17 @@ +// +build linux + +package content + +import ( + "os" + "syscall" + "time" +) + +func getStartTime(fi os.FileInfo) time.Time { + if st, ok := fi.Sys().(*syscall.Stat_t); ok { + return time.Unix(st.Ctim.Sec, st.Ctim.Nsec) + } + + return fi.ModTime() +} diff --git a/content/store_windows.go b/content/store_windows.go new file mode 100644 index 0000000..495e342 --- /dev/null +++ b/content/store_windows.go @@ -0,0 +1,10 @@ +package content + +import ( + "os" + "time" +) + +func getStartTime(fi os.FileInfo) time.Time { + return fi.ModTime() +} diff --git a/linux/container.go b/linux/container.go index c833aa5..1de553c 100644 --- a/linux/container.go +++ b/linux/container.go @@ -1,3 +1,5 @@ +// +build linux + package linux import ( diff --git a/linux/runtime.go b/linux/runtime.go index b107137..39d62b5 100644 --- a/linux/runtime.go +++ b/linux/runtime.go @@ -1,3 +1,5 @@ +// +build linux + package linux import ( diff --git a/linux/shim.go b/linux/shim.go index d5df4e3..2d62663 100644 --- a/linux/shim.go +++ b/linux/shim.go @@ -1,3 +1,5 @@ +// +build linux + package linux import ( diff --git a/linux/shim/exec.go b/linux/shim/exec.go index e3307a1..a224b0d 100644 --- a/linux/shim/exec.go +++ b/linux/shim/exec.go @@ -1,3 +1,5 @@ +// +build linux + package shim import ( diff --git a/linux/shim/init.go b/linux/shim/init.go index ddc6590..616b20a 100644 --- a/linux/shim/init.go +++ b/linux/shim/init.go @@ -1,3 +1,5 @@ +// +build linux + package shim import ( diff --git a/linux/shim/io.go b/linux/shim/io.go index 8387c04..60e41b8 100644 --- a/linux/shim/io.go +++ b/linux/shim/io.go @@ -1,3 +1,5 @@ +// +build linux + package shim import ( diff --git a/linux/shim/process.go b/linux/shim/process.go index 2f0f704..f1d2f58 100644 --- a/linux/shim/process.go +++ b/linux/shim/process.go @@ -1,3 +1,5 @@ +// +build linux + package shim import ( diff --git a/linux/shim/service.go b/linux/shim/service.go index b2f4fb6..96da164 100644 --- a/linux/shim/service.go +++ b/linux/shim/service.go @@ -1,3 +1,5 @@ +// +build linux + package shim import ( diff --git a/mount_unix.go b/mount_unix.go index 18acd28..b8f1d5f 100644 --- a/mount_unix.go +++ b/mount_unix.go @@ -1,4 +1,4 @@ -// +build !windows +// +build linux package containerd @@ -12,6 +12,10 @@ func (m *Mount) Mount(target string) error { return syscall.Mount(m.Source, target, m.Type, uintptr(flags), data) } +func Unmount(mount string, flags int) error { + return syscall.Unmount(mount, flags) +} + // parseMountOptions takes fstab style mount options and parses them for // use with a standard mount() syscall func parseMountOptions(options []string) (int, string) { diff --git a/mount_windows.go b/mount_windows.go index f865e41..d5098b7 100644 --- a/mount_windows.go +++ b/mount_windows.go @@ -1,9 +1,15 @@ package containerd -import ( - "errors" +import "github.com/pkg/errors" + +var ( + ErrNotImplementOnWindows = errors.New("not implemented under windows") ) func (m *Mount) Mount(target string) error { - return errors.New("mount not supported") + return ErrNotImplementOnWindows +} + +func Unmount(mount string, flags int) error { + return ErrNotImplementOnWindows } diff --git a/reaper/reaper.go b/reaper/reaper.go index 2a55702..166bcb9 100644 --- a/reaper/reaper.go +++ b/reaper/reaper.go @@ -1,3 +1,5 @@ +// +build !windows + package reaper import ( diff --git a/services/execution/service.go b/services/execution/service.go index 2c37842..0d8fa4a 100644 --- a/services/execution/service.go +++ b/services/execution/service.go @@ -1,7 +1,6 @@ package execution import ( - "fmt" "sync" "github.com/containerd/containerd" @@ -209,11 +208,7 @@ func (s *Service) Exec(ctx context.Context, r *api.ExecRequest) (*api.ExecRespon if err != nil { return nil, err } - l, ok := c.(containerd.LinuxContainer) - if !ok { - return nil, fmt.Errorf("cannot exec into a non linux container") - } - process, err := l.Exec(ctx, containerd.ExecOpts{ + process, err := c.Exec(ctx, containerd.ExecOpts{ Spec: r.Spec.Value, IO: containerd.IO{ Stdin: r.Stdin, diff --git a/services/rootfs/service.go b/services/rootfs/service.go index 539d36e..2ed937e 100644 --- a/services/rootfs/service.go +++ b/services/rootfs/service.go @@ -1,8 +1,6 @@ package rootfs import ( - "syscall" - "github.com/containerd/containerd" rootfsapi "github.com/containerd/containerd/api/services/rootfs" containerd_v1_types "github.com/containerd/containerd/api/types/mount" @@ -104,7 +102,7 @@ func (mounter) Mount(dir string, mounts ...containerd.Mount) error { } func (mounter) Unmount(dir string) error { - return syscall.Unmount(dir, 0) + return containerd.Unmount(dir, 0) } func emptyResolver(digest.Digest) digest.Digest { diff --git a/snapshot/btrfs/btrfs.go b/snapshot/btrfs/btrfs.go index 075daa4..61c6094 100644 --- a/snapshot/btrfs/btrfs.go +++ b/snapshot/btrfs/btrfs.go @@ -1,3 +1,5 @@ +// +build linux + package btrfs import ( diff --git a/snapshot/btrfs/btrfs_test.go b/snapshot/btrfs/btrfs_test.go index fc2194e..a2e5c6a 100644 --- a/snapshot/btrfs/btrfs_test.go +++ b/snapshot/btrfs/btrfs_test.go @@ -1,3 +1,5 @@ +// +build linux + package btrfs import ( diff --git a/snapshot/overlay/overlay.go b/snapshot/overlay/overlay.go index 7e5627a..9df80bd 100644 --- a/snapshot/overlay/overlay.go +++ b/snapshot/overlay/overlay.go @@ -1,3 +1,5 @@ +// +build linux + package overlay import ( diff --git a/snapshot/overlay/overlay_test.go b/snapshot/overlay/overlay_test.go index cf5f1be..a3180d8 100644 --- a/snapshot/overlay/overlay_test.go +++ b/snapshot/overlay/overlay_test.go @@ -1,3 +1,5 @@ +// +build linux + package overlay import ( diff --git a/snapshot/windows/windows.go b/snapshot/windows/windows.go new file mode 100644 index 0000000..b45e223 --- /dev/null +++ b/snapshot/windows/windows.go @@ -0,0 +1,76 @@ +// +build windows + +package windows + +import ( + "context" + "path/filepath" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/plugin" + "github.com/containerd/containerd/snapshot" + "github.com/pkg/errors" +) + +var ( + ErrNotImplemented = errors.New("not implemented") +) + +func init() { + plugin.Register("snapshot-windows", &plugin.Registration{ + Type: plugin.SnapshotPlugin, + Init: func(ic *plugin.InitContext) (interface{}, error) { + return NewSnapshotter(filepath.Join(ic.Root, "snapshot", "windows")) + }, + }) +} + +type Snapshotter struct { + root string +} + +func NewSnapshotter(root string) (snapshot.Snapshotter, error) { + return &Snapshotter{ + root: root, + }, nil +} + +// Stat returns the info for an active or committed snapshot by name or +// key. +// +// Should be used for parent resolution, existence checks and to discern +// the kind of snapshot. +func (o *Snapshotter) Stat(ctx context.Context, key string) (snapshot.Info, error) { + panic("not implemented") +} + +func (o *Snapshotter) Prepare(ctx context.Context, key, parent string) ([]containerd.Mount, error) { + panic("not implemented") +} + +func (o *Snapshotter) View(ctx context.Context, key, parent string) ([]containerd.Mount, error) { + panic("not implemented") +} + +// Mounts returns the mounts for the transaction identified by key. Can be +// called on an read-write or readonly transaction. +// +// This can be used to recover mounts after calling View or Prepare. +func (o *Snapshotter) Mounts(ctx context.Context, key string) ([]containerd.Mount, error) { + panic("not implemented") +} + +func (o *Snapshotter) Commit(ctx context.Context, name, key string) error { + panic("not implemented") +} + +// Remove abandons the transaction identified by key. All resources +// associated with the key will be removed. +func (o *Snapshotter) Remove(ctx context.Context, key string) error { + panic("not implemented") +} + +// Walk the committed snapshots. +func (o *Snapshotter) Walk(ctx context.Context, fn func(context.Context, snapshot.Info) error) error { + panic("not implemented") +} diff --git a/sys/socket.go b/sys/socket_unix.go similarity index 55% rename from sys/socket.go rename to sys/socket_unix.go index cac9d6f..e114c55 100644 --- a/sys/socket.go +++ b/sys/socket_unix.go @@ -1,3 +1,5 @@ +// +build !windows + package sys import ( @@ -17,3 +19,18 @@ func CreateUnixSocket(path string) (net.Listener, error) { } return net.Listen("unix", path) } + +// GetLocalListener returns a listerner out of a unix socket. +func GetLocalListener(path string, uid, gid int) (net.Listener, error) { + l, err := CreateUnixSocket(path) + if err != nil { + return l, err + } + + if err := os.Chown(path, uid, gid); err != nil { + l.Close() + return nil, err + } + + return l, nil +} diff --git a/sys/socket_windows.go b/sys/socket_windows.go new file mode 100644 index 0000000..de25c08 --- /dev/null +++ b/sys/socket_windows.go @@ -0,0 +1,16 @@ +// +build windows + +package sys + +import ( + "net" + + "github.com/Microsoft/go-winio" +) + +// GetLocalListener returns a Listernet out of a named pipe. +// `path` must be of the form of `\\.\pipe\` +// (see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365150) +func GetLocalListener(path string, uid, gid int) (net.Listener, error) { + return winio.ListenPipe(path, nil) +} diff --git a/vendor.conf b/vendor.conf index 643cc72..4fc0b76 100644 --- a/vendor.conf +++ b/vendor.conf @@ -1,5 +1,5 @@ github.com/crosbymichael/go-runc bd9aef7cf4402a3a8728e3ef83dcca6a5a1be899 -github.com/crosbymichael/console 4bf9d88357031b516b3794a2594b6d060a29c59c +github.com/crosbymichael/console 1e9d561b86cc6182bcc027ced7156b65c91edfc6 github.com/crosbymichael/cgroups 74ce513f69d6dbdb355668b4b4ca7ac3d0886664 github.com/docker/go-metrics 8fd5772bf1584597834c6f7961a530f06cbfbb87 github.com/prometheus/client_golang v0.8.0 @@ -33,3 +33,5 @@ github.com/BurntSushi/toml v0.2.0-21-g9906417 github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0 github.com/Microsoft/go-winio fff283ad5116362ca252298cfc9b95828956d85d github.com/boltdb/bolt e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd +github.com/Microsoft/hcsshim v0.5.15 +github.com/Azure/go-ansiterm/winterm fa152c58bc15761d0200cb75fe958b89a9d4888e diff --git a/vendor/github.com/Azure/go-ansiterm/LICENSE b/vendor/github.com/Azure/go-ansiterm/LICENSE new file mode 100644 index 0000000..e3d9a64 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Microsoft Corporation + +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. diff --git a/vendor/github.com/Azure/go-ansiterm/README.md b/vendor/github.com/Azure/go-ansiterm/README.md new file mode 100644 index 0000000..261c041 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/README.md @@ -0,0 +1,12 @@ +# go-ansiterm + +This is a cross platform Ansi Terminal Emulation library. It reads a stream of Ansi characters and produces the appropriate function calls. The results of the function calls are platform dependent. + +For example the parser might receive "ESC, [, A" as a stream of three characters. This is the code for Cursor Up (http://www.vt100.net/docs/vt510-rm/CUU). The parser then calls the cursor up function (CUU()) on an event handler. The event handler determines what platform specific work must be done to cause the cursor to move up one position. + +The parser (parser.go) is a partial implementation of this state machine (http://vt100.net/emu/vt500_parser.png). There are also two event handler implementations, one for tests (test_event_handler.go) to validate that the expected events are being produced and called, the other is a Windows implementation (winterm/win_event_handler.go). + +See parser_test.go for examples exercising the state machine and generating appropriate function calls. + +----- +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/vendor/github.com/Azure/go-ansiterm/constants.go b/vendor/github.com/Azure/go-ansiterm/constants.go new file mode 100644 index 0000000..96504a3 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/constants.go @@ -0,0 +1,188 @@ +package ansiterm + +const LogEnv = "DEBUG_TERMINAL" + +// ANSI constants +// References: +// -- http://www.ecma-international.org/publications/standards/Ecma-048.htm +// -- http://man7.org/linux/man-pages/man4/console_codes.4.html +// -- http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html +// -- http://en.wikipedia.org/wiki/ANSI_escape_code +// -- http://vt100.net/emu/dec_ansi_parser +// -- http://vt100.net/emu/vt500_parser.svg +// -- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html +// -- http://www.inwap.com/pdp10/ansicode.txt +const ( + // ECMA-48 Set Graphics Rendition + // Note: + // -- Constants leading with an underscore (e.g., _ANSI_xxx) are unsupported or reserved + // -- Fonts could possibly be supported via SetCurrentConsoleFontEx + // -- Windows does not expose the per-window cursor (i.e., caret) blink times + ANSI_SGR_RESET = 0 + ANSI_SGR_BOLD = 1 + ANSI_SGR_DIM = 2 + _ANSI_SGR_ITALIC = 3 + ANSI_SGR_UNDERLINE = 4 + _ANSI_SGR_BLINKSLOW = 5 + _ANSI_SGR_BLINKFAST = 6 + ANSI_SGR_REVERSE = 7 + _ANSI_SGR_INVISIBLE = 8 + _ANSI_SGR_LINETHROUGH = 9 + _ANSI_SGR_FONT_00 = 10 + _ANSI_SGR_FONT_01 = 11 + _ANSI_SGR_FONT_02 = 12 + _ANSI_SGR_FONT_03 = 13 + _ANSI_SGR_FONT_04 = 14 + _ANSI_SGR_FONT_05 = 15 + _ANSI_SGR_FONT_06 = 16 + _ANSI_SGR_FONT_07 = 17 + _ANSI_SGR_FONT_08 = 18 + _ANSI_SGR_FONT_09 = 19 + _ANSI_SGR_FONT_10 = 20 + _ANSI_SGR_DOUBLEUNDERLINE = 21 + ANSI_SGR_BOLD_DIM_OFF = 22 + _ANSI_SGR_ITALIC_OFF = 23 + ANSI_SGR_UNDERLINE_OFF = 24 + _ANSI_SGR_BLINK_OFF = 25 + _ANSI_SGR_RESERVED_00 = 26 + ANSI_SGR_REVERSE_OFF = 27 + _ANSI_SGR_INVISIBLE_OFF = 28 + _ANSI_SGR_LINETHROUGH_OFF = 29 + ANSI_SGR_FOREGROUND_BLACK = 30 + ANSI_SGR_FOREGROUND_RED = 31 + ANSI_SGR_FOREGROUND_GREEN = 32 + ANSI_SGR_FOREGROUND_YELLOW = 33 + ANSI_SGR_FOREGROUND_BLUE = 34 + ANSI_SGR_FOREGROUND_MAGENTA = 35 + ANSI_SGR_FOREGROUND_CYAN = 36 + ANSI_SGR_FOREGROUND_WHITE = 37 + _ANSI_SGR_RESERVED_01 = 38 + ANSI_SGR_FOREGROUND_DEFAULT = 39 + ANSI_SGR_BACKGROUND_BLACK = 40 + ANSI_SGR_BACKGROUND_RED = 41 + ANSI_SGR_BACKGROUND_GREEN = 42 + ANSI_SGR_BACKGROUND_YELLOW = 43 + ANSI_SGR_BACKGROUND_BLUE = 44 + ANSI_SGR_BACKGROUND_MAGENTA = 45 + ANSI_SGR_BACKGROUND_CYAN = 46 + ANSI_SGR_BACKGROUND_WHITE = 47 + _ANSI_SGR_RESERVED_02 = 48 + ANSI_SGR_BACKGROUND_DEFAULT = 49 + // 50 - 65: Unsupported + + ANSI_MAX_CMD_LENGTH = 4096 + + MAX_INPUT_EVENTS = 128 + DEFAULT_WIDTH = 80 + DEFAULT_HEIGHT = 24 + + ANSI_BEL = 0x07 + ANSI_BACKSPACE = 0x08 + ANSI_TAB = 0x09 + ANSI_LINE_FEED = 0x0A + ANSI_VERTICAL_TAB = 0x0B + ANSI_FORM_FEED = 0x0C + ANSI_CARRIAGE_RETURN = 0x0D + ANSI_ESCAPE_PRIMARY = 0x1B + ANSI_ESCAPE_SECONDARY = 0x5B + ANSI_OSC_STRING_ENTRY = 0x5D + ANSI_COMMAND_FIRST = 0x40 + ANSI_COMMAND_LAST = 0x7E + DCS_ENTRY = 0x90 + CSI_ENTRY = 0x9B + OSC_STRING = 0x9D + ANSI_PARAMETER_SEP = ";" + ANSI_CMD_G0 = '(' + ANSI_CMD_G1 = ')' + ANSI_CMD_G2 = '*' + ANSI_CMD_G3 = '+' + ANSI_CMD_DECPNM = '>' + ANSI_CMD_DECPAM = '=' + ANSI_CMD_OSC = ']' + ANSI_CMD_STR_TERM = '\\' + + KEY_CONTROL_PARAM_2 = ";2" + KEY_CONTROL_PARAM_3 = ";3" + KEY_CONTROL_PARAM_4 = ";4" + KEY_CONTROL_PARAM_5 = ";5" + KEY_CONTROL_PARAM_6 = ";6" + KEY_CONTROL_PARAM_7 = ";7" + KEY_CONTROL_PARAM_8 = ";8" + KEY_ESC_CSI = "\x1B[" + KEY_ESC_N = "\x1BN" + KEY_ESC_O = "\x1BO" + + FILL_CHARACTER = ' ' +) + +func getByteRange(start byte, end byte) []byte { + bytes := make([]byte, 0, 32) + for i := start; i <= end; i++ { + bytes = append(bytes, byte(i)) + } + + return bytes +} + +var toGroundBytes = getToGroundBytes() +var executors = getExecuteBytes() + +// SPACE 20+A0 hex Always and everywhere a blank space +// Intermediate 20-2F hex !"#$%&'()*+,-./ +var intermeds = getByteRange(0x20, 0x2F) + +// Parameters 30-3F hex 0123456789:;<=>? +// CSI Parameters 30-39, 3B hex 0123456789; +var csiParams = getByteRange(0x30, 0x3F) + +var csiCollectables = append(getByteRange(0x30, 0x39), getByteRange(0x3B, 0x3F)...) + +// Uppercase 40-5F hex @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ +var upperCase = getByteRange(0x40, 0x5F) + +// Lowercase 60-7E hex `abcdefghijlkmnopqrstuvwxyz{|}~ +var lowerCase = getByteRange(0x60, 0x7E) + +// Alphabetics 40-7E hex (all of upper and lower case) +var alphabetics = append(upperCase, lowerCase...) + +var printables = getByteRange(0x20, 0x7F) + +var escapeIntermediateToGroundBytes = getByteRange(0x30, 0x7E) +var escapeToGroundBytes = getEscapeToGroundBytes() + +// See http://www.vt100.net/emu/vt500_parser.png for description of the complex +// byte ranges below + +func getEscapeToGroundBytes() []byte { + escapeToGroundBytes := getByteRange(0x30, 0x4F) + escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x51, 0x57)...) + escapeToGroundBytes = append(escapeToGroundBytes, 0x59) + escapeToGroundBytes = append(escapeToGroundBytes, 0x5A) + escapeToGroundBytes = append(escapeToGroundBytes, 0x5C) + escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x60, 0x7E)...) + return escapeToGroundBytes +} + +func getExecuteBytes() []byte { + executeBytes := getByteRange(0x00, 0x17) + executeBytes = append(executeBytes, 0x19) + executeBytes = append(executeBytes, getByteRange(0x1C, 0x1F)...) + return executeBytes +} + +func getToGroundBytes() []byte { + groundBytes := []byte{0x18} + groundBytes = append(groundBytes, 0x1A) + groundBytes = append(groundBytes, getByteRange(0x80, 0x8F)...) + groundBytes = append(groundBytes, getByteRange(0x91, 0x97)...) + groundBytes = append(groundBytes, 0x99) + groundBytes = append(groundBytes, 0x9A) + groundBytes = append(groundBytes, 0x9C) + return groundBytes +} + +// Delete 7F hex Always and everywhere ignored +// C1 Control 80-9F hex 32 additional control characters +// G1 Displayable A1-FE hex 94 additional displayable characters +// Special A0+FF hex Same as SPACE and DELETE diff --git a/vendor/github.com/Azure/go-ansiterm/context.go b/vendor/github.com/Azure/go-ansiterm/context.go new file mode 100644 index 0000000..8d66e77 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/context.go @@ -0,0 +1,7 @@ +package ansiterm + +type ansiContext struct { + currentChar byte + paramBuffer []byte + interBuffer []byte +} diff --git a/vendor/github.com/Azure/go-ansiterm/csi_entry_state.go b/vendor/github.com/Azure/go-ansiterm/csi_entry_state.go new file mode 100644 index 0000000..1bd6057 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/csi_entry_state.go @@ -0,0 +1,49 @@ +package ansiterm + +type csiEntryState struct { + baseState +} + +func (csiState csiEntryState) Handle(b byte) (s state, e error) { + logger.Infof("CsiEntry::Handle %#x", b) + + nextState, err := csiState.baseState.Handle(b) + if nextState != nil || err != nil { + return nextState, err + } + + switch { + case sliceContains(alphabetics, b): + return csiState.parser.ground, nil + case sliceContains(csiCollectables, b): + return csiState.parser.csiParam, nil + case sliceContains(executors, b): + return csiState, csiState.parser.execute() + } + + return csiState, nil +} + +func (csiState csiEntryState) Transition(s state) error { + logger.Infof("CsiEntry::Transition %s --> %s", csiState.Name(), s.Name()) + csiState.baseState.Transition(s) + + switch s { + case csiState.parser.ground: + return csiState.parser.csiDispatch() + case csiState.parser.csiParam: + switch { + case sliceContains(csiParams, csiState.parser.context.currentChar): + csiState.parser.collectParam() + case sliceContains(intermeds, csiState.parser.context.currentChar): + csiState.parser.collectInter() + } + } + + return nil +} + +func (csiState csiEntryState) Enter() error { + csiState.parser.clear() + return nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/csi_param_state.go b/vendor/github.com/Azure/go-ansiterm/csi_param_state.go new file mode 100644 index 0000000..4be35c5 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/csi_param_state.go @@ -0,0 +1,38 @@ +package ansiterm + +type csiParamState struct { + baseState +} + +func (csiState csiParamState) Handle(b byte) (s state, e error) { + logger.Infof("CsiParam::Handle %#x", b) + + nextState, err := csiState.baseState.Handle(b) + if nextState != nil || err != nil { + return nextState, err + } + + switch { + case sliceContains(alphabetics, b): + return csiState.parser.ground, nil + case sliceContains(csiCollectables, b): + csiState.parser.collectParam() + return csiState, nil + case sliceContains(executors, b): + return csiState, csiState.parser.execute() + } + + return csiState, nil +} + +func (csiState csiParamState) Transition(s state) error { + logger.Infof("CsiParam::Transition %s --> %s", csiState.Name(), s.Name()) + csiState.baseState.Transition(s) + + switch s { + case csiState.parser.ground: + return csiState.parser.csiDispatch() + } + + return nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go b/vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go new file mode 100644 index 0000000..2189eb6 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go @@ -0,0 +1,36 @@ +package ansiterm + +type escapeIntermediateState struct { + baseState +} + +func (escState escapeIntermediateState) Handle(b byte) (s state, e error) { + logger.Infof("escapeIntermediateState::Handle %#x", b) + nextState, err := escState.baseState.Handle(b) + if nextState != nil || err != nil { + return nextState, err + } + + switch { + case sliceContains(intermeds, b): + return escState, escState.parser.collectInter() + case sliceContains(executors, b): + return escState, escState.parser.execute() + case sliceContains(escapeIntermediateToGroundBytes, b): + return escState.parser.ground, nil + } + + return escState, nil +} + +func (escState escapeIntermediateState) Transition(s state) error { + logger.Infof("escapeIntermediateState::Transition %s --> %s", escState.Name(), s.Name()) + escState.baseState.Transition(s) + + switch s { + case escState.parser.ground: + return escState.parser.escDispatch() + } + + return nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/escape_state.go b/vendor/github.com/Azure/go-ansiterm/escape_state.go new file mode 100644 index 0000000..7b1b9ad --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/escape_state.go @@ -0,0 +1,47 @@ +package ansiterm + +type escapeState struct { + baseState +} + +func (escState escapeState) Handle(b byte) (s state, e error) { + logger.Infof("escapeState::Handle %#x", b) + nextState, err := escState.baseState.Handle(b) + if nextState != nil || err != nil { + return nextState, err + } + + switch { + case b == ANSI_ESCAPE_SECONDARY: + return escState.parser.csiEntry, nil + case b == ANSI_OSC_STRING_ENTRY: + return escState.parser.oscString, nil + case sliceContains(executors, b): + return escState, escState.parser.execute() + case sliceContains(escapeToGroundBytes, b): + return escState.parser.ground, nil + case sliceContains(intermeds, b): + return escState.parser.escapeIntermediate, nil + } + + return escState, nil +} + +func (escState escapeState) Transition(s state) error { + logger.Infof("Escape::Transition %s --> %s", escState.Name(), s.Name()) + escState.baseState.Transition(s) + + switch s { + case escState.parser.ground: + return escState.parser.escDispatch() + case escState.parser.escapeIntermediate: + return escState.parser.collectInter() + } + + return nil +} + +func (escState escapeState) Enter() error { + escState.parser.clear() + return nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/event_handler.go b/vendor/github.com/Azure/go-ansiterm/event_handler.go new file mode 100644 index 0000000..98087b3 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/event_handler.go @@ -0,0 +1,90 @@ +package ansiterm + +type AnsiEventHandler interface { + // Print + Print(b byte) error + + // Execute C0 commands + Execute(b byte) error + + // CUrsor Up + CUU(int) error + + // CUrsor Down + CUD(int) error + + // CUrsor Forward + CUF(int) error + + // CUrsor Backward + CUB(int) error + + // Cursor to Next Line + CNL(int) error + + // Cursor to Previous Line + CPL(int) error + + // Cursor Horizontal position Absolute + CHA(int) error + + // Vertical line Position Absolute + VPA(int) error + + // CUrsor Position + CUP(int, int) error + + // Horizontal and Vertical Position (depends on PUM) + HVP(int, int) error + + // Text Cursor Enable Mode + DECTCEM(bool) error + + // Origin Mode + DECOM(bool) error + + // 132 Column Mode + DECCOLM(bool) error + + // Erase in Display + ED(int) error + + // Erase in Line + EL(int) error + + // Insert Line + IL(int) error + + // Delete Line + DL(int) error + + // Insert Character + ICH(int) error + + // Delete Character + DCH(int) error + + // Set Graphics Rendition + SGR([]int) error + + // Pan Down + SU(int) error + + // Pan Up + SD(int) error + + // Device Attributes + DA([]string) error + + // Set Top and Bottom Margins + DECSTBM(int, int) error + + // Index + IND() error + + // Reverse Index + RI() error + + // Flush updates from previous commands + Flush() error +} diff --git a/vendor/github.com/Azure/go-ansiterm/ground_state.go b/vendor/github.com/Azure/go-ansiterm/ground_state.go new file mode 100644 index 0000000..52451e9 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/ground_state.go @@ -0,0 +1,24 @@ +package ansiterm + +type groundState struct { + baseState +} + +func (gs groundState) Handle(b byte) (s state, e error) { + gs.parser.context.currentChar = b + + nextState, err := gs.baseState.Handle(b) + if nextState != nil || err != nil { + return nextState, err + } + + switch { + case sliceContains(printables, b): + return gs, gs.parser.print() + + case sliceContains(executors, b): + return gs, gs.parser.execute() + } + + return gs, nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/osc_string_state.go b/vendor/github.com/Azure/go-ansiterm/osc_string_state.go new file mode 100644 index 0000000..24062d4 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/osc_string_state.go @@ -0,0 +1,31 @@ +package ansiterm + +type oscStringState struct { + baseState +} + +func (oscState oscStringState) Handle(b byte) (s state, e error) { + logger.Infof("OscString::Handle %#x", b) + nextState, err := oscState.baseState.Handle(b) + if nextState != nil || err != nil { + return nextState, err + } + + switch { + case isOscStringTerminator(b): + return oscState.parser.ground, nil + } + + return oscState, nil +} + +// See below for OSC string terminators for linux +// http://man7.org/linux/man-pages/man4/console_codes.4.html +func isOscStringTerminator(b byte) bool { + + if b == ANSI_BEL || b == 0x5C { + return true + } + + return false +} diff --git a/vendor/github.com/Azure/go-ansiterm/parser.go b/vendor/github.com/Azure/go-ansiterm/parser.go new file mode 100644 index 0000000..169f68d --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/parser.go @@ -0,0 +1,136 @@ +package ansiterm + +import ( + "errors" + "io/ioutil" + "os" + + "github.com/Sirupsen/logrus" +) + +var logger *logrus.Logger + +type AnsiParser struct { + currState state + eventHandler AnsiEventHandler + context *ansiContext + csiEntry state + csiParam state + dcsEntry state + escape state + escapeIntermediate state + error state + ground state + oscString state + stateMap []state +} + +func CreateParser(initialState string, evtHandler AnsiEventHandler) *AnsiParser { + logFile := ioutil.Discard + + if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" { + logFile, _ = os.Create("ansiParser.log") + } + + logger = &logrus.Logger{ + Out: logFile, + Formatter: new(logrus.TextFormatter), + Level: logrus.InfoLevel, + } + + parser := &AnsiParser{ + eventHandler: evtHandler, + context: &ansiContext{}, + } + + parser.csiEntry = csiEntryState{baseState{name: "CsiEntry", parser: parser}} + parser.csiParam = csiParamState{baseState{name: "CsiParam", parser: parser}} + parser.dcsEntry = dcsEntryState{baseState{name: "DcsEntry", parser: parser}} + parser.escape = escapeState{baseState{name: "Escape", parser: parser}} + parser.escapeIntermediate = escapeIntermediateState{baseState{name: "EscapeIntermediate", parser: parser}} + parser.error = errorState{baseState{name: "Error", parser: parser}} + parser.ground = groundState{baseState{name: "Ground", parser: parser}} + parser.oscString = oscStringState{baseState{name: "OscString", parser: parser}} + + parser.stateMap = []state{ + parser.csiEntry, + parser.csiParam, + parser.dcsEntry, + parser.escape, + parser.escapeIntermediate, + parser.error, + parser.ground, + parser.oscString, + } + + parser.currState = getState(initialState, parser.stateMap) + + logger.Infof("CreateParser: parser %p", parser) + return parser +} + +func getState(name string, states []state) state { + for _, el := range states { + if el.Name() == name { + return el + } + } + + return nil +} + +func (ap *AnsiParser) Parse(bytes []byte) (int, error) { + for i, b := range bytes { + if err := ap.handle(b); err != nil { + return i, err + } + } + + return len(bytes), ap.eventHandler.Flush() +} + +func (ap *AnsiParser) handle(b byte) error { + ap.context.currentChar = b + newState, err := ap.currState.Handle(b) + if err != nil { + return err + } + + if newState == nil { + logger.Warning("newState is nil") + return errors.New("New state of 'nil' is invalid.") + } + + if newState != ap.currState { + if err := ap.changeState(newState); err != nil { + return err + } + } + + return nil +} + +func (ap *AnsiParser) changeState(newState state) error { + logger.Infof("ChangeState %s --> %s", ap.currState.Name(), newState.Name()) + + // Exit old state + if err := ap.currState.Exit(); err != nil { + logger.Infof("Exit state '%s' failed with : '%v'", ap.currState.Name(), err) + return err + } + + // Perform transition action + if err := ap.currState.Transition(newState); err != nil { + logger.Infof("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err) + return err + } + + // Enter new state + if err := newState.Enter(); err != nil { + logger.Infof("Enter state '%s' failed with: '%v'", newState.Name(), err) + return err + } + + ap.currState = newState + return nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go b/vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go new file mode 100644 index 0000000..8b69a67 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go @@ -0,0 +1,103 @@ +package ansiterm + +import ( + "strconv" +) + +func parseParams(bytes []byte) ([]string, error) { + paramBuff := make([]byte, 0, 0) + params := []string{} + + for _, v := range bytes { + if v == ';' { + if len(paramBuff) > 0 { + // Completed parameter, append it to the list + s := string(paramBuff) + params = append(params, s) + paramBuff = make([]byte, 0, 0) + } + } else { + paramBuff = append(paramBuff, v) + } + } + + // Last parameter may not be terminated with ';' + if len(paramBuff) > 0 { + s := string(paramBuff) + params = append(params, s) + } + + logger.Infof("Parsed params: %v with length: %d", params, len(params)) + return params, nil +} + +func parseCmd(context ansiContext) (string, error) { + return string(context.currentChar), nil +} + +func getInt(params []string, dflt int) int { + i := getInts(params, 1, dflt)[0] + logger.Infof("getInt: %v", i) + return i +} + +func getInts(params []string, minCount int, dflt int) []int { + ints := []int{} + + for _, v := range params { + i, _ := strconv.Atoi(v) + // Zero is mapped to the default value in VT100. + if i == 0 { + i = dflt + } + ints = append(ints, i) + } + + if len(ints) < minCount { + remaining := minCount - len(ints) + for i := 0; i < remaining; i++ { + ints = append(ints, dflt) + } + } + + logger.Infof("getInts: %v", ints) + + return ints +} + +func (ap *AnsiParser) modeDispatch(param string, set bool) error { + switch param { + case "?3": + return ap.eventHandler.DECCOLM(set) + case "?6": + return ap.eventHandler.DECOM(set) + case "?25": + return ap.eventHandler.DECTCEM(set) + } + return nil +} + +func (ap *AnsiParser) hDispatch(params []string) error { + if len(params) == 1 { + return ap.modeDispatch(params[0], true) + } + + return nil +} + +func (ap *AnsiParser) lDispatch(params []string) error { + if len(params) == 1 { + return ap.modeDispatch(params[0], false) + } + + return nil +} + +func getEraseParam(params []string) int { + param := getInt(params, 0) + if param < 0 || 3 < param { + param = 0 + } + + return param +} diff --git a/vendor/github.com/Azure/go-ansiterm/parser_actions.go b/vendor/github.com/Azure/go-ansiterm/parser_actions.go new file mode 100644 index 0000000..58750a2 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/parser_actions.go @@ -0,0 +1,122 @@ +package ansiterm + +import ( + "fmt" +) + +func (ap *AnsiParser) collectParam() error { + currChar := ap.context.currentChar + logger.Infof("collectParam %#x", currChar) + ap.context.paramBuffer = append(ap.context.paramBuffer, currChar) + return nil +} + +func (ap *AnsiParser) collectInter() error { + currChar := ap.context.currentChar + logger.Infof("collectInter %#x", currChar) + ap.context.paramBuffer = append(ap.context.interBuffer, currChar) + return nil +} + +func (ap *AnsiParser) escDispatch() error { + cmd, _ := parseCmd(*ap.context) + intermeds := ap.context.interBuffer + logger.Infof("escDispatch currentChar: %#x", ap.context.currentChar) + logger.Infof("escDispatch: %v(%v)", cmd, intermeds) + + switch cmd { + case "D": // IND + return ap.eventHandler.IND() + case "E": // NEL, equivalent to CRLF + err := ap.eventHandler.Execute(ANSI_CARRIAGE_RETURN) + if err == nil { + err = ap.eventHandler.Execute(ANSI_LINE_FEED) + } + return err + case "M": // RI + return ap.eventHandler.RI() + } + + return nil +} + +func (ap *AnsiParser) csiDispatch() error { + cmd, _ := parseCmd(*ap.context) + params, _ := parseParams(ap.context.paramBuffer) + + logger.Infof("csiDispatch: %v(%v)", cmd, params) + + switch cmd { + case "@": + return ap.eventHandler.ICH(getInt(params, 1)) + case "A": + return ap.eventHandler.CUU(getInt(params, 1)) + case "B": + return ap.eventHandler.CUD(getInt(params, 1)) + case "C": + return ap.eventHandler.CUF(getInt(params, 1)) + case "D": + return ap.eventHandler.CUB(getInt(params, 1)) + case "E": + return ap.eventHandler.CNL(getInt(params, 1)) + case "F": + return ap.eventHandler.CPL(getInt(params, 1)) + case "G": + return ap.eventHandler.CHA(getInt(params, 1)) + case "H": + ints := getInts(params, 2, 1) + x, y := ints[0], ints[1] + return ap.eventHandler.CUP(x, y) + case "J": + param := getEraseParam(params) + return ap.eventHandler.ED(param) + case "K": + param := getEraseParam(params) + return ap.eventHandler.EL(param) + case "L": + return ap.eventHandler.IL(getInt(params, 1)) + case "M": + return ap.eventHandler.DL(getInt(params, 1)) + case "P": + return ap.eventHandler.DCH(getInt(params, 1)) + case "S": + return ap.eventHandler.SU(getInt(params, 1)) + case "T": + return ap.eventHandler.SD(getInt(params, 1)) + case "c": + return ap.eventHandler.DA(params) + case "d": + return ap.eventHandler.VPA(getInt(params, 1)) + case "f": + ints := getInts(params, 2, 1) + x, y := ints[0], ints[1] + return ap.eventHandler.HVP(x, y) + case "h": + return ap.hDispatch(params) + case "l": + return ap.lDispatch(params) + case "m": + return ap.eventHandler.SGR(getInts(params, 1, 0)) + case "r": + ints := getInts(params, 2, 1) + top, bottom := ints[0], ints[1] + return ap.eventHandler.DECSTBM(top, bottom) + default: + logger.Errorf(fmt.Sprintf("Unsupported CSI command: '%s', with full context: %v", cmd, ap.context)) + return nil + } + +} + +func (ap *AnsiParser) print() error { + return ap.eventHandler.Print(ap.context.currentChar) +} + +func (ap *AnsiParser) clear() error { + ap.context = &ansiContext{} + return nil +} + +func (ap *AnsiParser) execute() error { + return ap.eventHandler.Execute(ap.context.currentChar) +} diff --git a/vendor/github.com/Azure/go-ansiterm/states.go b/vendor/github.com/Azure/go-ansiterm/states.go new file mode 100644 index 0000000..f2ea1fc --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/states.go @@ -0,0 +1,71 @@ +package ansiterm + +type stateID int + +type state interface { + Enter() error + Exit() error + Handle(byte) (state, error) + Name() string + Transition(state) error +} + +type baseState struct { + name string + parser *AnsiParser +} + +func (base baseState) Enter() error { + return nil +} + +func (base baseState) Exit() error { + return nil +} + +func (base baseState) Handle(b byte) (s state, e error) { + + switch { + case b == CSI_ENTRY: + return base.parser.csiEntry, nil + case b == DCS_ENTRY: + return base.parser.dcsEntry, nil + case b == ANSI_ESCAPE_PRIMARY: + return base.parser.escape, nil + case b == OSC_STRING: + return base.parser.oscString, nil + case sliceContains(toGroundBytes, b): + return base.parser.ground, nil + } + + return nil, nil +} + +func (base baseState) Name() string { + return base.name +} + +func (base baseState) Transition(s state) error { + if s == base.parser.ground { + execBytes := []byte{0x18} + execBytes = append(execBytes, 0x1A) + execBytes = append(execBytes, getByteRange(0x80, 0x8F)...) + execBytes = append(execBytes, getByteRange(0x91, 0x97)...) + execBytes = append(execBytes, 0x99) + execBytes = append(execBytes, 0x9A) + + if sliceContains(execBytes, base.parser.context.currentChar) { + return base.parser.execute() + } + } + + return nil +} + +type dcsEntryState struct { + baseState +} + +type errorState struct { + baseState +} diff --git a/vendor/github.com/Azure/go-ansiterm/utilities.go b/vendor/github.com/Azure/go-ansiterm/utilities.go new file mode 100644 index 0000000..3921144 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/utilities.go @@ -0,0 +1,21 @@ +package ansiterm + +import ( + "strconv" +) + +func sliceContains(bytes []byte, b byte) bool { + for _, v := range bytes { + if v == b { + return true + } + } + + return false +} + +func convertBytesToInteger(bytes []byte) int { + s := string(bytes) + i, _ := strconv.Atoi(s) + return i +} diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/ansi.go b/vendor/github.com/Azure/go-ansiterm/winterm/ansi.go new file mode 100644 index 0000000..daf2f06 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/winterm/ansi.go @@ -0,0 +1,182 @@ +// +build windows + +package winterm + +import ( + "fmt" + "os" + "strconv" + "strings" + "syscall" + + "github.com/Azure/go-ansiterm" +) + +// Windows keyboard constants +// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx. +const ( + VK_PRIOR = 0x21 // PAGE UP key + VK_NEXT = 0x22 // PAGE DOWN key + VK_END = 0x23 // END key + VK_HOME = 0x24 // HOME key + VK_LEFT = 0x25 // LEFT ARROW key + VK_UP = 0x26 // UP ARROW key + VK_RIGHT = 0x27 // RIGHT ARROW key + VK_DOWN = 0x28 // DOWN ARROW key + VK_SELECT = 0x29 // SELECT key + VK_PRINT = 0x2A // PRINT key + VK_EXECUTE = 0x2B // EXECUTE key + VK_SNAPSHOT = 0x2C // PRINT SCREEN key + VK_INSERT = 0x2D // INS key + VK_DELETE = 0x2E // DEL key + VK_HELP = 0x2F // HELP key + VK_F1 = 0x70 // F1 key + VK_F2 = 0x71 // F2 key + VK_F3 = 0x72 // F3 key + VK_F4 = 0x73 // F4 key + VK_F5 = 0x74 // F5 key + VK_F6 = 0x75 // F6 key + VK_F7 = 0x76 // F7 key + VK_F8 = 0x77 // F8 key + VK_F9 = 0x78 // F9 key + VK_F10 = 0x79 // F10 key + VK_F11 = 0x7A // F11 key + VK_F12 = 0x7B // F12 key + + RIGHT_ALT_PRESSED = 0x0001 + LEFT_ALT_PRESSED = 0x0002 + RIGHT_CTRL_PRESSED = 0x0004 + LEFT_CTRL_PRESSED = 0x0008 + SHIFT_PRESSED = 0x0010 + NUMLOCK_ON = 0x0020 + SCROLLLOCK_ON = 0x0040 + CAPSLOCK_ON = 0x0080 + ENHANCED_KEY = 0x0100 +) + +type ansiCommand struct { + CommandBytes []byte + Command string + Parameters []string + IsSpecial bool +} + +func newAnsiCommand(command []byte) *ansiCommand { + + if isCharacterSelectionCmdChar(command[1]) { + // Is Character Set Selection commands + return &ansiCommand{ + CommandBytes: command, + Command: string(command), + IsSpecial: true, + } + } + + // last char is command character + lastCharIndex := len(command) - 1 + + ac := &ansiCommand{ + CommandBytes: command, + Command: string(command[lastCharIndex]), + IsSpecial: false, + } + + // more than a single escape + if lastCharIndex != 0 { + start := 1 + // skip if double char escape sequence + if command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_ESCAPE_SECONDARY { + start++ + } + // convert this to GetNextParam method + ac.Parameters = strings.Split(string(command[start:lastCharIndex]), ansiterm.ANSI_PARAMETER_SEP) + } + + return ac +} + +func (ac *ansiCommand) paramAsSHORT(index int, defaultValue int16) int16 { + if index < 0 || index >= len(ac.Parameters) { + return defaultValue + } + + param, err := strconv.ParseInt(ac.Parameters[index], 10, 16) + if err != nil { + return defaultValue + } + + return int16(param) +} + +func (ac *ansiCommand) String() string { + return fmt.Sprintf("0x%v \"%v\" (\"%v\")", + bytesToHex(ac.CommandBytes), + ac.Command, + strings.Join(ac.Parameters, "\",\"")) +} + +// isAnsiCommandChar returns true if the passed byte falls within the range of ANSI commands. +// See http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html. +func isAnsiCommandChar(b byte) bool { + switch { + case ansiterm.ANSI_COMMAND_FIRST <= b && b <= ansiterm.ANSI_COMMAND_LAST && b != ansiterm.ANSI_ESCAPE_SECONDARY: + return true + case b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_OSC || b == ansiterm.ANSI_CMD_DECPAM || b == ansiterm.ANSI_CMD_DECPNM: + // non-CSI escape sequence terminator + return true + case b == ansiterm.ANSI_CMD_STR_TERM || b == ansiterm.ANSI_BEL: + // String escape sequence terminator + return true + } + return false +} + +func isXtermOscSequence(command []byte, current byte) bool { + return (len(command) >= 2 && command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_CMD_OSC && current != ansiterm.ANSI_BEL) +} + +func isCharacterSelectionCmdChar(b byte) bool { + return (b == ansiterm.ANSI_CMD_G0 || b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_G2 || b == ansiterm.ANSI_CMD_G3) +} + +// bytesToHex converts a slice of bytes to a human-readable string. +func bytesToHex(b []byte) string { + hex := make([]string, len(b)) + for i, ch := range b { + hex[i] = fmt.Sprintf("%X", ch) + } + return strings.Join(hex, "") +} + +// ensureInRange adjusts the passed value, if necessary, to ensure it is within +// the passed min / max range. +func ensureInRange(n int16, min int16, max int16) int16 { + if n < min { + return min + } else if n > max { + return max + } else { + return n + } +} + +func GetStdFile(nFile int) (*os.File, uintptr) { + var file *os.File + switch nFile { + case syscall.STD_INPUT_HANDLE: + file = os.Stdin + case syscall.STD_OUTPUT_HANDLE: + file = os.Stdout + case syscall.STD_ERROR_HANDLE: + file = os.Stderr + default: + panic(fmt.Errorf("Invalid standard handle identifier: %v", nFile)) + } + + fd, err := syscall.GetStdHandle(nFile) + if err != nil { + panic(fmt.Errorf("Invalid standard handle indentifier: %v -- %v", nFile, err)) + } + + return file, uintptr(fd) +} diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/api.go b/vendor/github.com/Azure/go-ansiterm/winterm/api.go new file mode 100644 index 0000000..462d92f --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/winterm/api.go @@ -0,0 +1,322 @@ +// +build windows + +package winterm + +import ( + "fmt" + "syscall" + "unsafe" +) + +//=========================================================================================================== +// IMPORTANT NOTE: +// +// The methods below make extensive use of the "unsafe" package to obtain the required pointers. +// Beginning in Go 1.3, the garbage collector may release local variables (e.g., incoming arguments, stack +// variables) the pointers reference *before* the API completes. +// +// As a result, in those cases, the code must hint that the variables remain in active by invoking the +// dummy method "use" (see below). Newer versions of Go are planned to change the mechanism to no longer +// require unsafe pointers. +// +// If you add or modify methods, ENSURE protection of local variables through the "use" builtin to inform +// the garbage collector the variables remain in use if: +// +// -- The value is not a pointer (e.g., int32, struct) +// -- The value is not referenced by the method after passing the pointer to Windows +// +// See http://golang.org/doc/go1.3. +//=========================================================================================================== + +var ( + kernel32DLL = syscall.NewLazyDLL("kernel32.dll") + + getConsoleCursorInfoProc = kernel32DLL.NewProc("GetConsoleCursorInfo") + setConsoleCursorInfoProc = kernel32DLL.NewProc("SetConsoleCursorInfo") + setConsoleCursorPositionProc = kernel32DLL.NewProc("SetConsoleCursorPosition") + setConsoleModeProc = kernel32DLL.NewProc("SetConsoleMode") + getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo") + setConsoleScreenBufferSizeProc = kernel32DLL.NewProc("SetConsoleScreenBufferSize") + scrollConsoleScreenBufferProc = kernel32DLL.NewProc("ScrollConsoleScreenBufferA") + setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute") + setConsoleWindowInfoProc = kernel32DLL.NewProc("SetConsoleWindowInfo") + writeConsoleOutputProc = kernel32DLL.NewProc("WriteConsoleOutputW") + readConsoleInputProc = kernel32DLL.NewProc("ReadConsoleInputW") + waitForSingleObjectProc = kernel32DLL.NewProc("WaitForSingleObject") +) + +// Windows Console constants +const ( + // Console modes + // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx. + ENABLE_PROCESSED_INPUT = 0x0001 + ENABLE_LINE_INPUT = 0x0002 + ENABLE_ECHO_INPUT = 0x0004 + ENABLE_WINDOW_INPUT = 0x0008 + ENABLE_MOUSE_INPUT = 0x0010 + ENABLE_INSERT_MODE = 0x0020 + ENABLE_QUICK_EDIT_MODE = 0x0040 + ENABLE_EXTENDED_FLAGS = 0x0080 + + ENABLE_PROCESSED_OUTPUT = 0x0001 + ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 + + // Character attributes + // Note: + // -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan). + // Clearing all foreground or background colors results in black; setting all creates white. + // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes. + FOREGROUND_BLUE uint16 = 0x0001 + FOREGROUND_GREEN uint16 = 0x0002 + FOREGROUND_RED uint16 = 0x0004 + FOREGROUND_INTENSITY uint16 = 0x0008 + FOREGROUND_MASK uint16 = 0x000F + + BACKGROUND_BLUE uint16 = 0x0010 + BACKGROUND_GREEN uint16 = 0x0020 + BACKGROUND_RED uint16 = 0x0040 + BACKGROUND_INTENSITY uint16 = 0x0080 + BACKGROUND_MASK uint16 = 0x00F0 + + COMMON_LVB_MASK uint16 = 0xFF00 + COMMON_LVB_REVERSE_VIDEO uint16 = 0x4000 + COMMON_LVB_UNDERSCORE uint16 = 0x8000 + + // Input event types + // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx. + KEY_EVENT = 0x0001 + MOUSE_EVENT = 0x0002 + WINDOW_BUFFER_SIZE_EVENT = 0x0004 + MENU_EVENT = 0x0008 + FOCUS_EVENT = 0x0010 + + // WaitForSingleObject return codes + WAIT_ABANDONED = 0x00000080 + WAIT_FAILED = 0xFFFFFFFF + WAIT_SIGNALED = 0x0000000 + WAIT_TIMEOUT = 0x00000102 + + // WaitForSingleObject wait duration + WAIT_INFINITE = 0xFFFFFFFF + WAIT_ONE_SECOND = 1000 + WAIT_HALF_SECOND = 500 + WAIT_QUARTER_SECOND = 250 +) + +// Windows API Console types +// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682101(v=vs.85).aspx for Console specific types (e.g., COORD) +// -- See https://msdn.microsoft.com/en-us/library/aa296569(v=vs.60).aspx for comments on alignment +type ( + CHAR_INFO struct { + UnicodeChar uint16 + Attributes uint16 + } + + CONSOLE_CURSOR_INFO struct { + Size uint32 + Visible int32 + } + + CONSOLE_SCREEN_BUFFER_INFO struct { + Size COORD + CursorPosition COORD + Attributes uint16 + Window SMALL_RECT + MaximumWindowSize COORD + } + + COORD struct { + X int16 + Y int16 + } + + SMALL_RECT struct { + Left int16 + Top int16 + Right int16 + Bottom int16 + } + + // INPUT_RECORD is a C/C++ union of which KEY_EVENT_RECORD is one case, it is also the largest + // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx. + INPUT_RECORD struct { + EventType uint16 + KeyEvent KEY_EVENT_RECORD + } + + KEY_EVENT_RECORD struct { + KeyDown int32 + RepeatCount uint16 + VirtualKeyCode uint16 + VirtualScanCode uint16 + UnicodeChar uint16 + ControlKeyState uint32 + } + + WINDOW_BUFFER_SIZE struct { + Size COORD + } +) + +// boolToBOOL converts a Go bool into a Windows int32. +func boolToBOOL(f bool) int32 { + if f { + return int32(1) + } else { + return int32(0) + } +} + +// GetConsoleCursorInfo retrieves information about the size and visiblity of the console cursor. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683163(v=vs.85).aspx. +func GetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error { + r1, r2, err := getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0) + return checkError(r1, r2, err) +} + +// SetConsoleCursorInfo sets the size and visiblity of the console cursor. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx. +func SetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error { + r1, r2, err := setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0) + return checkError(r1, r2, err) +} + +// SetConsoleCursorPosition location of the console cursor. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx. +func SetConsoleCursorPosition(handle uintptr, coord COORD) error { + r1, r2, err := setConsoleCursorPositionProc.Call(handle, coordToPointer(coord)) + use(coord) + return checkError(r1, r2, err) +} + +// GetConsoleMode gets the console mode for given file descriptor +// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx. +func GetConsoleMode(handle uintptr) (mode uint32, err error) { + err = syscall.GetConsoleMode(syscall.Handle(handle), &mode) + return mode, err +} + +// SetConsoleMode sets the console mode for given file descriptor +// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx. +func SetConsoleMode(handle uintptr, mode uint32) error { + r1, r2, err := setConsoleModeProc.Call(handle, uintptr(mode), 0) + use(mode) + return checkError(r1, r2, err) +} + +// GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer. +// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx. +func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) { + info := CONSOLE_SCREEN_BUFFER_INFO{} + err := checkError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0)) + if err != nil { + return nil, err + } + return &info, nil +} + +func ScrollConsoleScreenBuffer(handle uintptr, scrollRect SMALL_RECT, clipRect SMALL_RECT, destOrigin COORD, char CHAR_INFO) error { + r1, r2, err := scrollConsoleScreenBufferProc.Call(handle, uintptr(unsafe.Pointer(&scrollRect)), uintptr(unsafe.Pointer(&clipRect)), coordToPointer(destOrigin), uintptr(unsafe.Pointer(&char))) + use(scrollRect) + use(clipRect) + use(destOrigin) + use(char) + return checkError(r1, r2, err) +} + +// SetConsoleScreenBufferSize sets the size of the console screen buffer. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686044(v=vs.85).aspx. +func SetConsoleScreenBufferSize(handle uintptr, coord COORD) error { + r1, r2, err := setConsoleScreenBufferSizeProc.Call(handle, coordToPointer(coord)) + use(coord) + return checkError(r1, r2, err) +} + +// SetConsoleTextAttribute sets the attributes of characters written to the +// console screen buffer by the WriteFile or WriteConsole function. +// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx. +func SetConsoleTextAttribute(handle uintptr, attribute uint16) error { + r1, r2, err := setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0) + use(attribute) + return checkError(r1, r2, err) +} + +// SetConsoleWindowInfo sets the size and position of the console screen buffer's window. +// Note that the size and location must be within and no larger than the backing console screen buffer. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686125(v=vs.85).aspx. +func SetConsoleWindowInfo(handle uintptr, isAbsolute bool, rect SMALL_RECT) error { + r1, r2, err := setConsoleWindowInfoProc.Call(handle, uintptr(boolToBOOL(isAbsolute)), uintptr(unsafe.Pointer(&rect))) + use(isAbsolute) + use(rect) + return checkError(r1, r2, err) +} + +// WriteConsoleOutput writes the CHAR_INFOs from the provided buffer to the active console buffer. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx. +func WriteConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) error { + r1, r2, err := writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), coordToPointer(bufferSize), coordToPointer(bufferCoord), uintptr(unsafe.Pointer(writeRegion))) + use(buffer) + use(bufferSize) + use(bufferCoord) + return checkError(r1, r2, err) +} + +// ReadConsoleInput reads (and removes) data from the console input buffer. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx. +func ReadConsoleInput(handle uintptr, buffer []INPUT_RECORD, count *uint32) error { + r1, r2, err := readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), uintptr(len(buffer)), uintptr(unsafe.Pointer(count))) + use(buffer) + return checkError(r1, r2, err) +} + +// WaitForSingleObject waits for the passed handle to be signaled. +// It returns true if the handle was signaled; false otherwise. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx. +func WaitForSingleObject(handle uintptr, msWait uint32) (bool, error) { + r1, _, err := waitForSingleObjectProc.Call(handle, uintptr(uint32(msWait))) + switch r1 { + case WAIT_ABANDONED, WAIT_TIMEOUT: + return false, nil + case WAIT_SIGNALED: + return true, nil + } + use(msWait) + return false, err +} + +// String helpers +func (info CONSOLE_SCREEN_BUFFER_INFO) String() string { + return fmt.Sprintf("Size(%v) Cursor(%v) Window(%v) Max(%v)", info.Size, info.CursorPosition, info.Window, info.MaximumWindowSize) +} + +func (coord COORD) String() string { + return fmt.Sprintf("%v,%v", coord.X, coord.Y) +} + +func (rect SMALL_RECT) String() string { + return fmt.Sprintf("(%v,%v),(%v,%v)", rect.Left, rect.Top, rect.Right, rect.Bottom) +} + +// checkError evaluates the results of a Windows API call and returns the error if it failed. +func checkError(r1, r2 uintptr, err error) error { + // Windows APIs return non-zero to indicate success + if r1 != 0 { + return nil + } + + // Return the error if provided, otherwise default to EINVAL + if err != nil { + return err + } + return syscall.EINVAL +} + +// coordToPointer converts a COORD into a uintptr (by fooling the type system). +func coordToPointer(c COORD) uintptr { + // Note: This code assumes the two SHORTs are correctly laid out; the "cast" to uint32 is just to get a pointer to pass. + return uintptr(*((*uint32)(unsafe.Pointer(&c)))) +} + +// use is a no-op, but the compiler cannot see that it is. +// Calling use(p) ensures that p is kept live until that point. +func use(p interface{}) {} diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go b/vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go new file mode 100644 index 0000000..cbec8f7 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go @@ -0,0 +1,100 @@ +// +build windows + +package winterm + +import "github.com/Azure/go-ansiterm" + +const ( + FOREGROUND_COLOR_MASK = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE + BACKGROUND_COLOR_MASK = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE +) + +// collectAnsiIntoWindowsAttributes modifies the passed Windows text mode flags to reflect the +// request represented by the passed ANSI mode. +func collectAnsiIntoWindowsAttributes(windowsMode uint16, inverted bool, baseMode uint16, ansiMode int16) (uint16, bool) { + switch ansiMode { + + // Mode styles + case ansiterm.ANSI_SGR_BOLD: + windowsMode = windowsMode | FOREGROUND_INTENSITY + + case ansiterm.ANSI_SGR_DIM, ansiterm.ANSI_SGR_BOLD_DIM_OFF: + windowsMode &^= FOREGROUND_INTENSITY + + case ansiterm.ANSI_SGR_UNDERLINE: + windowsMode = windowsMode | COMMON_LVB_UNDERSCORE + + case ansiterm.ANSI_SGR_REVERSE: + inverted = true + + case ansiterm.ANSI_SGR_REVERSE_OFF: + inverted = false + + case ansiterm.ANSI_SGR_UNDERLINE_OFF: + windowsMode &^= COMMON_LVB_UNDERSCORE + + // Foreground colors + case ansiterm.ANSI_SGR_FOREGROUND_DEFAULT: + windowsMode = (windowsMode &^ FOREGROUND_MASK) | (baseMode & FOREGROUND_MASK) + + case ansiterm.ANSI_SGR_FOREGROUND_BLACK: + windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) + + case ansiterm.ANSI_SGR_FOREGROUND_RED: + windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED + + case ansiterm.ANSI_SGR_FOREGROUND_GREEN: + windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN + + case ansiterm.ANSI_SGR_FOREGROUND_YELLOW: + windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN + + case ansiterm.ANSI_SGR_FOREGROUND_BLUE: + windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_BLUE + + case ansiterm.ANSI_SGR_FOREGROUND_MAGENTA: + windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_BLUE + + case ansiterm.ANSI_SGR_FOREGROUND_CYAN: + windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN | FOREGROUND_BLUE + + case ansiterm.ANSI_SGR_FOREGROUND_WHITE: + windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE + + // Background colors + case ansiterm.ANSI_SGR_BACKGROUND_DEFAULT: + // Black with no intensity + windowsMode = (windowsMode &^ BACKGROUND_MASK) | (baseMode & BACKGROUND_MASK) + + case ansiterm.ANSI_SGR_BACKGROUND_BLACK: + windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) + + case ansiterm.ANSI_SGR_BACKGROUND_RED: + windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED + + case ansiterm.ANSI_SGR_BACKGROUND_GREEN: + windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN + + case ansiterm.ANSI_SGR_BACKGROUND_YELLOW: + windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN + + case ansiterm.ANSI_SGR_BACKGROUND_BLUE: + windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_BLUE + + case ansiterm.ANSI_SGR_BACKGROUND_MAGENTA: + windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_BLUE + + case ansiterm.ANSI_SGR_BACKGROUND_CYAN: + windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN | BACKGROUND_BLUE + + case ansiterm.ANSI_SGR_BACKGROUND_WHITE: + windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE + } + + return windowsMode, inverted +} + +// invertAttributes inverts the foreground and background colors of a Windows attributes value +func invertAttributes(windowsMode uint16) uint16 { + return (COMMON_LVB_MASK & windowsMode) | ((FOREGROUND_MASK & windowsMode) << 4) | ((BACKGROUND_MASK & windowsMode) >> 4) +} diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go b/vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go new file mode 100644 index 0000000..f015723 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go @@ -0,0 +1,101 @@ +// +build windows + +package winterm + +const ( + horizontal = iota + vertical +) + +func (h *windowsAnsiEventHandler) getCursorWindow(info *CONSOLE_SCREEN_BUFFER_INFO) SMALL_RECT { + if h.originMode { + sr := h.effectiveSr(info.Window) + return SMALL_RECT{ + Top: sr.top, + Bottom: sr.bottom, + Left: 0, + Right: info.Size.X - 1, + } + } else { + return SMALL_RECT{ + Top: info.Window.Top, + Bottom: info.Window.Bottom, + Left: 0, + Right: info.Size.X - 1, + } + } +} + +// setCursorPosition sets the cursor to the specified position, bounded to the screen size +func (h *windowsAnsiEventHandler) setCursorPosition(position COORD, window SMALL_RECT) error { + position.X = ensureInRange(position.X, window.Left, window.Right) + position.Y = ensureInRange(position.Y, window.Top, window.Bottom) + err := SetConsoleCursorPosition(h.fd, position) + if err != nil { + return err + } + logger.Infof("Cursor position set: (%d, %d)", position.X, position.Y) + return err +} + +func (h *windowsAnsiEventHandler) moveCursorVertical(param int) error { + return h.moveCursor(vertical, param) +} + +func (h *windowsAnsiEventHandler) moveCursorHorizontal(param int) error { + return h.moveCursor(horizontal, param) +} + +func (h *windowsAnsiEventHandler) moveCursor(moveMode int, param int) error { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + position := info.CursorPosition + switch moveMode { + case horizontal: + position.X += int16(param) + case vertical: + position.Y += int16(param) + } + + if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil { + return err + } + + return nil +} + +func (h *windowsAnsiEventHandler) moveCursorLine(param int) error { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + position := info.CursorPosition + position.X = 0 + position.Y += int16(param) + + if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil { + return err + } + + return nil +} + +func (h *windowsAnsiEventHandler) moveCursorColumn(param int) error { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + position := info.CursorPosition + position.X = int16(param) - 1 + + if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go b/vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go new file mode 100644 index 0000000..244b5fa --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go @@ -0,0 +1,84 @@ +// +build windows + +package winterm + +import "github.com/Azure/go-ansiterm" + +func (h *windowsAnsiEventHandler) clearRange(attributes uint16, fromCoord COORD, toCoord COORD) error { + // Ignore an invalid (negative area) request + if toCoord.Y < fromCoord.Y { + return nil + } + + var err error + + var coordStart = COORD{} + var coordEnd = COORD{} + + xCurrent, yCurrent := fromCoord.X, fromCoord.Y + xEnd, yEnd := toCoord.X, toCoord.Y + + // Clear any partial initial line + if xCurrent > 0 { + coordStart.X, coordStart.Y = xCurrent, yCurrent + coordEnd.X, coordEnd.Y = xEnd, yCurrent + + err = h.clearRect(attributes, coordStart, coordEnd) + if err != nil { + return err + } + + xCurrent = 0 + yCurrent += 1 + } + + // Clear intervening rectangular section + if yCurrent < yEnd { + coordStart.X, coordStart.Y = xCurrent, yCurrent + coordEnd.X, coordEnd.Y = xEnd, yEnd-1 + + err = h.clearRect(attributes, coordStart, coordEnd) + if err != nil { + return err + } + + xCurrent = 0 + yCurrent = yEnd + } + + // Clear remaining partial ending line + coordStart.X, coordStart.Y = xCurrent, yCurrent + coordEnd.X, coordEnd.Y = xEnd, yEnd + + err = h.clearRect(attributes, coordStart, coordEnd) + if err != nil { + return err + } + + return nil +} + +func (h *windowsAnsiEventHandler) clearRect(attributes uint16, fromCoord COORD, toCoord COORD) error { + region := SMALL_RECT{Top: fromCoord.Y, Left: fromCoord.X, Bottom: toCoord.Y, Right: toCoord.X} + width := toCoord.X - fromCoord.X + 1 + height := toCoord.Y - fromCoord.Y + 1 + size := uint32(width) * uint32(height) + + if size <= 0 { + return nil + } + + buffer := make([]CHAR_INFO, size) + + char := CHAR_INFO{ansiterm.FILL_CHARACTER, attributes} + for i := 0; i < int(size); i++ { + buffer[i] = char + } + + err := WriteConsoleOutput(h.fd, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, ®ion) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go b/vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go new file mode 100644 index 0000000..706d270 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go @@ -0,0 +1,118 @@ +// +build windows + +package winterm + +// effectiveSr gets the current effective scroll region in buffer coordinates +func (h *windowsAnsiEventHandler) effectiveSr(window SMALL_RECT) scrollRegion { + top := addInRange(window.Top, h.sr.top, window.Top, window.Bottom) + bottom := addInRange(window.Top, h.sr.bottom, window.Top, window.Bottom) + if top >= bottom { + top = window.Top + bottom = window.Bottom + } + return scrollRegion{top: top, bottom: bottom} +} + +func (h *windowsAnsiEventHandler) scrollUp(param int) error { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + sr := h.effectiveSr(info.Window) + return h.scroll(param, sr, info) +} + +func (h *windowsAnsiEventHandler) scrollDown(param int) error { + return h.scrollUp(-param) +} + +func (h *windowsAnsiEventHandler) deleteLines(param int) error { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + start := info.CursorPosition.Y + sr := h.effectiveSr(info.Window) + // Lines cannot be inserted or deleted outside the scrolling region. + if start >= sr.top && start <= sr.bottom { + sr.top = start + return h.scroll(param, sr, info) + } else { + return nil + } +} + +func (h *windowsAnsiEventHandler) insertLines(param int) error { + return h.deleteLines(-param) +} + +// scroll scrolls the provided scroll region by param lines. The scroll region is in buffer coordinates. +func (h *windowsAnsiEventHandler) scroll(param int, sr scrollRegion, info *CONSOLE_SCREEN_BUFFER_INFO) error { + logger.Infof("scroll: scrollTop: %d, scrollBottom: %d", sr.top, sr.bottom) + logger.Infof("scroll: windowTop: %d, windowBottom: %d", info.Window.Top, info.Window.Bottom) + + // Copy from and clip to the scroll region (full buffer width) + scrollRect := SMALL_RECT{ + Top: sr.top, + Bottom: sr.bottom, + Left: 0, + Right: info.Size.X - 1, + } + + // Origin to which area should be copied + destOrigin := COORD{ + X: 0, + Y: sr.top - int16(param), + } + + char := CHAR_INFO{ + UnicodeChar: ' ', + Attributes: h.attributes, + } + + if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil { + return err + } + return nil +} + +func (h *windowsAnsiEventHandler) deleteCharacters(param int) error { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + return h.scrollLine(param, info.CursorPosition, info) +} + +func (h *windowsAnsiEventHandler) insertCharacters(param int) error { + return h.deleteCharacters(-param) +} + +// scrollLine scrolls a line horizontally starting at the provided position by a number of columns. +func (h *windowsAnsiEventHandler) scrollLine(columns int, position COORD, info *CONSOLE_SCREEN_BUFFER_INFO) error { + // Copy from and clip to the scroll region (full buffer width) + scrollRect := SMALL_RECT{ + Top: position.Y, + Bottom: position.Y, + Left: position.X, + Right: info.Size.X - 1, + } + + // Origin to which area should be copied + destOrigin := COORD{ + X: position.X - int16(columns), + Y: position.Y, + } + + char := CHAR_INFO{ + UnicodeChar: ' ', + Attributes: h.attributes, + } + + if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/utilities.go b/vendor/github.com/Azure/go-ansiterm/winterm/utilities.go new file mode 100644 index 0000000..afa7635 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/winterm/utilities.go @@ -0,0 +1,9 @@ +// +build windows + +package winterm + +// AddInRange increments a value by the passed quantity while ensuring the values +// always remain within the supplied min / max range. +func addInRange(n int16, increment int16, min int16, max int16) int16 { + return ensureInRange(n+increment, min, max) +} diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go b/vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go new file mode 100644 index 0000000..4d858ed --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go @@ -0,0 +1,726 @@ +// +build windows + +package winterm + +import ( + "bytes" + "io/ioutil" + "os" + "strconv" + + "github.com/Azure/go-ansiterm" + "github.com/Sirupsen/logrus" +) + +var logger *logrus.Logger + +type windowsAnsiEventHandler struct { + fd uintptr + file *os.File + infoReset *CONSOLE_SCREEN_BUFFER_INFO + sr scrollRegion + buffer bytes.Buffer + attributes uint16 + inverted bool + wrapNext bool + drewMarginByte bool + originMode bool + marginByte byte + curInfo *CONSOLE_SCREEN_BUFFER_INFO + curPos COORD +} + +func CreateWinEventHandler(fd uintptr, file *os.File) ansiterm.AnsiEventHandler { + logFile := ioutil.Discard + + if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" { + logFile, _ = os.Create("winEventHandler.log") + } + + logger = &logrus.Logger{ + Out: logFile, + Formatter: new(logrus.TextFormatter), + Level: logrus.DebugLevel, + } + + infoReset, err := GetConsoleScreenBufferInfo(fd) + if err != nil { + return nil + } + + return &windowsAnsiEventHandler{ + fd: fd, + file: file, + infoReset: infoReset, + attributes: infoReset.Attributes, + } +} + +type scrollRegion struct { + top int16 + bottom int16 +} + +// simulateLF simulates a LF or CR+LF by scrolling if necessary to handle the +// current cursor position and scroll region settings, in which case it returns +// true. If no special handling is necessary, then it does nothing and returns +// false. +// +// In the false case, the caller should ensure that a carriage return +// and line feed are inserted or that the text is otherwise wrapped. +func (h *windowsAnsiEventHandler) simulateLF(includeCR bool) (bool, error) { + if h.wrapNext { + if err := h.Flush(); err != nil { + return false, err + } + h.clearWrap() + } + pos, info, err := h.getCurrentInfo() + if err != nil { + return false, err + } + sr := h.effectiveSr(info.Window) + if pos.Y == sr.bottom { + // Scrolling is necessary. Let Windows automatically scroll if the scrolling region + // is the full window. + if sr.top == info.Window.Top && sr.bottom == info.Window.Bottom { + if includeCR { + pos.X = 0 + h.updatePos(pos) + } + return false, nil + } + + // A custom scroll region is active. Scroll the window manually to simulate + // the LF. + if err := h.Flush(); err != nil { + return false, err + } + logger.Info("Simulating LF inside scroll region") + if err := h.scrollUp(1); err != nil { + return false, err + } + if includeCR { + pos.X = 0 + if err := SetConsoleCursorPosition(h.fd, pos); err != nil { + return false, err + } + } + return true, nil + + } else if pos.Y < info.Window.Bottom { + // Let Windows handle the LF. + pos.Y++ + if includeCR { + pos.X = 0 + } + h.updatePos(pos) + return false, nil + } else { + // The cursor is at the bottom of the screen but outside the scroll + // region. Skip the LF. + logger.Info("Simulating LF outside scroll region") + if includeCR { + if err := h.Flush(); err != nil { + return false, err + } + pos.X = 0 + if err := SetConsoleCursorPosition(h.fd, pos); err != nil { + return false, err + } + } + return true, nil + } +} + +// executeLF executes a LF without a CR. +func (h *windowsAnsiEventHandler) executeLF() error { + handled, err := h.simulateLF(false) + if err != nil { + return err + } + if !handled { + // Windows LF will reset the cursor column position. Write the LF + // and restore the cursor position. + pos, _, err := h.getCurrentInfo() + if err != nil { + return err + } + h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED) + if pos.X != 0 { + if err := h.Flush(); err != nil { + return err + } + logger.Info("Resetting cursor position for LF without CR") + if err := SetConsoleCursorPosition(h.fd, pos); err != nil { + return err + } + } + } + return nil +} + +func (h *windowsAnsiEventHandler) Print(b byte) error { + if h.wrapNext { + h.buffer.WriteByte(h.marginByte) + h.clearWrap() + if _, err := h.simulateLF(true); err != nil { + return err + } + } + pos, info, err := h.getCurrentInfo() + if err != nil { + return err + } + if pos.X == info.Size.X-1 { + h.wrapNext = true + h.marginByte = b + } else { + pos.X++ + h.updatePos(pos) + h.buffer.WriteByte(b) + } + return nil +} + +func (h *windowsAnsiEventHandler) Execute(b byte) error { + switch b { + case ansiterm.ANSI_TAB: + logger.Info("Execute(TAB)") + // Move to the next tab stop, but preserve auto-wrap if already set. + if !h.wrapNext { + pos, info, err := h.getCurrentInfo() + if err != nil { + return err + } + pos.X = (pos.X + 8) - pos.X%8 + if pos.X >= info.Size.X { + pos.X = info.Size.X - 1 + } + if err := h.Flush(); err != nil { + return err + } + if err := SetConsoleCursorPosition(h.fd, pos); err != nil { + return err + } + } + return nil + + case ansiterm.ANSI_BEL: + h.buffer.WriteByte(ansiterm.ANSI_BEL) + return nil + + case ansiterm.ANSI_BACKSPACE: + if h.wrapNext { + if err := h.Flush(); err != nil { + return err + } + h.clearWrap() + } + pos, _, err := h.getCurrentInfo() + if err != nil { + return err + } + if pos.X > 0 { + pos.X-- + h.updatePos(pos) + h.buffer.WriteByte(ansiterm.ANSI_BACKSPACE) + } + return nil + + case ansiterm.ANSI_VERTICAL_TAB, ansiterm.ANSI_FORM_FEED: + // Treat as true LF. + return h.executeLF() + + case ansiterm.ANSI_LINE_FEED: + // Simulate a CR and LF for now since there is no way in go-ansiterm + // to tell if the LF should include CR (and more things break when it's + // missing than when it's incorrectly added). + handled, err := h.simulateLF(true) + if handled || err != nil { + return err + } + return h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED) + + case ansiterm.ANSI_CARRIAGE_RETURN: + if h.wrapNext { + if err := h.Flush(); err != nil { + return err + } + h.clearWrap() + } + pos, _, err := h.getCurrentInfo() + if err != nil { + return err + } + if pos.X != 0 { + pos.X = 0 + h.updatePos(pos) + h.buffer.WriteByte(ansiterm.ANSI_CARRIAGE_RETURN) + } + return nil + + default: + return nil + } +} + +func (h *windowsAnsiEventHandler) CUU(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("CUU: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.moveCursorVertical(-param) +} + +func (h *windowsAnsiEventHandler) CUD(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("CUD: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.moveCursorVertical(param) +} + +func (h *windowsAnsiEventHandler) CUF(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("CUF: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.moveCursorHorizontal(param) +} + +func (h *windowsAnsiEventHandler) CUB(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("CUB: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.moveCursorHorizontal(-param) +} + +func (h *windowsAnsiEventHandler) CNL(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("CNL: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.moveCursorLine(param) +} + +func (h *windowsAnsiEventHandler) CPL(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("CPL: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.moveCursorLine(-param) +} + +func (h *windowsAnsiEventHandler) CHA(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("CHA: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.moveCursorColumn(param) +} + +func (h *windowsAnsiEventHandler) VPA(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("VPA: [[%d]]", param) + h.clearWrap() + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + window := h.getCursorWindow(info) + position := info.CursorPosition + position.Y = window.Top + int16(param) - 1 + return h.setCursorPosition(position, window) +} + +func (h *windowsAnsiEventHandler) CUP(row int, col int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("CUP: [[%d %d]]", row, col) + h.clearWrap() + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + window := h.getCursorWindow(info) + position := COORD{window.Left + int16(col) - 1, window.Top + int16(row) - 1} + return h.setCursorPosition(position, window) +} + +func (h *windowsAnsiEventHandler) HVP(row int, col int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("HVP: [[%d %d]]", row, col) + h.clearWrap() + return h.CUP(row, col) +} + +func (h *windowsAnsiEventHandler) DECTCEM(visible bool) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("DECTCEM: [%v]", []string{strconv.FormatBool(visible)}) + h.clearWrap() + return nil +} + +func (h *windowsAnsiEventHandler) DECOM(enable bool) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("DECOM: [%v]", []string{strconv.FormatBool(enable)}) + h.clearWrap() + h.originMode = enable + return h.CUP(1, 1) +} + +func (h *windowsAnsiEventHandler) DECCOLM(use132 bool) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("DECCOLM: [%v]", []string{strconv.FormatBool(use132)}) + h.clearWrap() + if err := h.ED(2); err != nil { + return err + } + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + targetWidth := int16(80) + if use132 { + targetWidth = 132 + } + if info.Size.X < targetWidth { + if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil { + logger.Info("set buffer failed:", err) + return err + } + } + window := info.Window + window.Left = 0 + window.Right = targetWidth - 1 + if err := SetConsoleWindowInfo(h.fd, true, window); err != nil { + logger.Info("set window failed:", err) + return err + } + if info.Size.X > targetWidth { + if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil { + logger.Info("set buffer failed:", err) + return err + } + } + return SetConsoleCursorPosition(h.fd, COORD{0, 0}) +} + +func (h *windowsAnsiEventHandler) ED(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("ED: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + + // [J -- Erases from the cursor to the end of the screen, including the cursor position. + // [1J -- Erases from the beginning of the screen to the cursor, including the cursor position. + // [2J -- Erases the complete display. The cursor does not move. + // Notes: + // -- Clearing the entire buffer, versus just the Window, works best for Windows Consoles + + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + var start COORD + var end COORD + + switch param { + case 0: + start = info.CursorPosition + end = COORD{info.Size.X - 1, info.Size.Y - 1} + + case 1: + start = COORD{0, 0} + end = info.CursorPosition + + case 2: + start = COORD{0, 0} + end = COORD{info.Size.X - 1, info.Size.Y - 1} + } + + err = h.clearRange(h.attributes, start, end) + if err != nil { + return err + } + + // If the whole buffer was cleared, move the window to the top while preserving + // the window-relative cursor position. + if param == 2 { + pos := info.CursorPosition + window := info.Window + pos.Y -= window.Top + window.Bottom -= window.Top + window.Top = 0 + if err := SetConsoleCursorPosition(h.fd, pos); err != nil { + return err + } + if err := SetConsoleWindowInfo(h.fd, true, window); err != nil { + return err + } + } + + return nil +} + +func (h *windowsAnsiEventHandler) EL(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("EL: [%v]", strconv.Itoa(param)) + h.clearWrap() + + // [K -- Erases from the cursor to the end of the line, including the cursor position. + // [1K -- Erases from the beginning of the line to the cursor, including the cursor position. + // [2K -- Erases the complete line. + + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + var start COORD + var end COORD + + switch param { + case 0: + start = info.CursorPosition + end = COORD{info.Size.X, info.CursorPosition.Y} + + case 1: + start = COORD{0, info.CursorPosition.Y} + end = info.CursorPosition + + case 2: + start = COORD{0, info.CursorPosition.Y} + end = COORD{info.Size.X, info.CursorPosition.Y} + } + + err = h.clearRange(h.attributes, start, end) + if err != nil { + return err + } + + return nil +} + +func (h *windowsAnsiEventHandler) IL(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("IL: [%v]", strconv.Itoa(param)) + h.clearWrap() + return h.insertLines(param) +} + +func (h *windowsAnsiEventHandler) DL(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("DL: [%v]", strconv.Itoa(param)) + h.clearWrap() + return h.deleteLines(param) +} + +func (h *windowsAnsiEventHandler) ICH(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("ICH: [%v]", strconv.Itoa(param)) + h.clearWrap() + return h.insertCharacters(param) +} + +func (h *windowsAnsiEventHandler) DCH(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("DCH: [%v]", strconv.Itoa(param)) + h.clearWrap() + return h.deleteCharacters(param) +} + +func (h *windowsAnsiEventHandler) SGR(params []int) error { + if err := h.Flush(); err != nil { + return err + } + strings := []string{} + for _, v := range params { + strings = append(strings, strconv.Itoa(v)) + } + + logger.Infof("SGR: [%v]", strings) + + if len(params) <= 0 { + h.attributes = h.infoReset.Attributes + h.inverted = false + } else { + for _, attr := range params { + + if attr == ansiterm.ANSI_SGR_RESET { + h.attributes = h.infoReset.Attributes + h.inverted = false + continue + } + + h.attributes, h.inverted = collectAnsiIntoWindowsAttributes(h.attributes, h.inverted, h.infoReset.Attributes, int16(attr)) + } + } + + attributes := h.attributes + if h.inverted { + attributes = invertAttributes(attributes) + } + err := SetConsoleTextAttribute(h.fd, attributes) + if err != nil { + return err + } + + return nil +} + +func (h *windowsAnsiEventHandler) SU(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("SU: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.scrollUp(param) +} + +func (h *windowsAnsiEventHandler) SD(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("SD: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.scrollDown(param) +} + +func (h *windowsAnsiEventHandler) DA(params []string) error { + logger.Infof("DA: [%v]", params) + // DA cannot be implemented because it must send data on the VT100 input stream, + // which is not available to go-ansiterm. + return nil +} + +func (h *windowsAnsiEventHandler) DECSTBM(top int, bottom int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("DECSTBM: [%d, %d]", top, bottom) + + // Windows is 0 indexed, Linux is 1 indexed + h.sr.top = int16(top - 1) + h.sr.bottom = int16(bottom - 1) + + // This command also moves the cursor to the origin. + h.clearWrap() + return h.CUP(1, 1) +} + +func (h *windowsAnsiEventHandler) RI() error { + if err := h.Flush(); err != nil { + return err + } + logger.Info("RI: []") + h.clearWrap() + + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + sr := h.effectiveSr(info.Window) + if info.CursorPosition.Y == sr.top { + return h.scrollDown(1) + } + + return h.moveCursorVertical(-1) +} + +func (h *windowsAnsiEventHandler) IND() error { + logger.Info("IND: []") + return h.executeLF() +} + +func (h *windowsAnsiEventHandler) Flush() error { + h.curInfo = nil + if h.buffer.Len() > 0 { + logger.Infof("Flush: [%s]", h.buffer.Bytes()) + if _, err := h.buffer.WriteTo(h.file); err != nil { + return err + } + } + + if h.wrapNext && !h.drewMarginByte { + logger.Infof("Flush: drawing margin byte '%c'", h.marginByte) + + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + charInfo := []CHAR_INFO{{UnicodeChar: uint16(h.marginByte), Attributes: info.Attributes}} + size := COORD{1, 1} + position := COORD{0, 0} + region := SMALL_RECT{Left: info.CursorPosition.X, Top: info.CursorPosition.Y, Right: info.CursorPosition.X, Bottom: info.CursorPosition.Y} + if err := WriteConsoleOutput(h.fd, charInfo, size, position, ®ion); err != nil { + return err + } + h.drewMarginByte = true + } + return nil +} + +// cacheConsoleInfo ensures that the current console screen information has been queried +// since the last call to Flush(). It must be called before accessing h.curInfo or h.curPos. +func (h *windowsAnsiEventHandler) getCurrentInfo() (COORD, *CONSOLE_SCREEN_BUFFER_INFO, error) { + if h.curInfo == nil { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return COORD{}, nil, err + } + h.curInfo = info + h.curPos = info.CursorPosition + } + return h.curPos, h.curInfo, nil +} + +func (h *windowsAnsiEventHandler) updatePos(pos COORD) { + if h.curInfo == nil { + panic("failed to call getCurrentInfo before calling updatePos") + } + h.curPos = pos +} + +// clearWrap clears the state where the cursor is in the margin +// waiting for the next character before wrapping the line. This must +// be done before most operations that act on the cursor. +func (h *windowsAnsiEventHandler) clearWrap() { + h.wrapNext = false + h.drewMarginByte = false +} diff --git a/vendor/github.com/Microsoft/hcsshim/LICENSE b/vendor/github.com/Microsoft/hcsshim/LICENSE new file mode 100644 index 0000000..49d2166 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Microsoft + +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. \ No newline at end of file diff --git a/vendor/github.com/Microsoft/hcsshim/README.md b/vendor/github.com/Microsoft/hcsshim/README.md new file mode 100644 index 0000000..30991a1 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/README.md @@ -0,0 +1,12 @@ +# hcsshim + +This package supports launching Windows Server containers from Go. It is +primarily used in the [Docker Engine](https://github.com/docker/docker) project, +but it can be freely used by other projects as well. + +This project has adopted the [Microsoft Open Source Code of +Conduct](https://opensource.microsoft.com/codeofconduct/). For more information +see the [Code of Conduct +FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact +[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional +questions or comments. diff --git a/vendor/github.com/Microsoft/hcsshim/activatelayer.go b/vendor/github.com/Microsoft/hcsshim/activatelayer.go new file mode 100644 index 0000000..efc4d80 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/activatelayer.go @@ -0,0 +1,28 @@ +package hcsshim + +import "github.com/Sirupsen/logrus" + +// ActivateLayer will find the layer with the given id and mount it's filesystem. +// For a read/write layer, the mounted filesystem will appear as a volume on the +// host, while a read-only layer is generally expected to be a no-op. +// An activated layer must later be deactivated via DeactivateLayer. +func ActivateLayer(info DriverInfo, id string) error { + title := "hcsshim::ActivateLayer " + logrus.Debugf(title+"Flavour %d ID %s", info.Flavour, id) + + infop, err := convertDriverInfo(info) + if err != nil { + logrus.Error(err) + return err + } + + err = activateLayer(&infop, id) + if err != nil { + err = makeErrorf(err, title, "id=%s flavour=%d", id, info.Flavour) + logrus.Error(err) + return err + } + + logrus.Debugf(title+" - succeeded id=%s flavour=%d", id, info.Flavour) + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/baselayer.go b/vendor/github.com/Microsoft/hcsshim/baselayer.go new file mode 100644 index 0000000..9babd4e --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/baselayer.go @@ -0,0 +1,183 @@ +package hcsshim + +import ( + "errors" + "os" + "path/filepath" + "syscall" + + "github.com/Microsoft/go-winio" +) + +type baseLayerWriter struct { + root string + f *os.File + bw *winio.BackupFileWriter + err error + hasUtilityVM bool + dirInfo []dirInfo +} + +type dirInfo struct { + path string + fileInfo winio.FileBasicInfo +} + +// reapplyDirectoryTimes reapplies directory modification, creation, etc. times +// after processing of the directory tree has completed. The times are expected +// to be ordered such that parent directories come before child directories. +func reapplyDirectoryTimes(dis []dirInfo) error { + for i := range dis { + di := &dis[len(dis)-i-1] // reverse order: process child directories first + f, err := winio.OpenForBackup(di.path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, syscall.FILE_SHARE_READ, syscall.OPEN_EXISTING) + if err != nil { + return err + } + + err = winio.SetFileBasicInfo(f, &di.fileInfo) + f.Close() + if err != nil { + return err + } + } + return nil +} + +func (w *baseLayerWriter) closeCurrentFile() error { + if w.f != nil { + err := w.bw.Close() + err2 := w.f.Close() + w.f = nil + w.bw = nil + if err != nil { + return err + } + if err2 != nil { + return err2 + } + } + return nil +} + +func (w *baseLayerWriter) Add(name string, fileInfo *winio.FileBasicInfo) (err error) { + defer func() { + if err != nil { + w.err = err + } + }() + + err = w.closeCurrentFile() + if err != nil { + return err + } + + if filepath.ToSlash(name) == `UtilityVM/Files` { + w.hasUtilityVM = true + } + + path := filepath.Join(w.root, name) + path, err = makeLongAbsPath(path) + if err != nil { + return err + } + + var f *os.File + defer func() { + if f != nil { + f.Close() + } + }() + + createmode := uint32(syscall.CREATE_NEW) + if fileInfo.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { + err := os.Mkdir(path, 0) + if err != nil && !os.IsExist(err) { + return err + } + createmode = syscall.OPEN_EXISTING + if fileInfo.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 { + w.dirInfo = append(w.dirInfo, dirInfo{path, *fileInfo}) + } + } + + mode := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | winio.WRITE_DAC | winio.WRITE_OWNER | winio.ACCESS_SYSTEM_SECURITY) + f, err = winio.OpenForBackup(path, mode, syscall.FILE_SHARE_READ, createmode) + if err != nil { + return makeError(err, "Failed to OpenForBackup", path) + } + + err = winio.SetFileBasicInfo(f, fileInfo) + if err != nil { + return makeError(err, "Failed to SetFileBasicInfo", path) + } + + w.f = f + w.bw = winio.NewBackupFileWriter(f, true) + f = nil + return nil +} + +func (w *baseLayerWriter) AddLink(name string, target string) (err error) { + defer func() { + if err != nil { + w.err = err + } + }() + + err = w.closeCurrentFile() + if err != nil { + return err + } + + linkpath, err := makeLongAbsPath(filepath.Join(w.root, name)) + if err != nil { + return err + } + + linktarget, err := makeLongAbsPath(filepath.Join(w.root, target)) + if err != nil { + return err + } + + return os.Link(linktarget, linkpath) +} + +func (w *baseLayerWriter) Remove(name string) error { + return errors.New("base layer cannot have tombstones") +} + +func (w *baseLayerWriter) Write(b []byte) (int, error) { + n, err := w.bw.Write(b) + if err != nil { + w.err = err + } + return n, err +} + +func (w *baseLayerWriter) Close() error { + err := w.closeCurrentFile() + if err != nil { + return err + } + if w.err == nil { + // Restore the file times of all the directories, since they may have + // been modified by creating child directories. + err = reapplyDirectoryTimes(w.dirInfo) + if err != nil { + return err + } + + err = ProcessBaseLayer(w.root) + if err != nil { + return err + } + + if w.hasUtilityVM { + err = ProcessUtilityVMImage(filepath.Join(w.root, "UtilityVM")) + if err != nil { + return err + } + } + } + return w.err +} diff --git a/vendor/github.com/Microsoft/hcsshim/callback.go b/vendor/github.com/Microsoft/hcsshim/callback.go new file mode 100644 index 0000000..e8c2b00 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/callback.go @@ -0,0 +1,79 @@ +package hcsshim + +import ( + "sync" + "syscall" +) + +var ( + nextCallback uintptr + callbackMap = map[uintptr]*notifcationWatcherContext{} + callbackMapLock = sync.RWMutex{} + + notificationWatcherCallback = syscall.NewCallback(notificationWatcher) + + // Notifications for HCS_SYSTEM handles + hcsNotificationSystemExited hcsNotification = 0x00000001 + hcsNotificationSystemCreateCompleted hcsNotification = 0x00000002 + hcsNotificationSystemStartCompleted hcsNotification = 0x00000003 + hcsNotificationSystemPauseCompleted hcsNotification = 0x00000004 + hcsNotificationSystemResumeCompleted hcsNotification = 0x00000005 + + // Notifications for HCS_PROCESS handles + hcsNotificationProcessExited hcsNotification = 0x00010000 + + // Common notifications + hcsNotificationInvalid hcsNotification = 0x00000000 + hcsNotificationServiceDisconnect hcsNotification = 0x01000000 +) + +type hcsNotification uint32 +type notificationChannel chan error + +type notifcationWatcherContext struct { + channels notificationChannels + handle hcsCallback +} + +type notificationChannels map[hcsNotification]notificationChannel + +func newChannels() notificationChannels { + channels := make(notificationChannels) + + channels[hcsNotificationSystemExited] = make(notificationChannel, 1) + channels[hcsNotificationSystemCreateCompleted] = make(notificationChannel, 1) + channels[hcsNotificationSystemStartCompleted] = make(notificationChannel, 1) + channels[hcsNotificationSystemPauseCompleted] = make(notificationChannel, 1) + channels[hcsNotificationSystemResumeCompleted] = make(notificationChannel, 1) + channels[hcsNotificationProcessExited] = make(notificationChannel, 1) + channels[hcsNotificationServiceDisconnect] = make(notificationChannel, 1) + return channels +} +func closeChannels(channels notificationChannels) { + close(channels[hcsNotificationSystemExited]) + close(channels[hcsNotificationSystemCreateCompleted]) + close(channels[hcsNotificationSystemStartCompleted]) + close(channels[hcsNotificationSystemPauseCompleted]) + close(channels[hcsNotificationSystemResumeCompleted]) + close(channels[hcsNotificationProcessExited]) + close(channels[hcsNotificationServiceDisconnect]) +} + +func notificationWatcher(notificationType hcsNotification, callbackNumber uintptr, notificationStatus uintptr, notificationData *uint16) uintptr { + var result error + if int32(notificationStatus) < 0 { + result = syscall.Errno(win32FromHresult(notificationStatus)) + } + + callbackMapLock.RLock() + context := callbackMap[callbackNumber] + callbackMapLock.RUnlock() + + if context == nil { + return 0 + } + + context.channels[notificationType] <- result + + return 0 +} diff --git a/vendor/github.com/Microsoft/hcsshim/cgo.go b/vendor/github.com/Microsoft/hcsshim/cgo.go new file mode 100644 index 0000000..2003332 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/cgo.go @@ -0,0 +1,7 @@ +package hcsshim + +import "C" + +// This import is needed to make the library compile as CGO because HCSSHIM +// only works with CGO due to callbacks from HCS comming back from a C thread +// which is not supported without CGO. See https://github.com/golang/go/issues/10973 diff --git a/vendor/github.com/Microsoft/hcsshim/container.go b/vendor/github.com/Microsoft/hcsshim/container.go new file mode 100644 index 0000000..e43cc14 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/container.go @@ -0,0 +1,750 @@ +package hcsshim + +import ( + "encoding/json" + "fmt" + "os" + "runtime" + "sync" + "syscall" + "time" + + "github.com/Sirupsen/logrus" +) + +var ( + defaultTimeout = time.Minute * 4 +) + +const ( + pendingUpdatesQuery = `{ "PropertyTypes" : ["PendingUpdates"]}` + statisticsQuery = `{ "PropertyTypes" : ["Statistics"]}` + processListQuery = `{ "PropertyTypes" : ["ProcessList"]}` +) + +type container struct { + handleLock sync.RWMutex + handle hcsSystem + id string + callbackNumber uintptr +} + +// ContainerProperties holds the properties for a container and the processes running in that container +type ContainerProperties struct { + ID string `json:"Id"` + Name string + SystemType string + Owner string + SiloGUID string `json:"SiloGuid,omitempty"` + IsDummy bool `json:",omitempty"` + RuntimeID string `json:"RuntimeId,omitempty"` + IsRuntimeTemplate bool `json:",omitempty"` + RuntimeImagePath string `json:",omitempty"` + Stopped bool `json:",omitempty"` + ExitType string `json:",omitempty"` + AreUpdatesPending bool `json:",omitempty"` + ObRoot string `json:",omitempty"` + Statistics Statistics `json:",omitempty"` + ProcessList []ProcessListItem `json:",omitempty"` +} + +// MemoryStats holds the memory statistics for a container +type MemoryStats struct { + UsageCommitBytes uint64 `json:"MemoryUsageCommitBytes,omitempty"` + UsageCommitPeakBytes uint64 `json:"MemoryUsageCommitPeakBytes,omitempty"` + UsagePrivateWorkingSetBytes uint64 `json:"MemoryUsagePrivateWorkingSetBytes,omitempty"` +} + +// ProcessorStats holds the processor statistics for a container +type ProcessorStats struct { + TotalRuntime100ns uint64 `json:",omitempty"` + RuntimeUser100ns uint64 `json:",omitempty"` + RuntimeKernel100ns uint64 `json:",omitempty"` +} + +// StorageStats holds the storage statistics for a container +type StorageStats struct { + ReadCountNormalized uint64 `json:",omitempty"` + ReadSizeBytes uint64 `json:",omitempty"` + WriteCountNormalized uint64 `json:",omitempty"` + WriteSizeBytes uint64 `json:",omitempty"` +} + +// NetworkStats holds the network statistics for a container +type NetworkStats struct { + BytesReceived uint64 `json:",omitempty"` + BytesSent uint64 `json:",omitempty"` + PacketsReceived uint64 `json:",omitempty"` + PacketsSent uint64 `json:",omitempty"` + DroppedPacketsIncoming uint64 `json:",omitempty"` + DroppedPacketsOutgoing uint64 `json:",omitempty"` + EndpointId string `json:",omitempty"` + InstanceId string `json:",omitempty"` +} + +// Statistics is the structure returned by a statistics call on a container +type Statistics struct { + Timestamp time.Time `json:",omitempty"` + ContainerStartTime time.Time `json:",omitempty"` + Uptime100ns uint64 `json:",omitempty"` + Memory MemoryStats `json:",omitempty"` + Processor ProcessorStats `json:",omitempty"` + Storage StorageStats `json:",omitempty"` + Network []NetworkStats `json:",omitempty"` +} + +// ProcessList is the structure of an item returned by a ProcessList call on a container +type ProcessListItem struct { + CreateTimestamp time.Time `json:",omitempty"` + ImageName string `json:",omitempty"` + KernelTime100ns uint64 `json:",omitempty"` + MemoryCommitBytes uint64 `json:",omitempty"` + MemoryWorkingSetPrivateBytes uint64 `json:",omitempty"` + MemoryWorkingSetSharedBytes uint64 `json:",omitempty"` + ProcessId uint32 `json:",omitempty"` + UserTime100ns uint64 `json:",omitempty"` +} + +// Type of Request Support in ModifySystem +type RequestType string + +// Type of Resource Support in ModifySystem +type ResourceType string + +// RequestType const +const ( + Add RequestType = "Add" + Remove RequestType = "Remove" + Network ResourceType = "Network" +) + +// ResourceModificationRequestResponse is the structure used to send request to the container to modify the system +// Supported resource types are Network and Request Types are Add/Remove +type ResourceModificationRequestResponse struct { + Resource ResourceType `json:"ResourceType"` + Data string `json:"Settings"` + Request RequestType `json:"RequestType,omitempty"` +} + +// createContainerAdditionalJSON is read from the environment at initialisation +// time. It allows an environment variable to define additional JSON which +// is merged in the CreateContainer call to HCS. +var createContainerAdditionalJSON string + +func init() { + createContainerAdditionalJSON = os.Getenv("HCSSHIM_CREATECONTAINER_ADDITIONALJSON") +} + +// CreateContainer creates a new container with the given configuration but does not start it. +func CreateContainer(id string, c *ContainerConfig) (Container, error) { + return createContainerWithJSON(id, c, "") +} + +// CreateContainerWithJSON creates a new container with the given configuration but does not start it. +// It is identical to CreateContainer except that optional additional JSON can be merged before passing to HCS. +func CreateContainerWithJSON(id string, c *ContainerConfig, additionalJSON string) (Container, error) { + return createContainerWithJSON(id, c, additionalJSON) +} + +func createContainerWithJSON(id string, c *ContainerConfig, additionalJSON string) (Container, error) { + operation := "CreateContainer" + title := "HCSShim::" + operation + + container := &container{ + id: id, + } + + configurationb, err := json.Marshal(c) + if err != nil { + return nil, err + } + + configuration := string(configurationb) + logrus.Debugf(title+" id=%s config=%s", id, configuration) + + // Merge any additional JSON. Priority is given to what is passed in explicitly, + // falling back to what's set in the environment. + if additionalJSON == "" && createContainerAdditionalJSON != "" { + additionalJSON = createContainerAdditionalJSON + } + if additionalJSON != "" { + configurationMap := map[string]interface{}{} + if err := json.Unmarshal([]byte(configuration), &configurationMap); err != nil { + return nil, fmt.Errorf("failed to unmarshal %s: %s", configuration, err) + } + + additionalMap := map[string]interface{}{} + if err := json.Unmarshal([]byte(additionalJSON), &additionalMap); err != nil { + return nil, fmt.Errorf("failed to unmarshal %s: %s", additionalJSON, err) + } + + mergedMap := mergeMaps(additionalMap, configurationMap) + mergedJSON, err := json.Marshal(mergedMap) + if err != nil { + return nil, fmt.Errorf("failed to marshal merged configuration map %+v: %s", mergedMap, err) + } + + configuration = string(mergedJSON) + logrus.Debugf(title+" id=%s merged config=%s", id, configuration) + } + + var ( + resultp *uint16 + identity syscall.Handle + ) + createError := hcsCreateComputeSystem(id, configuration, identity, &container.handle, &resultp) + + if createError == nil || IsPending(createError) { + if err := container.registerCallback(); err != nil { + return nil, makeContainerError(container, operation, "", err) + } + } + + err = processAsyncHcsResult(createError, resultp, container.callbackNumber, hcsNotificationSystemCreateCompleted, &defaultTimeout) + if err != nil { + return nil, makeContainerError(container, operation, configuration, err) + } + + logrus.Debugf(title+" succeeded id=%s handle=%d", id, container.handle) + runtime.SetFinalizer(container, closeContainer) + return container, nil +} + +// mergeMaps recursively merges map `fromMap` into map `ToMap`. Any pre-existing values +// in ToMap are overwritten. Values in fromMap are added to ToMap. +// From http://stackoverflow.com/questions/40491438/merging-two-json-strings-in-golang +func mergeMaps(fromMap, ToMap interface{}) interface{} { + switch fromMap := fromMap.(type) { + case map[string]interface{}: + ToMap, ok := ToMap.(map[string]interface{}) + if !ok { + return fromMap + } + for keyToMap, valueToMap := range ToMap { + if valueFromMap, ok := fromMap[keyToMap]; ok { + fromMap[keyToMap] = mergeMaps(valueFromMap, valueToMap) + } else { + fromMap[keyToMap] = valueToMap + } + } + case nil: + // merge(nil, map[string]interface{...}) -> map[string]interface{...} + ToMap, ok := ToMap.(map[string]interface{}) + if ok { + return ToMap + } + } + return fromMap +} + +// OpenContainer opens an existing container by ID. +func OpenContainer(id string) (Container, error) { + operation := "OpenContainer" + title := "HCSShim::" + operation + logrus.Debugf(title+" id=%s", id) + + container := &container{ + id: id, + } + + var ( + handle hcsSystem + resultp *uint16 + ) + err := hcsOpenComputeSystem(id, &handle, &resultp) + err = processHcsResult(err, resultp) + if err != nil { + return nil, makeContainerError(container, operation, "", err) + } + + container.handle = handle + + if err := container.registerCallback(); err != nil { + return nil, makeContainerError(container, operation, "", err) + } + + logrus.Debugf(title+" succeeded id=%s handle=%d", id, handle) + runtime.SetFinalizer(container, closeContainer) + return container, nil +} + +// GetContainers gets a list of the containers on the system that match the query +func GetContainers(q ComputeSystemQuery) ([]ContainerProperties, error) { + operation := "GetContainers" + title := "HCSShim::" + operation + + queryb, err := json.Marshal(q) + if err != nil { + return nil, err + } + + query := string(queryb) + logrus.Debugf(title+" query=%s", query) + + var ( + resultp *uint16 + computeSystemsp *uint16 + ) + err = hcsEnumerateComputeSystems(query, &computeSystemsp, &resultp) + err = processHcsResult(err, resultp) + if err != nil { + return nil, err + } + + if computeSystemsp == nil { + return nil, ErrUnexpectedValue + } + computeSystemsRaw := convertAndFreeCoTaskMemBytes(computeSystemsp) + computeSystems := []ContainerProperties{} + if err := json.Unmarshal(computeSystemsRaw, &computeSystems); err != nil { + return nil, err + } + + logrus.Debugf(title + " succeeded") + return computeSystems, nil +} + +// Start synchronously starts the container. +func (container *container) Start() error { + container.handleLock.RLock() + defer container.handleLock.RUnlock() + operation := "Start" + title := "HCSShim::Container::" + operation + logrus.Debugf(title+" id=%s", container.id) + + if container.handle == 0 { + return makeContainerError(container, operation, "", ErrAlreadyClosed) + } + + var resultp *uint16 + err := hcsStartComputeSystem(container.handle, "", &resultp) + err = processAsyncHcsResult(err, resultp, container.callbackNumber, hcsNotificationSystemStartCompleted, &defaultTimeout) + if err != nil { + return makeContainerError(container, operation, "", err) + } + + logrus.Debugf(title+" succeeded id=%s", container.id) + return nil +} + +// Shutdown requests a container shutdown, if IsPending() on the error returned is true, +// it may not actually be shut down until Wait() succeeds. +func (container *container) Shutdown() error { + container.handleLock.RLock() + defer container.handleLock.RUnlock() + operation := "Shutdown" + title := "HCSShim::Container::" + operation + logrus.Debugf(title+" id=%s", container.id) + + if container.handle == 0 { + return makeContainerError(container, operation, "", ErrAlreadyClosed) + } + + var resultp *uint16 + err := hcsShutdownComputeSystem(container.handle, "", &resultp) + err = processHcsResult(err, resultp) + if err != nil { + return makeContainerError(container, operation, "", err) + } + + logrus.Debugf(title+" succeeded id=%s", container.id) + return nil +} + +// Terminate requests a container terminate, if IsPending() on the error returned is true, +// it may not actually be shut down until Wait() succeeds. +func (container *container) Terminate() error { + container.handleLock.RLock() + defer container.handleLock.RUnlock() + operation := "Terminate" + title := "HCSShim::Container::" + operation + logrus.Debugf(title+" id=%s", container.id) + + if container.handle == 0 { + return makeContainerError(container, operation, "", ErrAlreadyClosed) + } + + var resultp *uint16 + err := hcsTerminateComputeSystem(container.handle, "", &resultp) + err = processHcsResult(err, resultp) + if err != nil { + return makeContainerError(container, operation, "", err) + } + + logrus.Debugf(title+" succeeded id=%s", container.id) + return nil +} + +// Wait synchronously waits for the container to shutdown or terminate. +func (container *container) Wait() error { + operation := "Wait" + title := "HCSShim::Container::" + operation + logrus.Debugf(title+" id=%s", container.id) + + err := waitForNotification(container.callbackNumber, hcsNotificationSystemExited, nil) + if err != nil { + return makeContainerError(container, operation, "", err) + } + + logrus.Debugf(title+" succeeded id=%s", container.id) + return nil +} + +// WaitTimeout synchronously waits for the container to terminate or the duration to elapse. +// If the timeout expires, IsTimeout(err) == true +func (container *container) WaitTimeout(timeout time.Duration) error { + operation := "WaitTimeout" + title := "HCSShim::Container::" + operation + logrus.Debugf(title+" id=%s", container.id) + + err := waitForNotification(container.callbackNumber, hcsNotificationSystemExited, &timeout) + if err != nil { + return makeContainerError(container, operation, "", err) + } + + logrus.Debugf(title+" succeeded id=%s", container.id) + return nil +} + +func (container *container) properties(query string) (*ContainerProperties, error) { + var ( + resultp *uint16 + propertiesp *uint16 + ) + err := hcsGetComputeSystemProperties(container.handle, query, &propertiesp, &resultp) + err = processHcsResult(err, resultp) + if err != nil { + return nil, err + } + + if propertiesp == nil { + return nil, ErrUnexpectedValue + } + propertiesRaw := convertAndFreeCoTaskMemBytes(propertiesp) + properties := &ContainerProperties{} + if err := json.Unmarshal(propertiesRaw, properties); err != nil { + return nil, err + } + return properties, nil +} + +// HasPendingUpdates returns true if the container has updates pending to install +func (container *container) HasPendingUpdates() (bool, error) { + container.handleLock.RLock() + defer container.handleLock.RUnlock() + operation := "HasPendingUpdates" + title := "HCSShim::Container::" + operation + logrus.Debugf(title+" id=%s", container.id) + + if container.handle == 0 { + return false, makeContainerError(container, operation, "", ErrAlreadyClosed) + } + + properties, err := container.properties(pendingUpdatesQuery) + if err != nil { + return false, makeContainerError(container, operation, "", err) + } + + logrus.Debugf(title+" succeeded id=%s", container.id) + return properties.AreUpdatesPending, nil +} + +// Statistics returns statistics for the container +func (container *container) Statistics() (Statistics, error) { + container.handleLock.RLock() + defer container.handleLock.RUnlock() + operation := "Statistics" + title := "HCSShim::Container::" + operation + logrus.Debugf(title+" id=%s", container.id) + + if container.handle == 0 { + return Statistics{}, makeContainerError(container, operation, "", ErrAlreadyClosed) + } + + properties, err := container.properties(statisticsQuery) + if err != nil { + return Statistics{}, makeContainerError(container, operation, "", err) + } + + logrus.Debugf(title+" succeeded id=%s", container.id) + return properties.Statistics, nil +} + +// ProcessList returns an array of ProcessListItems for the container +func (container *container) ProcessList() ([]ProcessListItem, error) { + container.handleLock.RLock() + defer container.handleLock.RUnlock() + operation := "ProcessList" + title := "HCSShim::Container::" + operation + logrus.Debugf(title+" id=%s", container.id) + + if container.handle == 0 { + return nil, makeContainerError(container, operation, "", ErrAlreadyClosed) + } + + properties, err := container.properties(processListQuery) + if err != nil { + return nil, makeContainerError(container, operation, "", err) + } + + logrus.Debugf(title+" succeeded id=%s", container.id) + return properties.ProcessList, nil +} + +// Pause pauses the execution of the container. This feature is not enabled in TP5. +func (container *container) Pause() error { + container.handleLock.RLock() + defer container.handleLock.RUnlock() + operation := "Pause" + title := "HCSShim::Container::" + operation + logrus.Debugf(title+" id=%s", container.id) + + if container.handle == 0 { + return makeContainerError(container, operation, "", ErrAlreadyClosed) + } + + var resultp *uint16 + err := hcsPauseComputeSystem(container.handle, "", &resultp) + err = processAsyncHcsResult(err, resultp, container.callbackNumber, hcsNotificationSystemPauseCompleted, &defaultTimeout) + if err != nil { + return makeContainerError(container, operation, "", err) + } + + logrus.Debugf(title+" succeeded id=%s", container.id) + return nil +} + +// Resume resumes the execution of the container. This feature is not enabled in TP5. +func (container *container) Resume() error { + container.handleLock.RLock() + defer container.handleLock.RUnlock() + operation := "Resume" + title := "HCSShim::Container::" + operation + logrus.Debugf(title+" id=%s", container.id) + + if container.handle == 0 { + return makeContainerError(container, operation, "", ErrAlreadyClosed) + } + + var resultp *uint16 + err := hcsResumeComputeSystem(container.handle, "", &resultp) + err = processAsyncHcsResult(err, resultp, container.callbackNumber, hcsNotificationSystemResumeCompleted, &defaultTimeout) + if err != nil { + return makeContainerError(container, operation, "", err) + } + + logrus.Debugf(title+" succeeded id=%s", container.id) + return nil +} + +// CreateProcess launches a new process within the container. +func (container *container) CreateProcess(c *ProcessConfig) (Process, error) { + container.handleLock.RLock() + defer container.handleLock.RUnlock() + operation := "CreateProcess" + title := "HCSShim::Container::" + operation + var ( + processInfo hcsProcessInformation + processHandle hcsProcess + resultp *uint16 + ) + + if container.handle == 0 { + return nil, makeContainerError(container, operation, "", ErrAlreadyClosed) + } + + // If we are not emulating a console, ignore any console size passed to us + if !c.EmulateConsole { + c.ConsoleSize[0] = 0 + c.ConsoleSize[1] = 0 + } + + configurationb, err := json.Marshal(c) + if err != nil { + return nil, makeContainerError(container, operation, "", err) + } + + configuration := string(configurationb) + logrus.Debugf(title+" id=%s config=%s", container.id, configuration) + + err = hcsCreateProcess(container.handle, configuration, &processInfo, &processHandle, &resultp) + err = processHcsResult(err, resultp) + if err != nil { + return nil, makeContainerError(container, operation, configuration, err) + } + + process := &process{ + handle: processHandle, + processID: int(processInfo.ProcessId), + container: container, + cachedPipes: &cachedPipes{ + stdIn: processInfo.StdInput, + stdOut: processInfo.StdOutput, + stdErr: processInfo.StdError, + }, + } + + if err := process.registerCallback(); err != nil { + return nil, makeContainerError(container, operation, "", err) + } + + logrus.Debugf(title+" succeeded id=%s processid=%s", container.id, process.processID) + runtime.SetFinalizer(process, closeProcess) + return process, nil +} + +// OpenProcess gets an interface to an existing process within the container. +func (container *container) OpenProcess(pid int) (Process, error) { + container.handleLock.RLock() + defer container.handleLock.RUnlock() + operation := "OpenProcess" + title := "HCSShim::Container::" + operation + logrus.Debugf(title+" id=%s, processid=%d", container.id, pid) + var ( + processHandle hcsProcess + resultp *uint16 + ) + + if container.handle == 0 { + return nil, makeContainerError(container, operation, "", ErrAlreadyClosed) + } + + err := hcsOpenProcess(container.handle, uint32(pid), &processHandle, &resultp) + err = processHcsResult(err, resultp) + if err != nil { + return nil, makeContainerError(container, operation, "", err) + } + + process := &process{ + handle: processHandle, + processID: pid, + container: container, + } + + if err := process.registerCallback(); err != nil { + return nil, makeContainerError(container, operation, "", err) + } + + logrus.Debugf(title+" succeeded id=%s processid=%s", container.id, process.processID) + runtime.SetFinalizer(process, closeProcess) + return process, nil +} + +// Close cleans up any state associated with the container but does not terminate or wait for it. +func (container *container) Close() error { + container.handleLock.Lock() + defer container.handleLock.Unlock() + operation := "Close" + title := "HCSShim::Container::" + operation + logrus.Debugf(title+" id=%s", container.id) + + // Don't double free this + if container.handle == 0 { + return nil + } + + if err := container.unregisterCallback(); err != nil { + return makeContainerError(container, operation, "", err) + } + + if err := hcsCloseComputeSystem(container.handle); err != nil { + return makeContainerError(container, operation, "", err) + } + + container.handle = 0 + runtime.SetFinalizer(container, nil) + + logrus.Debugf(title+" succeeded id=%s", container.id) + return nil +} + +// closeContainer wraps container.Close for use by a finalizer +func closeContainer(container *container) { + container.Close() +} + +func (container *container) registerCallback() error { + context := ¬ifcationWatcherContext{ + channels: newChannels(), + } + + callbackMapLock.Lock() + callbackNumber := nextCallback + nextCallback++ + callbackMap[callbackNumber] = context + callbackMapLock.Unlock() + + var callbackHandle hcsCallback + err := hcsRegisterComputeSystemCallback(container.handle, notificationWatcherCallback, callbackNumber, &callbackHandle) + if err != nil { + return err + } + context.handle = callbackHandle + container.callbackNumber = callbackNumber + + return nil +} + +func (container *container) unregisterCallback() error { + callbackNumber := container.callbackNumber + + callbackMapLock.RLock() + context := callbackMap[callbackNumber] + callbackMapLock.RUnlock() + + if context == nil { + return nil + } + + handle := context.handle + + if handle == 0 { + return nil + } + + // hcsUnregisterComputeSystemCallback has its own syncronization + // to wait for all callbacks to complete. We must NOT hold the callbackMapLock. + err := hcsUnregisterComputeSystemCallback(handle) + if err != nil { + return err + } + + closeChannels(context.channels) + + callbackMapLock.Lock() + callbackMap[callbackNumber] = nil + callbackMapLock.Unlock() + + handle = 0 + + return nil +} + +// Modifies the System by sending a request to HCS +func (container *container) Modify(config *ResourceModificationRequestResponse) error { + container.handleLock.RLock() + defer container.handleLock.RUnlock() + operation := "Modify" + title := "HCSShim::Container::" + operation + + if container.handle == 0 { + return makeContainerError(container, operation, "", ErrAlreadyClosed) + } + + requestJSON, err := json.Marshal(config) + if err != nil { + return err + } + + requestString := string(requestJSON) + logrus.Debugf(title+" id=%s request=%s", container.id, requestString) + + var resultp *uint16 + err = hcsModifyComputeSystem(container.handle, requestString, &resultp) + err = processHcsResult(err, resultp) + if err != nil { + return makeContainerError(container, operation, "", err) + } + logrus.Debugf(title+" succeeded id=%s", container.id) + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/createlayer.go b/vendor/github.com/Microsoft/hcsshim/createlayer.go new file mode 100644 index 0000000..9ecffb1 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/createlayer.go @@ -0,0 +1,27 @@ +package hcsshim + +import "github.com/Sirupsen/logrus" + +// CreateLayer creates a new, empty, read-only layer on the filesystem based on +// the parent layer provided. +func CreateLayer(info DriverInfo, id, parent string) error { + title := "hcsshim::CreateLayer " + logrus.Debugf(title+"Flavour %d ID %s parent %s", info.Flavour, id, parent) + + // Convert info to API calling convention + infop, err := convertDriverInfo(info) + if err != nil { + logrus.Error(err) + return err + } + + err = createLayer(&infop, id, parent) + if err != nil { + err = makeErrorf(err, title, "id=%s parent=%s flavour=%d", id, parent, info.Flavour) + logrus.Error(err) + return err + } + + logrus.Debugf(title+" - succeeded id=%s parent=%s flavour=%d", id, parent, info.Flavour) + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/createsandboxlayer.go b/vendor/github.com/Microsoft/hcsshim/createsandboxlayer.go new file mode 100644 index 0000000..b69c3da --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/createsandboxlayer.go @@ -0,0 +1,35 @@ +package hcsshim + +import "github.com/Sirupsen/logrus" + +// CreateSandboxLayer creates and populates new read-write layer for use by a container. +// This requires both the id of the direct parent layer, as well as the full list +// of paths to all parent layers up to the base (and including the direct parent +// whose id was provided). +func CreateSandboxLayer(info DriverInfo, layerId, parentId string, parentLayerPaths []string) error { + title := "hcsshim::CreateSandboxLayer " + logrus.Debugf(title+"layerId %s parentId %s", layerId, parentId) + + // Generate layer descriptors + layers, err := layerPathsToDescriptors(parentLayerPaths) + if err != nil { + return err + } + + // Convert info to API calling convention + infop, err := convertDriverInfo(info) + if err != nil { + logrus.Error(err) + return err + } + + err = createSandboxLayer(&infop, layerId, parentId, layers) + if err != nil { + err = makeErrorf(err, title, "layerId=%s parentId=%s", layerId, parentId) + logrus.Error(err) + return err + } + + logrus.Debugf(title+"- succeeded layerId=%s parentId=%s", layerId, parentId) + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/deactivatelayer.go b/vendor/github.com/Microsoft/hcsshim/deactivatelayer.go new file mode 100644 index 0000000..c02bcb3 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/deactivatelayer.go @@ -0,0 +1,26 @@ +package hcsshim + +import "github.com/Sirupsen/logrus" + +// DeactivateLayer will dismount a layer that was mounted via ActivateLayer. +func DeactivateLayer(info DriverInfo, id string) error { + title := "hcsshim::DeactivateLayer " + logrus.Debugf(title+"Flavour %d ID %s", info.Flavour, id) + + // Convert info to API calling convention + infop, err := convertDriverInfo(info) + if err != nil { + logrus.Error(err) + return err + } + + err = deactivateLayer(&infop, id) + if err != nil { + err = makeErrorf(err, title, "id=%s flavour=%d", id, info.Flavour) + logrus.Error(err) + return err + } + + logrus.Debugf(title+"succeeded flavour=%d id=%s", info.Flavour, id) + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/destroylayer.go b/vendor/github.com/Microsoft/hcsshim/destroylayer.go new file mode 100644 index 0000000..91ed269 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/destroylayer.go @@ -0,0 +1,27 @@ +package hcsshim + +import "github.com/Sirupsen/logrus" + +// DestroyLayer will remove the on-disk files representing the layer with the given +// id, including that layer's containing folder, if any. +func DestroyLayer(info DriverInfo, id string) error { + title := "hcsshim::DestroyLayer " + logrus.Debugf(title+"Flavour %d ID %s", info.Flavour, id) + + // Convert info to API calling convention + infop, err := convertDriverInfo(info) + if err != nil { + logrus.Error(err) + return err + } + + err = destroyLayer(&infop, id) + if err != nil { + err = makeErrorf(err, title, "id=%s flavour=%d", id, info.Flavour) + logrus.Error(err) + return err + } + + logrus.Debugf(title+"succeeded flavour=%d id=%s", info.Flavour, id) + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/errors.go b/vendor/github.com/Microsoft/hcsshim/errors.go new file mode 100644 index 0000000..d2f9cc8 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/errors.go @@ -0,0 +1,239 @@ +package hcsshim + +import ( + "errors" + "fmt" + "syscall" +) + +var ( + // ErrComputeSystemDoesNotExist is an error encountered when the container being operated on no longer exists + ErrComputeSystemDoesNotExist = syscall.Errno(0xc037010e) + + // ErrElementNotFound is an error encountered when the object being referenced does not exist + ErrElementNotFound = syscall.Errno(0x490) + + // ErrElementNotFound is an error encountered when the object being referenced does not exist + ErrNotSupported = syscall.Errno(0x32) + + // ErrInvalidData is an error encountered when the request being sent to hcs is invalid/unsupported + // decimal -2147024883 / hex 0x8007000d + ErrInvalidData = syscall.Errno(0xd) + + // ErrHandleClose is an error encountered when the handle generating the notification being waited on has been closed + ErrHandleClose = errors.New("hcsshim: the handle generating this notification has been closed") + + // ErrAlreadyClosed is an error encountered when using a handle that has been closed by the Close method + ErrAlreadyClosed = errors.New("hcsshim: the handle has already been closed") + + // ErrInvalidNotificationType is an error encountered when an invalid notification type is used + ErrInvalidNotificationType = errors.New("hcsshim: invalid notification type") + + // ErrInvalidProcessState is an error encountered when the process is not in a valid state for the requested operation + ErrInvalidProcessState = errors.New("the process is in an invalid state for the attempted operation") + + // ErrTimeout is an error encountered when waiting on a notification times out + ErrTimeout = errors.New("hcsshim: timeout waiting for notification") + + // ErrUnexpectedContainerExit is the error encountered when a container exits while waiting for + // a different expected notification + ErrUnexpectedContainerExit = errors.New("unexpected container exit") + + // ErrUnexpectedProcessAbort is the error encountered when communication with the compute service + // is lost while waiting for a notification + ErrUnexpectedProcessAbort = errors.New("lost communication with compute service") + + // ErrUnexpectedValue is an error encountered when hcs returns an invalid value + ErrUnexpectedValue = errors.New("unexpected value returned from hcs") + + // ErrVmcomputeAlreadyStopped is an error encountered when a shutdown or terminate request is made on a stopped container + ErrVmcomputeAlreadyStopped = syscall.Errno(0xc0370110) + + // ErrVmcomputeOperationPending is an error encountered when the operation is being completed asynchronously + ErrVmcomputeOperationPending = syscall.Errno(0xC0370103) + + // ErrVmcomputeOperationInvalidState is an error encountered when the compute system is not in a valid state for the requested operation + ErrVmcomputeOperationInvalidState = syscall.Errno(0xc0370105) + + // ErrProcNotFound is an error encountered when the the process cannot be found + ErrProcNotFound = syscall.Errno(0x7f) + + // ErrVmcomputeOperationAccessIsDenied is an error which can be encountered when enumerating compute systems in RS1/RS2 + // builds when the underlying silo might be in the process of terminating. HCS was fixed in RS3. + ErrVmcomputeOperationAccessIsDenied = syscall.Errno(0x5) + + // ErrVmcomputeInvalidJSON is an error encountered when the compute system does not support/understand the messages sent by management + ErrVmcomputeInvalidJSON = syscall.Errno(0xc037010d) + + // ErrVmcomputeUnknownMessage is an error encountered guest compute system doesn't support the message + ErrVmcomputeUnknownMessage = syscall.Errno(0xc037010b) + + // ErrNotSupported is an error encountered when hcs doesn't support the request + ErrPlatformNotSupported = errors.New("unsupported platform request") +) + +// ProcessError is an error encountered in HCS during an operation on a Process object +type ProcessError struct { + Process *process + Operation string + ExtraInfo string + Err error +} + +// ContainerError is an error encountered in HCS during an operation on a Container object +type ContainerError struct { + Container *container + Operation string + ExtraInfo string + Err error +} + +func (e *ContainerError) Error() string { + if e == nil { + return "" + } + + if e.Container == nil { + return "unexpected nil container for error: " + e.Err.Error() + } + + s := "container " + e.Container.id + + if e.Operation != "" { + s += " encountered an error during " + e.Operation + } + + switch e.Err.(type) { + case nil: + break + case syscall.Errno: + s += fmt.Sprintf(": failure in a Windows system call: %s (0x%x)", e.Err, win32FromError(e.Err)) + default: + s += fmt.Sprintf(": %s", e.Err.Error()) + } + + if e.ExtraInfo != "" { + s += " extra info: " + e.ExtraInfo + } + + return s +} + +func makeContainerError(container *container, operation string, extraInfo string, err error) error { + // Don't double wrap errors + if _, ok := err.(*ContainerError); ok { + return err + } + containerError := &ContainerError{Container: container, Operation: operation, ExtraInfo: extraInfo, Err: err} + return containerError +} + +func (e *ProcessError) Error() string { + if e == nil { + return "" + } + + if e.Process == nil { + return "Unexpected nil process for error: " + e.Err.Error() + } + + s := fmt.Sprintf("process %d", e.Process.processID) + + if e.Process.container != nil { + s += " in container " + e.Process.container.id + } + + if e.Operation != "" { + s += " encountered an error during " + e.Operation + } + + switch e.Err.(type) { + case nil: + break + case syscall.Errno: + s += fmt.Sprintf(": failure in a Windows system call: %s (0x%x)", e.Err, win32FromError(e.Err)) + default: + s += fmt.Sprintf(": %s", e.Err.Error()) + } + + return s +} + +func makeProcessError(process *process, operation string, extraInfo string, err error) error { + // Don't double wrap errors + if _, ok := err.(*ProcessError); ok { + return err + } + processError := &ProcessError{Process: process, Operation: operation, ExtraInfo: extraInfo, Err: err} + return processError +} + +// IsNotExist checks if an error is caused by the Container or Process not existing. +// Note: Currently, ErrElementNotFound can mean that a Process has either +// already exited, or does not exist. Both IsAlreadyStopped and IsNotExist +// will currently return true when the error is ErrElementNotFound or ErrProcNotFound. +func IsNotExist(err error) bool { + err = getInnerError(err) + return err == ErrComputeSystemDoesNotExist || + err == ErrElementNotFound || + err == ErrProcNotFound +} + +// IsAlreadyClosed checks if an error is caused by the Container or Process having been +// already closed by a call to the Close() method. +func IsAlreadyClosed(err error) bool { + err = getInnerError(err) + return err == ErrAlreadyClosed +} + +// IsPending returns a boolean indicating whether the error is that +// the requested operation is being completed in the background. +func IsPending(err error) bool { + err = getInnerError(err) + return err == ErrVmcomputeOperationPending +} + +// IsTimeout returns a boolean indicating whether the error is caused by +// a timeout waiting for the operation to complete. +func IsTimeout(err error) bool { + err = getInnerError(err) + return err == ErrTimeout +} + +// IsAlreadyStopped returns a boolean indicating whether the error is caused by +// a Container or Process being already stopped. +// Note: Currently, ErrElementNotFound can mean that a Process has either +// already exited, or does not exist. Both IsAlreadyStopped and IsNotExist +// will currently return true when the error is ErrElementNotFound or ErrProcNotFound. +func IsAlreadyStopped(err error) bool { + err = getInnerError(err) + return err == ErrVmcomputeAlreadyStopped || + err == ErrElementNotFound || + err == ErrProcNotFound +} + +// IsNotSupported returns a boolean indicating whether the error is caused by +// unsupported platform requests +// Note: Currently Unsupported platform requests can be mean either +// ErrVmcomputeInvalidJSON, ErrInvalidData, ErrNotSupported or ErrVmcomputeUnknownMessage +// is thrown from the Platform +func IsNotSupported(err error) bool { + err = getInnerError(err) + // If Platform doesn't recognize or support the request sent, below errors are seen + return err == ErrVmcomputeInvalidJSON || + err == ErrInvalidData || + err == ErrNotSupported || + err == ErrVmcomputeUnknownMessage +} + +func getInnerError(err error) error { + switch pe := err.(type) { + case nil: + return nil + case *ContainerError: + err = pe.Err + case *ProcessError: + err = pe.Err + } + return err +} diff --git a/vendor/github.com/Microsoft/hcsshim/expandsandboxsize.go b/vendor/github.com/Microsoft/hcsshim/expandsandboxsize.go new file mode 100644 index 0000000..e168921 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/expandsandboxsize.go @@ -0,0 +1,26 @@ +package hcsshim + +import "github.com/Sirupsen/logrus" + +// ExpandSandboxSize expands the size of a layer to at least size bytes. +func ExpandSandboxSize(info DriverInfo, layerId string, size uint64) error { + title := "hcsshim::ExpandSandboxSize " + logrus.Debugf(title+"layerId=%s size=%d", layerId, size) + + // Convert info to API calling convention + infop, err := convertDriverInfo(info) + if err != nil { + logrus.Error(err) + return err + } + + err = expandSandboxSize(&infop, layerId, size) + if err != nil { + err = makeErrorf(err, title, "layerId=%s size=%d", layerId, size) + logrus.Error(err) + return err + } + + logrus.Debugf(title+"- succeeded layerId=%s size=%d", layerId, size) + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/exportlayer.go b/vendor/github.com/Microsoft/hcsshim/exportlayer.go new file mode 100644 index 0000000..903e085 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/exportlayer.go @@ -0,0 +1,158 @@ +package hcsshim + +import ( + "io" + "io/ioutil" + "os" + "runtime" + "syscall" + + "github.com/Microsoft/go-winio" + "github.com/Sirupsen/logrus" +) + +// ExportLayer will create a folder at exportFolderPath and fill that folder with +// the transport format version of the layer identified by layerId. This transport +// format includes any metadata required for later importing the layer (using +// ImportLayer), and requires the full list of parent layer paths in order to +// perform the export. +func ExportLayer(info DriverInfo, layerId string, exportFolderPath string, parentLayerPaths []string) error { + title := "hcsshim::ExportLayer " + logrus.Debugf(title+"flavour %d layerId %s folder %s", info.Flavour, layerId, exportFolderPath) + + // Generate layer descriptors + layers, err := layerPathsToDescriptors(parentLayerPaths) + if err != nil { + return err + } + + // Convert info to API calling convention + infop, err := convertDriverInfo(info) + if err != nil { + logrus.Error(err) + return err + } + + err = exportLayer(&infop, layerId, exportFolderPath, layers) + if err != nil { + err = makeErrorf(err, title, "layerId=%s flavour=%d folder=%s", layerId, info.Flavour, exportFolderPath) + logrus.Error(err) + return err + } + + logrus.Debugf(title+"succeeded flavour=%d layerId=%s folder=%s", info.Flavour, layerId, exportFolderPath) + return nil +} + +type LayerReader interface { + Next() (string, int64, *winio.FileBasicInfo, error) + Read(b []byte) (int, error) + Close() error +} + +// FilterLayerReader provides an interface for extracting the contents of an on-disk layer. +type FilterLayerReader struct { + context uintptr +} + +// Next reads the next available file from a layer, ensuring that parent directories are always read +// before child files and directories. +// +// Next returns the file's relative path, size, and basic file metadata. Read() should be used to +// extract a Win32 backup stream with the remainder of the metadata and the data. +func (r *FilterLayerReader) Next() (string, int64, *winio.FileBasicInfo, error) { + var fileNamep *uint16 + fileInfo := &winio.FileBasicInfo{} + var deleted uint32 + var fileSize int64 + err := exportLayerNext(r.context, &fileNamep, fileInfo, &fileSize, &deleted) + if err != nil { + if err == syscall.ERROR_NO_MORE_FILES { + err = io.EOF + } else { + err = makeError(err, "ExportLayerNext", "") + } + return "", 0, nil, err + } + fileName := convertAndFreeCoTaskMemString(fileNamep) + if deleted != 0 { + fileInfo = nil + } + if fileName[0] == '\\' { + fileName = fileName[1:] + } + return fileName, fileSize, fileInfo, nil +} + +// Read reads from the current file's Win32 backup stream. +func (r *FilterLayerReader) Read(b []byte) (int, error) { + var bytesRead uint32 + err := exportLayerRead(r.context, b, &bytesRead) + if err != nil { + return 0, makeError(err, "ExportLayerRead", "") + } + if bytesRead == 0 { + return 0, io.EOF + } + return int(bytesRead), nil +} + +// Close frees resources associated with the layer reader. It will return an +// error if there was an error while reading the layer or of the layer was not +// completely read. +func (r *FilterLayerReader) Close() (err error) { + if r.context != 0 { + err = exportLayerEnd(r.context) + if err != nil { + err = makeError(err, "ExportLayerEnd", "") + } + r.context = 0 + } + return +} + +// NewLayerReader returns a new layer reader for reading the contents of an on-disk layer. +// The caller must have taken the SeBackupPrivilege privilege +// to call this and any methods on the resulting LayerReader. +func NewLayerReader(info DriverInfo, layerID string, parentLayerPaths []string) (LayerReader, error) { + if procExportLayerBegin.Find() != nil { + // The new layer reader is not available on this Windows build. Fall back to the + // legacy export code path. + path, err := ioutil.TempDir("", "hcs") + if err != nil { + return nil, err + } + err = ExportLayer(info, layerID, path, parentLayerPaths) + if err != nil { + os.RemoveAll(path) + return nil, err + } + return &legacyLayerReaderWrapper{newLegacyLayerReader(path)}, nil + } + + layers, err := layerPathsToDescriptors(parentLayerPaths) + if err != nil { + return nil, err + } + infop, err := convertDriverInfo(info) + if err != nil { + return nil, err + } + r := &FilterLayerReader{} + err = exportLayerBegin(&infop, layerID, layers, &r.context) + if err != nil { + return nil, makeError(err, "ExportLayerBegin", "") + } + runtime.SetFinalizer(r, func(r *FilterLayerReader) { r.Close() }) + return r, err +} + +type legacyLayerReaderWrapper struct { + *legacyLayerReader +} + +func (r *legacyLayerReaderWrapper) Close() error { + err := r.legacyLayerReader.Close() + os.RemoveAll(r.root) + return err +} diff --git a/vendor/github.com/Microsoft/hcsshim/getlayermountpath.go b/vendor/github.com/Microsoft/hcsshim/getlayermountpath.go new file mode 100644 index 0000000..41b5758 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/getlayermountpath.go @@ -0,0 +1,55 @@ +package hcsshim + +import ( + "syscall" + + "github.com/Sirupsen/logrus" +) + +// GetLayerMountPath will look for a mounted layer with the given id and return +// the path at which that layer can be accessed. This path may be a volume path +// if the layer is a mounted read-write layer, otherwise it is expected to be the +// folder path at which the layer is stored. +func GetLayerMountPath(info DriverInfo, id string) (string, error) { + title := "hcsshim::GetLayerMountPath " + logrus.Debugf(title+"Flavour %d ID %s", info.Flavour, id) + + // Convert info to API calling convention + infop, err := convertDriverInfo(info) + if err != nil { + logrus.Error(err) + return "", err + } + + var mountPathLength uintptr + mountPathLength = 0 + + // Call the procedure itself. + logrus.Debugf("Calling proc (1)") + err = getLayerMountPath(&infop, id, &mountPathLength, nil) + if err != nil { + err = makeErrorf(err, title, "(first call) id=%s flavour=%d", id, info.Flavour) + logrus.Error(err) + return "", err + } + + // Allocate a mount path of the returned length. + if mountPathLength == 0 { + return "", nil + } + mountPathp := make([]uint16, mountPathLength) + mountPathp[0] = 0 + + // Call the procedure again + logrus.Debugf("Calling proc (2)") + err = getLayerMountPath(&infop, id, &mountPathLength, &mountPathp[0]) + if err != nil { + err = makeErrorf(err, title, "(second call) id=%s flavour=%d", id, info.Flavour) + logrus.Error(err) + return "", err + } + + path := syscall.UTF16ToString(mountPathp[0:]) + logrus.Debugf(title+"succeeded flavour=%d id=%s path=%s", info.Flavour, id, path) + return path, nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/getsharedbaseimages.go b/vendor/github.com/Microsoft/hcsshim/getsharedbaseimages.go new file mode 100644 index 0000000..01ab4da --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/getsharedbaseimages.go @@ -0,0 +1,22 @@ +package hcsshim + +import "github.com/Sirupsen/logrus" + +// GetSharedBaseImages will enumerate the images stored in the common central +// image store and return descriptive info about those images for the purpose +// of registering them with the graphdriver, graph, and tagstore. +func GetSharedBaseImages() (imageData string, err error) { + title := "hcsshim::GetSharedBaseImages " + + logrus.Debugf("Calling proc") + var buffer *uint16 + err = getBaseImages(&buffer) + if err != nil { + err = makeError(err, title, "") + logrus.Error(err) + return + } + imageData = convertAndFreeCoTaskMemString(buffer) + logrus.Debugf(title+" - succeeded output=%s", imageData) + return +} diff --git a/vendor/github.com/Microsoft/hcsshim/guid.go b/vendor/github.com/Microsoft/hcsshim/guid.go new file mode 100644 index 0000000..620aba1 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/guid.go @@ -0,0 +1,19 @@ +package hcsshim + +import ( + "crypto/sha1" + "fmt" +) + +type GUID [16]byte + +func NewGUID(source string) *GUID { + h := sha1.Sum([]byte(source)) + var g GUID + copy(g[0:], h[0:16]) + return &g +} + +func (g *GUID) ToString() string { + return fmt.Sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x-%02x", g[3], g[2], g[1], g[0], g[5], g[4], g[7], g[6], g[8:10], g[10:]) +} diff --git a/vendor/github.com/Microsoft/hcsshim/hcsshim.go b/vendor/github.com/Microsoft/hcsshim/hcsshim.go new file mode 100644 index 0000000..3cb3a29 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/hcsshim.go @@ -0,0 +1,166 @@ +// Shim for the Host Compute Service (HCS) to manage Windows Server +// containers and Hyper-V containers. + +package hcsshim + +import ( + "fmt" + "syscall" + "unsafe" + + "github.com/Sirupsen/logrus" +) + +//go:generate go run mksyscall_windows.go -output zhcsshim.go hcsshim.go + +//sys coTaskMemFree(buffer unsafe.Pointer) = ole32.CoTaskMemFree +//sys SetCurrentThreadCompartmentId(compartmentId uint32) (hr error) = iphlpapi.SetCurrentThreadCompartmentId + +//sys activateLayer(info *driverInfo, id string) (hr error) = vmcompute.ActivateLayer? +//sys copyLayer(info *driverInfo, srcId string, dstId string, descriptors []WC_LAYER_DESCRIPTOR) (hr error) = vmcompute.CopyLayer? +//sys createLayer(info *driverInfo, id string, parent string) (hr error) = vmcompute.CreateLayer? +//sys createSandboxLayer(info *driverInfo, id string, parent string, descriptors []WC_LAYER_DESCRIPTOR) (hr error) = vmcompute.CreateSandboxLayer? +//sys expandSandboxSize(info *driverInfo, id string, size uint64) (hr error) = vmcompute.ExpandSandboxSize? +//sys deactivateLayer(info *driverInfo, id string) (hr error) = vmcompute.DeactivateLayer? +//sys destroyLayer(info *driverInfo, id string) (hr error) = vmcompute.DestroyLayer? +//sys exportLayer(info *driverInfo, id string, path string, descriptors []WC_LAYER_DESCRIPTOR) (hr error) = vmcompute.ExportLayer? +//sys getLayerMountPath(info *driverInfo, id string, length *uintptr, buffer *uint16) (hr error) = vmcompute.GetLayerMountPath? +//sys getBaseImages(buffer **uint16) (hr error) = vmcompute.GetBaseImages? +//sys importLayer(info *driverInfo, id string, path string, descriptors []WC_LAYER_DESCRIPTOR) (hr error) = vmcompute.ImportLayer? +//sys layerExists(info *driverInfo, id string, exists *uint32) (hr error) = vmcompute.LayerExists? +//sys nameToGuid(name string, guid *GUID) (hr error) = vmcompute.NameToGuid? +//sys prepareLayer(info *driverInfo, id string, descriptors []WC_LAYER_DESCRIPTOR) (hr error) = vmcompute.PrepareLayer? +//sys unprepareLayer(info *driverInfo, id string) (hr error) = vmcompute.UnprepareLayer? +//sys processBaseImage(path string) (hr error) = vmcompute.ProcessBaseImage? +//sys processUtilityImage(path string) (hr error) = vmcompute.ProcessUtilityImage? + +//sys importLayerBegin(info *driverInfo, id string, descriptors []WC_LAYER_DESCRIPTOR, context *uintptr) (hr error) = vmcompute.ImportLayerBegin? +//sys importLayerNext(context uintptr, fileName string, fileInfo *winio.FileBasicInfo) (hr error) = vmcompute.ImportLayerNext? +//sys importLayerWrite(context uintptr, buffer []byte) (hr error) = vmcompute.ImportLayerWrite? +//sys importLayerEnd(context uintptr) (hr error) = vmcompute.ImportLayerEnd? + +//sys exportLayerBegin(info *driverInfo, id string, descriptors []WC_LAYER_DESCRIPTOR, context *uintptr) (hr error) = vmcompute.ExportLayerBegin? +//sys exportLayerNext(context uintptr, fileName **uint16, fileInfo *winio.FileBasicInfo, fileSize *int64, deleted *uint32) (hr error) = vmcompute.ExportLayerNext? +//sys exportLayerRead(context uintptr, buffer []byte, bytesRead *uint32) (hr error) = vmcompute.ExportLayerRead? +//sys exportLayerEnd(context uintptr) (hr error) = vmcompute.ExportLayerEnd? + +//sys hcsEnumerateComputeSystems(query string, computeSystems **uint16, result **uint16) (hr error) = vmcompute.HcsEnumerateComputeSystems? +//sys hcsCreateComputeSystem(id string, configuration string, identity syscall.Handle, computeSystem *hcsSystem, result **uint16) (hr error) = vmcompute.HcsCreateComputeSystem? +//sys hcsOpenComputeSystem(id string, computeSystem *hcsSystem, result **uint16) (hr error) = vmcompute.HcsOpenComputeSystem? +//sys hcsCloseComputeSystem(computeSystem hcsSystem) (hr error) = vmcompute.HcsCloseComputeSystem? +//sys hcsStartComputeSystem(computeSystem hcsSystem, options string, result **uint16) (hr error) = vmcompute.HcsStartComputeSystem? +//sys hcsShutdownComputeSystem(computeSystem hcsSystem, options string, result **uint16) (hr error) = vmcompute.HcsShutdownComputeSystem? +//sys hcsTerminateComputeSystem(computeSystem hcsSystem, options string, result **uint16) (hr error) = vmcompute.HcsTerminateComputeSystem? +//sys hcsPauseComputeSystem(computeSystem hcsSystem, options string, result **uint16) (hr error) = vmcompute.HcsPauseComputeSystem? +//sys hcsResumeComputeSystem(computeSystem hcsSystem, options string, result **uint16) (hr error) = vmcompute.HcsResumeComputeSystem? +//sys hcsGetComputeSystemProperties(computeSystem hcsSystem, propertyQuery string, properties **uint16, result **uint16) (hr error) = vmcompute.HcsGetComputeSystemProperties? +//sys hcsModifyComputeSystem(computeSystem hcsSystem, configuration string, result **uint16) (hr error) = vmcompute.HcsModifyComputeSystem? +//sys hcsRegisterComputeSystemCallback(computeSystem hcsSystem, callback uintptr, context uintptr, callbackHandle *hcsCallback) (hr error) = vmcompute.HcsRegisterComputeSystemCallback? +//sys hcsUnregisterComputeSystemCallback(callbackHandle hcsCallback) (hr error) = vmcompute.HcsUnregisterComputeSystemCallback? + +//sys hcsCreateProcess(computeSystem hcsSystem, processParameters string, processInformation *hcsProcessInformation, process *hcsProcess, result **uint16) (hr error) = vmcompute.HcsCreateProcess? +//sys hcsOpenProcess(computeSystem hcsSystem, pid uint32, process *hcsProcess, result **uint16) (hr error) = vmcompute.HcsOpenProcess? +//sys hcsCloseProcess(process hcsProcess) (hr error) = vmcompute.HcsCloseProcess? +//sys hcsTerminateProcess(process hcsProcess, result **uint16) (hr error) = vmcompute.HcsTerminateProcess? +//sys hcsGetProcessInfo(process hcsProcess, processInformation *hcsProcessInformation, result **uint16) (hr error) = vmcompute.HcsGetProcessInfo? +//sys hcsGetProcessProperties(process hcsProcess, processProperties **uint16, result **uint16) (hr error) = vmcompute.HcsGetProcessProperties? +//sys hcsModifyProcess(process hcsProcess, settings string, result **uint16) (hr error) = vmcompute.HcsModifyProcess? +//sys hcsGetServiceProperties(propertyQuery string, properties **uint16, result **uint16) (hr error) = vmcompute.HcsGetServiceProperties? +//sys hcsRegisterProcessCallback(process hcsProcess, callback uintptr, context uintptr, callbackHandle *hcsCallback) (hr error) = vmcompute.HcsRegisterProcessCallback? +//sys hcsUnregisterProcessCallback(callbackHandle hcsCallback) (hr error) = vmcompute.HcsUnregisterProcessCallback? + +//sys hcsModifyServiceSettings(settings string, result **uint16) (hr error) = vmcompute.HcsModifyServiceSettings? + +//sys _hnsCall(method string, path string, object string, response **uint16) (hr error) = vmcompute.HNSCall? + +const ( + // Specific user-visible exit codes + WaitErrExecFailed = 32767 + + ERROR_GEN_FAILURE = syscall.Errno(31) + ERROR_SHUTDOWN_IN_PROGRESS = syscall.Errno(1115) + WSAEINVAL = syscall.Errno(10022) + + // Timeout on wait calls + TimeoutInfinite = 0xFFFFFFFF +) + +type HcsError struct { + title string + rest string + Err error +} + +type hcsSystem syscall.Handle +type hcsProcess syscall.Handle +type hcsCallback syscall.Handle + +type hcsProcessInformation struct { + ProcessId uint32 + Reserved uint32 + StdInput syscall.Handle + StdOutput syscall.Handle + StdError syscall.Handle +} + +func makeError(err error, title, rest string) error { + // Pass through DLL errors directly since they do not originate from HCS. + if _, ok := err.(*syscall.DLLError); ok { + return err + } + return &HcsError{title, rest, err} +} + +func makeErrorf(err error, title, format string, a ...interface{}) error { + return makeError(err, title, fmt.Sprintf(format, a...)) +} + +func win32FromError(err error) uint32 { + if herr, ok := err.(*HcsError); ok { + return win32FromError(herr.Err) + } + if code, ok := err.(syscall.Errno); ok { + return uint32(code) + } + return uint32(ERROR_GEN_FAILURE) +} + +func win32FromHresult(hr uintptr) uintptr { + if hr&0x1fff0000 == 0x00070000 { + return hr & 0xffff + } + return hr +} + +func (e *HcsError) Error() string { + s := e.title + if len(s) > 0 && s[len(s)-1] != ' ' { + s += " " + } + s += fmt.Sprintf("failed in Win32: %s (0x%x)", e.Err, win32FromError(e.Err)) + if e.rest != "" { + if e.rest[0] != ' ' { + s += " " + } + s += e.rest + } + return s +} + +func convertAndFreeCoTaskMemString(buffer *uint16) string { + str := syscall.UTF16ToString((*[1 << 30]uint16)(unsafe.Pointer(buffer))[:]) + coTaskMemFree(unsafe.Pointer(buffer)) + return str +} + +func convertAndFreeCoTaskMemBytes(buffer *uint16) []byte { + return []byte(convertAndFreeCoTaskMemString(buffer)) +} + +func processHcsResult(err error, resultp *uint16) error { + if resultp != nil { + result := convertAndFreeCoTaskMemString(resultp) + logrus.Debugf("Result: %s", result) + } + return err +} diff --git a/vendor/github.com/Microsoft/hcsshim/hnsfuncs.go b/vendor/github.com/Microsoft/hcsshim/hnsfuncs.go new file mode 100644 index 0000000..97ec2e8 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/hnsfuncs.go @@ -0,0 +1,271 @@ +package hcsshim + +import ( + "encoding/json" + "fmt" + "net" + + "github.com/Sirupsen/logrus" +) + +type NatPolicy struct { + Type string + Protocol string + InternalPort uint16 + ExternalPort uint16 +} + +type QosPolicy struct { + Type string + MaximumOutgoingBandwidthInBytes uint64 +} + +type VlanPolicy struct { + Type string + VLAN uint +} + +type VsidPolicy struct { + Type string + VSID uint +} + +type PaPolicy struct { + Type string + PA string +} + +// Subnet is assoicated with a network and represents a list +// of subnets available to the network +type Subnet struct { + AddressPrefix string `json:",omitempty"` + GatewayAddress string `json:",omitempty"` + Policies []json.RawMessage `json:",omitempty"` +} + +// MacPool is assoicated with a network and represents a list +// of macaddresses available to the network +type MacPool struct { + StartMacAddress string `json:",omitempty"` + EndMacAddress string `json:",omitempty"` +} + +// HNSNetwork represents a network in HNS +type HNSNetwork struct { + Id string `json:"ID,omitempty"` + Name string `json:",omitempty"` + Type string `json:",omitempty"` + NetworkAdapterName string `json:",omitempty"` + SourceMac string `json:",omitempty"` + Policies []json.RawMessage `json:",omitempty"` + MacPools []MacPool `json:",omitempty"` + Subnets []Subnet `json:",omitempty"` + DNSSuffix string `json:",omitempty"` + DNSServerList string `json:",omitempty"` + DNSServerCompartment uint32 `json:",omitempty"` + ManagementIP string `json:",omitempty"` +} + +// HNSEndpoint represents a network endpoint in HNS +type HNSEndpoint struct { + Id string `json:"ID,omitempty"` + Name string `json:",omitempty"` + VirtualNetwork string `json:",omitempty"` + VirtualNetworkName string `json:",omitempty"` + Policies []json.RawMessage `json:",omitempty"` + MacAddress string `json:",omitempty"` + IPAddress net.IP `json:",omitempty"` + DNSSuffix string `json:",omitempty"` + DNSServerList string `json:",omitempty"` + GatewayAddress string `json:",omitempty"` + EnableInternalDNS bool `json:",omitempty"` + DisableICC bool `json:",omitempty"` + PrefixLength uint8 `json:",omitempty"` + IsRemoteEndpoint bool `json:",omitempty"` +} + +type hnsNetworkResponse struct { + Success bool + Error string + Output HNSNetwork +} + +type hnsResponse struct { + Success bool + Error string + Output json.RawMessage +} + +func hnsCall(method, path, request string, returnResponse interface{}) error { + var responseBuffer *uint16 + logrus.Debugf("[%s]=>[%s] Request : %s", method, path, request) + + err := _hnsCall(method, path, request, &responseBuffer) + if err != nil { + return makeError(err, "hnsCall ", "") + } + response := convertAndFreeCoTaskMemString(responseBuffer) + + hnsresponse := &hnsResponse{} + if err = json.Unmarshal([]byte(response), &hnsresponse); err != nil { + return err + } + + if !hnsresponse.Success { + return fmt.Errorf("HNS failed with error : %s", hnsresponse.Error) + } + + if len(hnsresponse.Output) == 0 { + return nil + } + + logrus.Debugf("Network Response : %s", hnsresponse.Output) + err = json.Unmarshal(hnsresponse.Output, returnResponse) + if err != nil { + return err + } + + return nil +} + +// HNSNetworkRequest makes a call into HNS to update/query a single network +func HNSNetworkRequest(method, path, request string) (*HNSNetwork, error) { + var network HNSNetwork + err := hnsCall(method, "/networks/"+path, request, &network) + if err != nil { + return nil, err + } + + return &network, nil +} + +// HNSListNetworkRequest makes a HNS call to query the list of available networks +func HNSListNetworkRequest(method, path, request string) ([]HNSNetwork, error) { + var network []HNSNetwork + err := hnsCall(method, "/networks/"+path, request, &network) + if err != nil { + return nil, err + } + + return network, nil +} + +// HNSEndpointRequest makes a HNS call to modify/query a network endpoint +func HNSEndpointRequest(method, path, request string) (*HNSEndpoint, error) { + endpoint := &HNSEndpoint{} + err := hnsCall(method, "/endpoints/"+path, request, &endpoint) + if err != nil { + return nil, err + } + + return endpoint, nil +} + +// HNSListEndpointRequest makes a HNS call to query the list of available endpoints +func HNSListEndpointRequest() ([]HNSEndpoint, error) { + var endpoint []HNSEndpoint + err := hnsCall("GET", "/endpoints/", "", &endpoint) + if err != nil { + return nil, err + } + + return endpoint, nil +} + +// HotAttachEndpoint makes a HCS Call to attach the endpoint to the container +func HotAttachEndpoint(containerID string, endpointID string) error { + return modifyNetworkEndpoint(containerID, endpointID, Add) +} + +// HotDetachEndpoint makes a HCS Call to detach the endpoint from the container +func HotDetachEndpoint(containerID string, endpointID string) error { + return modifyNetworkEndpoint(containerID, endpointID, Remove) +} + +// ModifyContainer corresponding to the container id, by sending a request +func modifyContainer(id string, request *ResourceModificationRequestResponse) error { + container, err := OpenContainer(id) + if err != nil { + if IsNotExist(err) { + return ErrComputeSystemDoesNotExist + } + return getInnerError(err) + } + defer container.Close() + err = container.Modify(request) + if err != nil { + if IsNotSupported(err) { + return ErrPlatformNotSupported + } + return getInnerError(err) + } + + return nil +} + +func modifyNetworkEndpoint(containerID string, endpointID string, request RequestType) error { + requestMessage := &ResourceModificationRequestResponse{ + Resource: Network, + Request: request, + Data: endpointID, + } + err := modifyContainer(containerID, requestMessage) + + if err != nil { + return err + } + + return nil +} + +// GetHNSNetworkByID +func GetHNSNetworkByID(networkID string) (*HNSNetwork, error) { + return HNSNetworkRequest("GET", networkID, "") +} + +// GetHNSNetworkName filtered by Name +func GetHNSNetworkByName(networkName string) (*HNSNetwork, error) { + hsnnetworks, err := HNSListNetworkRequest("GET", "", "") + if err != nil { + return nil, err + } + for _, hnsnetwork := range hsnnetworks { + if hnsnetwork.Name == networkName { + return &hnsnetwork, nil + } + } + return nil, fmt.Errorf("Network %v not found", networkName) +} + +// Create Endpoint by sending EndpointRequest to HNS. TODO: Create a separate HNS interface to place all these methods +func (endpoint *HNSEndpoint) Create() (*HNSEndpoint, error) { + jsonString, err := json.Marshal(endpoint) + if err != nil { + return nil, err + } + return HNSEndpointRequest("POST", "", string(jsonString)) +} + +// Create Endpoint by sending EndpointRequest to HNS +func (endpoint *HNSEndpoint) Delete() (*HNSEndpoint, error) { + return HNSEndpointRequest("DELETE", endpoint.Id, "") +} + +// GetHNSEndpointByID +func GetHNSEndpointByID(endpointID string) (*HNSEndpoint, error) { + return HNSEndpointRequest("GET", endpointID, "") +} + +// GetHNSNetworkName filtered by Name +func GetHNSEndpointByName(endpointName string) (*HNSEndpoint, error) { + hnsResponse, err := HNSListEndpointRequest() + if err != nil { + return nil, err + } + for _, hnsEndpoint := range hnsResponse { + if hnsEndpoint.Name == endpointName { + return &hnsEndpoint, nil + } + } + return nil, fmt.Errorf("Endpoint %v not found", endpointName) +} diff --git a/vendor/github.com/Microsoft/hcsshim/importlayer.go b/vendor/github.com/Microsoft/hcsshim/importlayer.go new file mode 100644 index 0000000..5f82630 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/importlayer.go @@ -0,0 +1,214 @@ +package hcsshim + +import ( + "errors" + "io/ioutil" + "os" + "path/filepath" + "runtime" + + "github.com/Microsoft/go-winio" + "github.com/Sirupsen/logrus" +) + +// ImportLayer will take the contents of the folder at importFolderPath and import +// that into a layer with the id layerId. Note that in order to correctly populate +// the layer and interperet the transport format, all parent layers must already +// be present on the system at the paths provided in parentLayerPaths. +func ImportLayer(info DriverInfo, layerID string, importFolderPath string, parentLayerPaths []string) error { + title := "hcsshim::ImportLayer " + logrus.Debugf(title+"flavour %d layerId %s folder %s", info.Flavour, layerID, importFolderPath) + + // Generate layer descriptors + layers, err := layerPathsToDescriptors(parentLayerPaths) + if err != nil { + return err + } + + // Convert info to API calling convention + infop, err := convertDriverInfo(info) + if err != nil { + logrus.Error(err) + return err + } + + err = importLayer(&infop, layerID, importFolderPath, layers) + if err != nil { + err = makeErrorf(err, title, "layerId=%s flavour=%d folder=%s", layerID, info.Flavour, importFolderPath) + logrus.Error(err) + return err + } + + logrus.Debugf(title+"succeeded flavour=%d layerId=%s folder=%s", info.Flavour, layerID, importFolderPath) + return nil +} + +// LayerWriter is an interface that supports writing a new container image layer. +type LayerWriter interface { + // Add adds a file to the layer with given metadata. + Add(name string, fileInfo *winio.FileBasicInfo) error + // AddLink adds a hard link to the layer. The target must already have been added. + AddLink(name string, target string) error + // Remove removes a file that was present in a parent layer from the layer. + Remove(name string) error + // Write writes data to the current file. The data must be in the format of a Win32 + // backup stream. + Write(b []byte) (int, error) + // Close finishes the layer writing process and releases any resources. + Close() error +} + +// FilterLayerWriter provides an interface to write the contents of a layer to the file system. +type FilterLayerWriter struct { + context uintptr +} + +// Add adds a file or directory to the layer. The file's parent directory must have already been added. +// +// name contains the file's relative path. fileInfo contains file times and file attributes; the rest +// of the file metadata and the file data must be written as a Win32 backup stream to the Write() method. +// winio.BackupStreamWriter can be used to facilitate this. +func (w *FilterLayerWriter) Add(name string, fileInfo *winio.FileBasicInfo) error { + if name[0] != '\\' { + name = `\` + name + } + err := importLayerNext(w.context, name, fileInfo) + if err != nil { + return makeError(err, "ImportLayerNext", "") + } + return nil +} + +// AddLink adds a hard link to the layer. The target of the link must have already been added. +func (w *FilterLayerWriter) AddLink(name string, target string) error { + return errors.New("hard links not yet supported") +} + +// Remove removes a file from the layer. The file must have been present in the parent layer. +// +// name contains the file's relative path. +func (w *FilterLayerWriter) Remove(name string) error { + if name[0] != '\\' { + name = `\` + name + } + err := importLayerNext(w.context, name, nil) + if err != nil { + return makeError(err, "ImportLayerNext", "") + } + return nil +} + +// Write writes more backup stream data to the current file. +func (w *FilterLayerWriter) Write(b []byte) (int, error) { + err := importLayerWrite(w.context, b) + if err != nil { + err = makeError(err, "ImportLayerWrite", "") + return 0, err + } + return len(b), err +} + +// Close completes the layer write operation. The error must be checked to ensure that the +// operation was successful. +func (w *FilterLayerWriter) Close() (err error) { + if w.context != 0 { + err = importLayerEnd(w.context) + if err != nil { + err = makeError(err, "ImportLayerEnd", "") + } + w.context = 0 + } + return +} + +type legacyLayerWriterWrapper struct { + *legacyLayerWriter + info DriverInfo + layerID string + path string + parentLayerPaths []string +} + +func (r *legacyLayerWriterWrapper) Close() error { + defer os.RemoveAll(r.root) + err := r.legacyLayerWriter.Close() + if err != nil { + return err + } + + // Use the original path here because ImportLayer does not support long paths for the source in TP5. + // But do use a long path for the destination to work around another bug with directories + // with MAX_PATH - 12 < length < MAX_PATH. + info := r.info + fullPath, err := makeLongAbsPath(filepath.Join(info.HomeDir, r.layerID)) + if err != nil { + return err + } + + info.HomeDir = "" + if err = ImportLayer(info, fullPath, r.path, r.parentLayerPaths); err != nil { + return err + } + // Add any hard links that were collected. + for _, lnk := range r.PendingLinks { + if err = os.Remove(lnk.Path); err != nil && !os.IsNotExist(err) { + return err + } + if err = os.Link(lnk.Target, lnk.Path); err != nil { + return err + } + } + // Prepare the utility VM for use if one is present in the layer. + if r.HasUtilityVM { + err = ProcessUtilityVMImage(filepath.Join(fullPath, "UtilityVM")) + if err != nil { + return err + } + } + return nil +} + +// NewLayerWriter returns a new layer writer for creating a layer on disk. +// The caller must have taken the SeBackupPrivilege and SeRestorePrivilege privileges +// to call this and any methods on the resulting LayerWriter. +func NewLayerWriter(info DriverInfo, layerID string, parentLayerPaths []string) (LayerWriter, error) { + if len(parentLayerPaths) == 0 { + // This is a base layer. It gets imported differently. + return &baseLayerWriter{ + root: filepath.Join(info.HomeDir, layerID), + }, nil + } + + if procImportLayerBegin.Find() != nil { + // The new layer reader is not available on this Windows build. Fall back to the + // legacy export code path. + path, err := ioutil.TempDir("", "hcs") + if err != nil { + return nil, err + } + return &legacyLayerWriterWrapper{ + legacyLayerWriter: newLegacyLayerWriter(path, parentLayerPaths, filepath.Join(info.HomeDir, layerID)), + info: info, + layerID: layerID, + path: path, + parentLayerPaths: parentLayerPaths, + }, nil + } + layers, err := layerPathsToDescriptors(parentLayerPaths) + if err != nil { + return nil, err + } + + infop, err := convertDriverInfo(info) + if err != nil { + return nil, err + } + + w := &FilterLayerWriter{} + err = importLayerBegin(&infop, layerID, layers, &w.context) + if err != nil { + return nil, makeError(err, "ImportLayerStart", "") + } + runtime.SetFinalizer(w, func(w *FilterLayerWriter) { w.Close() }) + return w, nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/interface.go b/vendor/github.com/Microsoft/hcsshim/interface.go new file mode 100644 index 0000000..d981c4f --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/interface.go @@ -0,0 +1,159 @@ +package hcsshim + +import ( + "io" + "time" +) + +// ProcessConfig is used as both the input of Container.CreateProcess +// and to convert the parameters to JSON for passing onto the HCS +type ProcessConfig struct { + ApplicationName string + CommandLine string + User string + WorkingDirectory string + Environment map[string]string + EmulateConsole bool + CreateStdInPipe bool + CreateStdOutPipe bool + CreateStdErrPipe bool + ConsoleSize [2]uint +} + +type Layer struct { + ID string + Path string +} + +type MappedDir struct { + HostPath string + ContainerPath string + ReadOnly bool + BandwidthMaximum uint64 + IOPSMaximum uint64 +} + +type HvRuntime struct { + ImagePath string `json:",omitempty"` + SkipTemplate bool `json:",omitempty"` +} + +// ContainerConfig is used as both the input of CreateContainer +// and to convert the parameters to JSON for passing onto the HCS +type ContainerConfig struct { + SystemType string // HCS requires this to be hard-coded to "Container" + Name string // Name of the container. We use the docker ID. + Owner string // The management platform that created this container + IsDummy bool // Used for development purposes. + VolumePath string `json:",omitempty"` // Windows volume path for scratch space. Used by Windows Server Containers only. Format \\?\\Volume{GUID} + IgnoreFlushesDuringBoot bool // Optimization hint for container startup in Windows + LayerFolderPath string `json:",omitempty"` // Where the layer folders are located. Used by Windows Server Containers only. Format %root%\windowsfilter\containerID + Layers []Layer // List of storage layers. Required for Windows Server and Hyper-V Containers. Format ID=GUID;Path=%root%\windowsfilter\layerID + Credentials string `json:",omitempty"` // Credentials information + ProcessorCount uint32 `json:",omitempty"` // Number of processors to assign to the container. + ProcessorWeight uint64 `json:",omitempty"` // CPU Shares 0..10000 on Windows; where 0 will be omitted and HCS will default. + ProcessorMaximum int64 `json:",omitempty"` // CPU maximum usage percent 1..100 + StorageIOPSMaximum uint64 `json:",omitempty"` // Maximum Storage IOPS + StorageBandwidthMaximum uint64 `json:",omitempty"` // Maximum Storage Bandwidth in bytes per second + StorageSandboxSize uint64 `json:",omitempty"` // Size in bytes that the container system drive should be expanded to if smaller + MemoryMaximumInMB int64 `json:",omitempty"` // Maximum memory available to the container in Megabytes + HostName string // Hostname + MappedDirectories []MappedDir // List of mapped directories (volumes/mounts) + SandboxPath string `json:",omitempty"` // Location of unmounted sandbox. Used by Hyper-V containers only. Format %root%\windowsfilter + HvPartition bool // True if it a Hyper-V Container + EndpointList []string // List of networking endpoints to be attached to container + NetworkSharedContainerName string `json:",omitempty"` // Name (ID) of the container that we will share the network stack with. + HvRuntime *HvRuntime `json:",omitempty"` // Hyper-V container settings. Used by Hyper-V containers only. Format ImagePath=%root%\BaseLayerID\UtilityVM + Servicing bool // True if this container is for servicing + AllowUnqualifiedDNSQuery bool // True to allow unqualified DNS name resolution + DNSSearchList string `json:",omitempty"` // Comma seperated list of DNS suffixes to use for name resolution +} + +type ComputeSystemQuery struct { + IDs []string `json:"Ids,omitempty"` + Types []string `json:",omitempty"` + Names []string `json:",omitempty"` + Owners []string `json:",omitempty"` +} + +// Container represents a created (but not necessarily running) container. +type Container interface { + // Start synchronously starts the container. + Start() error + + // Shutdown requests a container shutdown, but it may not actually be shutdown until Wait() succeeds. + Shutdown() error + + // Terminate requests a container terminate, but it may not actually be terminated until Wait() succeeds. + Terminate() error + + // Waits synchronously waits for the container to shutdown or terminate. + Wait() error + + // WaitTimeout synchronously waits for the container to terminate or the duration to elapse. It + // returns false if timeout occurs. + WaitTimeout(time.Duration) error + + // Pause pauses the execution of a container. + Pause() error + + // Resume resumes the execution of a container. + Resume() error + + // HasPendingUpdates returns true if the container has updates pending to install. + HasPendingUpdates() (bool, error) + + // Statistics returns statistics for a container. + Statistics() (Statistics, error) + + // ProcessList returns details for the processes in a container. + ProcessList() ([]ProcessListItem, error) + + // CreateProcess launches a new process within the container. + CreateProcess(c *ProcessConfig) (Process, error) + + // OpenProcess gets an interface to an existing process within the container. + OpenProcess(pid int) (Process, error) + + // Close cleans up any state associated with the container but does not terminate or wait for it. + Close() error + + // Modify the System + Modify(config *ResourceModificationRequestResponse) error +} + +// Process represents a running or exited process. +type Process interface { + // Pid returns the process ID of the process within the container. + Pid() int + + // Kill signals the process to terminate but does not wait for it to finish terminating. + Kill() error + + // Wait waits for the process to exit. + Wait() error + + // WaitTimeout waits for the process to exit or the duration to elapse. It returns + // false if timeout occurs. + WaitTimeout(time.Duration) error + + // ExitCode returns the exit code of the process. The process must have + // already terminated. + ExitCode() (int, error) + + // ResizeConsole resizes the console of the process. + ResizeConsole(width, height uint16) error + + // Stdio returns the stdin, stdout, and stderr pipes, respectively. Closing + // these pipes does not close the underlying pipes; it should be possible to + // call this multiple times to get multiple interfaces. + Stdio() (io.WriteCloser, io.ReadCloser, io.ReadCloser, error) + + // CloseStdin closes the write side of the stdin pipe so that the process is + // notified on the read side that there is no more data in stdin. + CloseStdin() error + + // Close cleans up any state associated with the process but does not kill + // or wait on it. + Close() error +} diff --git a/vendor/github.com/Microsoft/hcsshim/layerexists.go b/vendor/github.com/Microsoft/hcsshim/layerexists.go new file mode 100644 index 0000000..522d95c --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/layerexists.go @@ -0,0 +1,30 @@ +package hcsshim + +import "github.com/Sirupsen/logrus" + +// LayerExists will return true if a layer with the given id exists and is known +// to the system. +func LayerExists(info DriverInfo, id string) (bool, error) { + title := "hcsshim::LayerExists " + logrus.Debugf(title+"Flavour %d ID %s", info.Flavour, id) + + // Convert info to API calling convention + infop, err := convertDriverInfo(info) + if err != nil { + logrus.Error(err) + return false, err + } + + // Call the procedure itself. + var exists uint32 + + err = layerExists(&infop, id, &exists) + if err != nil { + err = makeErrorf(err, title, "id=%s flavour=%d", id, info.Flavour) + logrus.Error(err) + return false, err + } + + logrus.Debugf(title+"succeeded flavour=%d id=%s exists=%d", info.Flavour, id, exists) + return exists != 0, nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/layerutils.go b/vendor/github.com/Microsoft/hcsshim/layerutils.go new file mode 100644 index 0000000..47229d2 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/layerutils.go @@ -0,0 +1,111 @@ +package hcsshim + +// This file contains utility functions to support storage (graph) related +// functionality. + +import ( + "path/filepath" + "syscall" + + "github.com/Sirupsen/logrus" +) + +/* To pass into syscall, we need a struct matching the following: +enum GraphDriverType +{ + DiffDriver, + FilterDriver +}; + +struct DriverInfo { + GraphDriverType Flavour; + LPCWSTR HomeDir; +}; +*/ +type DriverInfo struct { + Flavour int + HomeDir string +} + +type driverInfo struct { + Flavour int + HomeDirp *uint16 +} + +func convertDriverInfo(info DriverInfo) (driverInfo, error) { + homedirp, err := syscall.UTF16PtrFromString(info.HomeDir) + if err != nil { + logrus.Debugf("Failed conversion of home to pointer for driver info: %s", err.Error()) + return driverInfo{}, err + } + + return driverInfo{ + Flavour: info.Flavour, + HomeDirp: homedirp, + }, nil +} + +/* To pass into syscall, we need a struct matching the following: +typedef struct _WC_LAYER_DESCRIPTOR { + + // + // The ID of the layer + // + + GUID LayerId; + + // + // Additional flags + // + + union { + struct { + ULONG Reserved : 31; + ULONG Dirty : 1; // Created from sandbox as a result of snapshot + }; + ULONG Value; + } Flags; + + // + // Path to the layer root directory, null-terminated + // + + PCWSTR Path; + +} WC_LAYER_DESCRIPTOR, *PWC_LAYER_DESCRIPTOR; +*/ +type WC_LAYER_DESCRIPTOR struct { + LayerId GUID + Flags uint32 + Pathp *uint16 +} + +func layerPathsToDescriptors(parentLayerPaths []string) ([]WC_LAYER_DESCRIPTOR, error) { + // Array of descriptors that gets constructed. + var layers []WC_LAYER_DESCRIPTOR + + for i := 0; i < len(parentLayerPaths); i++ { + // Create a layer descriptor, using the folder name + // as the source for a GUID LayerId + _, folderName := filepath.Split(parentLayerPaths[i]) + g, err := NameToGuid(folderName) + if err != nil { + logrus.Debugf("Failed to convert name to guid %s", err) + return nil, err + } + + p, err := syscall.UTF16PtrFromString(parentLayerPaths[i]) + if err != nil { + logrus.Debugf("Failed conversion of parentLayerPath to pointer %s", err) + return nil, err + } + + layers = append(layers, WC_LAYER_DESCRIPTOR{ + LayerId: g, + Flags: 0, + Pathp: p, + }) + } + + return layers, nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/legacy.go b/vendor/github.com/Microsoft/hcsshim/legacy.go new file mode 100644 index 0000000..11d90a7 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/legacy.go @@ -0,0 +1,723 @@ +package hcsshim + +import ( + "bufio" + "encoding/binary" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "syscall" + + "github.com/Microsoft/go-winio" +) + +var errorIterationCanceled = errors.New("") + +var mutatedUtilityVMFiles = map[string]bool{ + `EFI\Microsoft\Boot\BCD`: true, + `EFI\Microsoft\Boot\BCD.LOG`: true, + `EFI\Microsoft\Boot\BCD.LOG1`: true, + `EFI\Microsoft\Boot\BCD.LOG2`: true, +} + +func openFileOrDir(path string, mode uint32, createDisposition uint32) (file *os.File, err error) { + return winio.OpenForBackup(path, mode, syscall.FILE_SHARE_READ, createDisposition) +} + +func makeLongAbsPath(path string) (string, error) { + if strings.HasPrefix(path, `\\?\`) || strings.HasPrefix(path, `\\.\`) { + return path, nil + } + if !filepath.IsAbs(path) { + absPath, err := filepath.Abs(path) + if err != nil { + return "", err + } + path = absPath + } + if strings.HasPrefix(path, `\\`) { + return `\\?\UNC\` + path[2:], nil + } + return `\\?\` + path, nil +} + +type fileEntry struct { + path string + fi os.FileInfo + err error +} + +type legacyLayerReader struct { + root string + result chan *fileEntry + proceed chan bool + currentFile *os.File + backupReader *winio.BackupFileReader +} + +// newLegacyLayerReader returns a new LayerReader that can read the Windows +// container layer transport format from disk. +func newLegacyLayerReader(root string) *legacyLayerReader { + r := &legacyLayerReader{ + root: root, + result: make(chan *fileEntry), + proceed: make(chan bool), + } + go r.walk() + return r +} + +func readTombstones(path string) (map[string]([]string), error) { + tf, err := os.Open(filepath.Join(path, "tombstones.txt")) + if err != nil { + return nil, err + } + defer tf.Close() + s := bufio.NewScanner(tf) + if !s.Scan() || s.Text() != "\xef\xbb\xbfVersion 1.0" { + return nil, errors.New("Invalid tombstones file") + } + + ts := make(map[string]([]string)) + for s.Scan() { + t := filepath.Join("Files", s.Text()[1:]) // skip leading `\` + dir := filepath.Dir(t) + ts[dir] = append(ts[dir], t) + } + if err = s.Err(); err != nil { + return nil, err + } + + return ts, nil +} + +func (r *legacyLayerReader) walkUntilCancelled() error { + root, err := makeLongAbsPath(r.root) + if err != nil { + return err + } + + r.root = root + ts, err := readTombstones(r.root) + if err != nil { + return err + } + + err = filepath.Walk(r.root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if path == r.root || path == filepath.Join(r.root, "tombstones.txt") || strings.HasSuffix(path, ".$wcidirs$") { + return nil + } + + r.result <- &fileEntry{path, info, nil} + if !<-r.proceed { + return errorIterationCanceled + } + + // List all the tombstones. + if info.IsDir() { + relPath, err := filepath.Rel(r.root, path) + if err != nil { + return err + } + if dts, ok := ts[relPath]; ok { + for _, t := range dts { + r.result <- &fileEntry{filepath.Join(r.root, t), nil, nil} + if !<-r.proceed { + return errorIterationCanceled + } + } + } + } + return nil + }) + if err == errorIterationCanceled { + return nil + } + if err == nil { + return io.EOF + } + return err +} + +func (r *legacyLayerReader) walk() { + defer close(r.result) + if !<-r.proceed { + return + } + + err := r.walkUntilCancelled() + if err != nil { + for { + r.result <- &fileEntry{err: err} + if !<-r.proceed { + return + } + } + } +} + +func (r *legacyLayerReader) reset() { + if r.backupReader != nil { + r.backupReader.Close() + r.backupReader = nil + } + if r.currentFile != nil { + r.currentFile.Close() + r.currentFile = nil + } +} + +func findBackupStreamSize(r io.Reader) (int64, error) { + br := winio.NewBackupStreamReader(r) + for { + hdr, err := br.Next() + if err != nil { + if err == io.EOF { + err = nil + } + return 0, err + } + if hdr.Id == winio.BackupData { + return hdr.Size, nil + } + } +} + +func (r *legacyLayerReader) Next() (path string, size int64, fileInfo *winio.FileBasicInfo, err error) { + r.reset() + r.proceed <- true + fe := <-r.result + if fe == nil { + err = errors.New("LegacyLayerReader closed") + return + } + if fe.err != nil { + err = fe.err + return + } + + path, err = filepath.Rel(r.root, fe.path) + if err != nil { + return + } + + if fe.fi == nil { + // This is a tombstone. Return a nil fileInfo. + return + } + + if fe.fi.IsDir() && strings.HasPrefix(path, `Files\`) { + fe.path += ".$wcidirs$" + } + + f, err := openFileOrDir(fe.path, syscall.GENERIC_READ, syscall.OPEN_EXISTING) + if err != nil { + return + } + defer func() { + if f != nil { + f.Close() + } + }() + + fileInfo, err = winio.GetFileBasicInfo(f) + if err != nil { + return + } + + if !strings.HasPrefix(path, `Files\`) { + size = fe.fi.Size() + r.backupReader = winio.NewBackupFileReader(f, false) + if path == "Hives" || path == "Files" { + // The Hives directory has a non-deterministic file time because of the + // nature of the import process. Use the times from System_Delta. + var g *os.File + g, err = os.Open(filepath.Join(r.root, `Hives\System_Delta`)) + if err != nil { + return + } + attr := fileInfo.FileAttributes + fileInfo, err = winio.GetFileBasicInfo(g) + g.Close() + if err != nil { + return + } + fileInfo.FileAttributes = attr + } + + // The creation time and access time get reset for files outside of the Files path. + fileInfo.CreationTime = fileInfo.LastWriteTime + fileInfo.LastAccessTime = fileInfo.LastWriteTime + + } else { + // The file attributes are written before the backup stream. + var attr uint32 + err = binary.Read(f, binary.LittleEndian, &attr) + if err != nil { + return + } + fileInfo.FileAttributes = uintptr(attr) + beginning := int64(4) + + // Find the accurate file size. + if !fe.fi.IsDir() { + size, err = findBackupStreamSize(f) + if err != nil { + err = &os.PathError{Op: "findBackupStreamSize", Path: fe.path, Err: err} + return + } + } + + // Return back to the beginning of the backup stream. + _, err = f.Seek(beginning, 0) + if err != nil { + return + } + } + + r.currentFile = f + f = nil + return +} + +func (r *legacyLayerReader) Read(b []byte) (int, error) { + if r.backupReader == nil { + if r.currentFile == nil { + return 0, io.EOF + } + return r.currentFile.Read(b) + } + return r.backupReader.Read(b) +} + +func (r *legacyLayerReader) Close() error { + r.proceed <- false + <-r.result + r.reset() + return nil +} + +type pendingLink struct { + Path, Target string +} + +type legacyLayerWriter struct { + root string + parentRoots []string + destRoot string + currentFile *os.File + backupWriter *winio.BackupFileWriter + tombstones []string + pathFixed bool + HasUtilityVM bool + uvmDi []dirInfo + addedFiles map[string]bool + PendingLinks []pendingLink +} + +// newLegacyLayerWriter returns a LayerWriter that can write the contaler layer +// transport format to disk. +func newLegacyLayerWriter(root string, parentRoots []string, destRoot string) *legacyLayerWriter { + return &legacyLayerWriter{ + root: root, + parentRoots: parentRoots, + destRoot: destRoot, + addedFiles: make(map[string]bool), + } +} + +func (w *legacyLayerWriter) init() error { + if !w.pathFixed { + path, err := makeLongAbsPath(w.root) + if err != nil { + return err + } + for i, p := range w.parentRoots { + w.parentRoots[i], err = makeLongAbsPath(p) + if err != nil { + return err + } + } + destPath, err := makeLongAbsPath(w.destRoot) + if err != nil { + return err + } + w.root = path + w.destRoot = destPath + w.pathFixed = true + } + return nil +} + +func (w *legacyLayerWriter) initUtilityVM() error { + if !w.HasUtilityVM { + err := os.Mkdir(filepath.Join(w.destRoot, `UtilityVM`), 0) + if err != nil { + return err + } + // Server 2016 does not support multiple layers for the utility VM, so + // clone the utility VM from the parent layer into this layer. Use hard + // links to avoid unnecessary copying, since most of the files are + // immutable. + err = cloneTree(filepath.Join(w.parentRoots[0], `UtilityVM\Files`), filepath.Join(w.destRoot, `UtilityVM\Files`), mutatedUtilityVMFiles) + if err != nil { + return fmt.Errorf("cloning the parent utility VM image failed: %s", err) + } + w.HasUtilityVM = true + } + return nil +} + +func (w *legacyLayerWriter) reset() { + if w.backupWriter != nil { + w.backupWriter.Close() + w.backupWriter = nil + } + if w.currentFile != nil { + w.currentFile.Close() + w.currentFile = nil + } +} + +// copyFileWithMetadata copies a file using the backup/restore APIs in order to preserve metadata +func copyFileWithMetadata(srcPath, destPath string, isDir bool) (fileInfo *winio.FileBasicInfo, err error) { + createDisposition := uint32(syscall.CREATE_NEW) + if isDir { + err = os.Mkdir(destPath, 0) + if err != nil { + return nil, err + } + createDisposition = syscall.OPEN_EXISTING + } + + src, err := openFileOrDir(srcPath, syscall.GENERIC_READ|winio.ACCESS_SYSTEM_SECURITY, syscall.OPEN_EXISTING) + if err != nil { + return nil, err + } + defer src.Close() + srcr := winio.NewBackupFileReader(src, true) + defer srcr.Close() + + fileInfo, err = winio.GetFileBasicInfo(src) + if err != nil { + return nil, err + } + + dest, err := openFileOrDir(destPath, syscall.GENERIC_READ|syscall.GENERIC_WRITE|winio.WRITE_DAC|winio.WRITE_OWNER|winio.ACCESS_SYSTEM_SECURITY, createDisposition) + if err != nil { + return nil, err + } + defer dest.Close() + + err = winio.SetFileBasicInfo(dest, fileInfo) + if err != nil { + return nil, err + } + + destw := winio.NewBackupFileWriter(dest, true) + defer func() { + cerr := destw.Close() + if err == nil { + err = cerr + } + }() + + _, err = io.Copy(destw, srcr) + if err != nil { + return nil, err + } + + return fileInfo, nil +} + +// cloneTree clones a directory tree using hard links. It skips hard links for +// the file names in the provided map and just copies those files. +func cloneTree(srcPath, destPath string, mutatedFiles map[string]bool) error { + var di []dirInfo + err := filepath.Walk(srcPath, func(srcFilePath string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + relPath, err := filepath.Rel(srcPath, srcFilePath) + if err != nil { + return err + } + destFilePath := filepath.Join(destPath, relPath) + + // Directories, reparse points, and files that will be mutated during + // utility VM import must be copied. All other files can be hard linked. + isReparsePoint := info.Sys().(*syscall.Win32FileAttributeData).FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 + if info.IsDir() || isReparsePoint || mutatedFiles[relPath] { + fi, err := copyFileWithMetadata(srcFilePath, destFilePath, info.IsDir()) + if err != nil { + return err + } + if info.IsDir() && !isReparsePoint { + di = append(di, dirInfo{path: destFilePath, fileInfo: *fi}) + } + } else { + err = os.Link(srcFilePath, destFilePath) + if err != nil { + return err + } + } + + // Don't recurse on reparse points. + if info.IsDir() && isReparsePoint { + return filepath.SkipDir + } + + return nil + }) + if err != nil { + return err + } + + return reapplyDirectoryTimes(di) +} + +func (w *legacyLayerWriter) Add(name string, fileInfo *winio.FileBasicInfo) error { + w.reset() + err := w.init() + if err != nil { + return err + } + + if name == `UtilityVM` { + return w.initUtilityVM() + } + + if strings.HasPrefix(name, `UtilityVM\`) { + if !w.HasUtilityVM { + return errors.New("missing UtilityVM directory") + } + if !strings.HasPrefix(name, `UtilityVM\Files\`) && name != `UtilityVM\Files` { + return errors.New("invalid UtilityVM layer") + } + path := filepath.Join(w.destRoot, name) + createDisposition := uint32(syscall.OPEN_EXISTING) + if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 { + st, err := os.Lstat(path) + if err != nil && !os.IsNotExist(err) { + return err + } + if st != nil { + // Delete the existing file/directory if it is not the same type as this directory. + existingAttr := st.Sys().(*syscall.Win32FileAttributeData).FileAttributes + if (uint32(fileInfo.FileAttributes)^existingAttr)&(syscall.FILE_ATTRIBUTE_DIRECTORY|syscall.FILE_ATTRIBUTE_REPARSE_POINT) != 0 { + if err = os.RemoveAll(path); err != nil { + return err + } + st = nil + } + } + if st == nil { + if err = os.Mkdir(path, 0); err != nil { + return err + } + } + if fileInfo.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 { + w.uvmDi = append(w.uvmDi, dirInfo{path: path, fileInfo: *fileInfo}) + } + } else { + // Overwrite any existing hard link. + err = os.Remove(path) + if err != nil && !os.IsNotExist(err) { + return err + } + createDisposition = syscall.CREATE_NEW + } + + f, err := openFileOrDir(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE|winio.WRITE_DAC|winio.WRITE_OWNER|winio.ACCESS_SYSTEM_SECURITY, createDisposition) + if err != nil { + return err + } + defer func() { + if f != nil { + f.Close() + os.Remove(path) + } + }() + + err = winio.SetFileBasicInfo(f, fileInfo) + if err != nil { + return err + } + + w.backupWriter = winio.NewBackupFileWriter(f, true) + w.currentFile = f + w.addedFiles[name] = true + f = nil + return nil + } + + path := filepath.Join(w.root, name) + if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 { + err := os.Mkdir(path, 0) + if err != nil { + return err + } + path += ".$wcidirs$" + } + + f, err := openFileOrDir(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, syscall.CREATE_NEW) + if err != nil { + return err + } + defer func() { + if f != nil { + f.Close() + os.Remove(path) + } + }() + + strippedFi := *fileInfo + strippedFi.FileAttributes = 0 + err = winio.SetFileBasicInfo(f, &strippedFi) + if err != nil { + return err + } + + if strings.HasPrefix(name, `Hives\`) { + w.backupWriter = winio.NewBackupFileWriter(f, false) + } else { + // The file attributes are written before the stream. + err = binary.Write(f, binary.LittleEndian, uint32(fileInfo.FileAttributes)) + if err != nil { + return err + } + } + + w.currentFile = f + w.addedFiles[name] = true + f = nil + return nil +} + +func (w *legacyLayerWriter) AddLink(name string, target string) error { + w.reset() + err := w.init() + if err != nil { + return err + } + + var requiredPrefix string + var roots []string + if prefix := `Files\`; strings.HasPrefix(name, prefix) { + requiredPrefix = prefix + // Look for cross-layer hard link targets in the parent layers, since + // nothing is in the destination path yet. + roots = w.parentRoots + } else if prefix := `UtilityVM\Files\`; strings.HasPrefix(name, prefix) { + requiredPrefix = prefix + // Since the utility VM is fully cloned into the destination path + // already, look for cross-layer hard link targets directly in the + // destination path. + roots = []string{w.destRoot} + } + + if requiredPrefix == "" || !strings.HasPrefix(target, requiredPrefix) { + return errors.New("invalid hard link in layer") + } + + // Find to try the target of the link in a previously added file. If that + // fails, search in parent layers. + var selectedRoot string + if _, ok := w.addedFiles[target]; ok { + selectedRoot = w.destRoot + } else { + for _, r := range roots { + if _, err = os.Lstat(filepath.Join(r, target)); err != nil { + if !os.IsNotExist(err) { + return err + } + } else { + selectedRoot = r + break + } + } + if selectedRoot == "" { + return fmt.Errorf("failed to find link target for '%s' -> '%s'", name, target) + } + } + // The link can't be written until after the ImportLayer call. + w.PendingLinks = append(w.PendingLinks, pendingLink{ + Path: filepath.Join(w.destRoot, name), + Target: filepath.Join(selectedRoot, target), + }) + w.addedFiles[name] = true + return nil +} + +func (w *legacyLayerWriter) Remove(name string) error { + if strings.HasPrefix(name, `Files\`) { + w.tombstones = append(w.tombstones, name[len(`Files\`):]) + } else if strings.HasPrefix(name, `UtilityVM\Files\`) { + err := w.initUtilityVM() + if err != nil { + return err + } + // Make sure the path exists; os.RemoveAll will not fail if the file is + // already gone, and this needs to be a fatal error for diagnostics + // purposes. + path := filepath.Join(w.destRoot, name) + if _, err := os.Lstat(path); err != nil { + return err + } + err = os.RemoveAll(path) + if err != nil { + return err + } + } else { + return fmt.Errorf("invalid tombstone %s", name) + } + + return nil +} + +func (w *legacyLayerWriter) Write(b []byte) (int, error) { + if w.backupWriter == nil { + if w.currentFile == nil { + return 0, errors.New("closed") + } + return w.currentFile.Write(b) + } + return w.backupWriter.Write(b) +} + +func (w *legacyLayerWriter) Close() error { + w.reset() + err := w.init() + if err != nil { + return err + } + tf, err := os.Create(filepath.Join(w.root, "tombstones.txt")) + if err != nil { + return err + } + defer tf.Close() + _, err = tf.Write([]byte("\xef\xbb\xbfVersion 1.0\n")) + if err != nil { + return err + } + for _, t := range w.tombstones { + _, err = tf.Write([]byte(filepath.Join(`\`, t) + "\n")) + if err != nil { + return err + } + } + if w.HasUtilityVM { + err = reapplyDirectoryTimes(w.uvmDi) + if err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/mksyscall_windows.go b/vendor/github.com/Microsoft/hcsshim/mksyscall_windows.go new file mode 100644 index 0000000..82393ca --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/mksyscall_windows.go @@ -0,0 +1,934 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ignore + +/* +mksyscall_windows generates windows system call bodies + +It parses all files specified on command line containing function +prototypes (like syscall_windows.go) and prints system call bodies +to standard output. + +The prototypes are marked by lines beginning with "//sys" and read +like func declarations if //sys is replaced by func, but: + +* The parameter lists must give a name for each argument. This + includes return parameters. + +* The parameter lists must give a type for each argument: + the (x, y, z int) shorthand is not allowed. + +* If the return parameter is an error number, it must be named err. + +* If go func name needs to be different from it's winapi dll name, + the winapi name could be specified at the end, after "=" sign, like + //sys LoadLibrary(libname string) (handle uint32, err error) = LoadLibraryA + +* Each function that returns err needs to supply a condition, that + return value of winapi will be tested against to detect failure. + This would set err to windows "last-error", otherwise it will be nil. + The value can be provided at end of //sys declaration, like + //sys LoadLibrary(libname string) (handle uint32, err error) [failretval==-1] = LoadLibraryA + and is [failretval==0] by default. + +Usage: + mksyscall_windows [flags] [path ...] + +The flags are: + -output + Specify output file name (outputs to console if blank). + -trace + Generate print statement after every syscall. +*/ +package main + +import ( + "bufio" + "bytes" + "errors" + "flag" + "fmt" + "go/format" + "go/parser" + "go/token" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + "runtime" + "sort" + "strconv" + "strings" + "text/template" +) + +var ( + filename = flag.String("output", "", "output file name (standard output if omitted)") + printTraceFlag = flag.Bool("trace", false, "generate print statement after every syscall") + systemDLL = flag.Bool("systemdll", true, "whether all DLLs should be loaded from the Windows system directory") +) + +func trim(s string) string { + return strings.Trim(s, " \t") +} + +var packageName string + +func packagename() string { + return packageName +} + +func syscalldot() string { + if packageName == "syscall" { + return "" + } + return "syscall." +} + +// Param is function parameter +type Param struct { + Name string + Type string + fn *Fn + tmpVarIdx int +} + +// tmpVar returns temp variable name that will be used to represent p during syscall. +func (p *Param) tmpVar() string { + if p.tmpVarIdx < 0 { + p.tmpVarIdx = p.fn.curTmpVarIdx + p.fn.curTmpVarIdx++ + } + return fmt.Sprintf("_p%d", p.tmpVarIdx) +} + +// BoolTmpVarCode returns source code for bool temp variable. +func (p *Param) BoolTmpVarCode() string { + const code = `var %s uint32 + if %s { + %s = 1 + } else { + %s = 0 + }` + tmp := p.tmpVar() + return fmt.Sprintf(code, tmp, p.Name, tmp, tmp) +} + +// SliceTmpVarCode returns source code for slice temp variable. +func (p *Param) SliceTmpVarCode() string { + const code = `var %s *%s + if len(%s) > 0 { + %s = &%s[0] + }` + tmp := p.tmpVar() + return fmt.Sprintf(code, tmp, p.Type[2:], p.Name, tmp, p.Name) +} + +// StringTmpVarCode returns source code for string temp variable. +func (p *Param) StringTmpVarCode() string { + errvar := p.fn.Rets.ErrorVarName() + if errvar == "" { + errvar = "_" + } + tmp := p.tmpVar() + const code = `var %s %s + %s, %s = %s(%s)` + s := fmt.Sprintf(code, tmp, p.fn.StrconvType(), tmp, errvar, p.fn.StrconvFunc(), p.Name) + if errvar == "-" { + return s + } + const morecode = ` + if %s != nil { + return + }` + return s + fmt.Sprintf(morecode, errvar) +} + +// TmpVarCode returns source code for temp variable. +func (p *Param) TmpVarCode() string { + switch { + case p.Type == "bool": + return p.BoolTmpVarCode() + case strings.HasPrefix(p.Type, "[]"): + return p.SliceTmpVarCode() + default: + return "" + } +} + +// TmpVarHelperCode returns source code for helper's temp variable. +func (p *Param) TmpVarHelperCode() string { + if p.Type != "string" { + return "" + } + return p.StringTmpVarCode() +} + +// SyscallArgList returns source code fragments representing p parameter +// in syscall. Slices are translated into 2 syscall parameters: pointer to +// the first element and length. +func (p *Param) SyscallArgList() []string { + t := p.HelperType() + var s string + switch { + case t[0] == '*': + s = fmt.Sprintf("unsafe.Pointer(%s)", p.Name) + case t == "bool": + s = p.tmpVar() + case strings.HasPrefix(t, "[]"): + return []string{ + fmt.Sprintf("uintptr(unsafe.Pointer(%s))", p.tmpVar()), + fmt.Sprintf("uintptr(len(%s))", p.Name), + } + default: + s = p.Name + } + return []string{fmt.Sprintf("uintptr(%s)", s)} +} + +// IsError determines if p parameter is used to return error. +func (p *Param) IsError() bool { + return p.Name == "err" && p.Type == "error" +} + +// HelperType returns type of parameter p used in helper function. +func (p *Param) HelperType() string { + if p.Type == "string" { + return p.fn.StrconvType() + } + return p.Type +} + +// join concatenates parameters ps into a string with sep separator. +// Each parameter is converted into string by applying fn to it +// before conversion. +func join(ps []*Param, fn func(*Param) string, sep string) string { + if len(ps) == 0 { + return "" + } + a := make([]string, 0) + for _, p := range ps { + a = append(a, fn(p)) + } + return strings.Join(a, sep) +} + +// Rets describes function return parameters. +type Rets struct { + Name string + Type string + ReturnsError bool + FailCond string +} + +// ErrorVarName returns error variable name for r. +func (r *Rets) ErrorVarName() string { + if r.ReturnsError { + return "err" + } + if r.Type == "error" { + return r.Name + } + return "" +} + +// ToParams converts r into slice of *Param. +func (r *Rets) ToParams() []*Param { + ps := make([]*Param, 0) + if len(r.Name) > 0 { + ps = append(ps, &Param{Name: r.Name, Type: r.Type}) + } + if r.ReturnsError { + ps = append(ps, &Param{Name: "err", Type: "error"}) + } + return ps +} + +// List returns source code of syscall return parameters. +func (r *Rets) List() string { + s := join(r.ToParams(), func(p *Param) string { return p.Name + " " + p.Type }, ", ") + if len(s) > 0 { + s = "(" + s + ")" + } + return s +} + +// PrintList returns source code of trace printing part correspondent +// to syscall return values. +func (r *Rets) PrintList() string { + return join(r.ToParams(), func(p *Param) string { return fmt.Sprintf(`"%s=", %s, `, p.Name, p.Name) }, `", ", `) +} + +// SetReturnValuesCode returns source code that accepts syscall return values. +func (r *Rets) SetReturnValuesCode() string { + if r.Name == "" && !r.ReturnsError { + return "" + } + retvar := "r0" + if r.Name == "" { + retvar = "r1" + } + errvar := "_" + if r.ReturnsError { + errvar = "e1" + } + return fmt.Sprintf("%s, _, %s := ", retvar, errvar) +} + +func (r *Rets) useLongHandleErrorCode(retvar string) string { + const code = `if %s { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = %sEINVAL + } + }` + cond := retvar + " == 0" + if r.FailCond != "" { + cond = strings.Replace(r.FailCond, "failretval", retvar, 1) + } + return fmt.Sprintf(code, cond, syscalldot()) +} + +// SetErrorCode returns source code that sets return parameters. +func (r *Rets) SetErrorCode() string { + const code = `if r0 != 0 { + %s = %sErrno(r0) + }` + const hrCode = `if int32(r0) < 0 { + %s = %sErrno(win32FromHresult(r0)) + }` + if r.Name == "" && !r.ReturnsError { + return "" + } + if r.Name == "" { + return r.useLongHandleErrorCode("r1") + } + if r.Type == "error" { + if r.Name == "hr" { + return fmt.Sprintf(hrCode, r.Name, syscalldot()) + } else { + return fmt.Sprintf(code, r.Name, syscalldot()) + } + } + s := "" + switch { + case r.Type[0] == '*': + s = fmt.Sprintf("%s = (%s)(unsafe.Pointer(r0))", r.Name, r.Type) + case r.Type == "bool": + s = fmt.Sprintf("%s = r0 != 0", r.Name) + default: + s = fmt.Sprintf("%s = %s(r0)", r.Name, r.Type) + } + if !r.ReturnsError { + return s + } + return s + "\n\t" + r.useLongHandleErrorCode(r.Name) +} + +// Fn describes syscall function. +type Fn struct { + Name string + Params []*Param + Rets *Rets + PrintTrace bool + confirmproc bool + dllname string + dllfuncname string + src string + // TODO: get rid of this field and just use parameter index instead + curTmpVarIdx int // insure tmp variables have uniq names +} + +// extractParams parses s to extract function parameters. +func extractParams(s string, f *Fn) ([]*Param, error) { + s = trim(s) + if s == "" { + return nil, nil + } + a := strings.Split(s, ",") + ps := make([]*Param, len(a)) + for i := range ps { + s2 := trim(a[i]) + b := strings.Split(s2, " ") + if len(b) != 2 { + b = strings.Split(s2, "\t") + if len(b) != 2 { + return nil, errors.New("Could not extract function parameter from \"" + s2 + "\"") + } + } + ps[i] = &Param{ + Name: trim(b[0]), + Type: trim(b[1]), + fn: f, + tmpVarIdx: -1, + } + } + return ps, nil +} + +// extractSection extracts text out of string s starting after start +// and ending just before end. found return value will indicate success, +// and prefix, body and suffix will contain correspondent parts of string s. +func extractSection(s string, start, end rune) (prefix, body, suffix string, found bool) { + s = trim(s) + if strings.HasPrefix(s, string(start)) { + // no prefix + body = s[1:] + } else { + a := strings.SplitN(s, string(start), 2) + if len(a) != 2 { + return "", "", s, false + } + prefix = a[0] + body = a[1] + } + a := strings.SplitN(body, string(end), 2) + if len(a) != 2 { + return "", "", "", false + } + return prefix, a[0], a[1], true +} + +// newFn parses string s and return created function Fn. +func newFn(s string) (*Fn, error) { + s = trim(s) + f := &Fn{ + Rets: &Rets{}, + src: s, + PrintTrace: *printTraceFlag, + } + // function name and args + prefix, body, s, found := extractSection(s, '(', ')') + if !found || prefix == "" { + return nil, errors.New("Could not extract function name and parameters from \"" + f.src + "\"") + } + f.Name = prefix + var err error + f.Params, err = extractParams(body, f) + if err != nil { + return nil, err + } + // return values + _, body, s, found = extractSection(s, '(', ')') + if found { + r, err := extractParams(body, f) + if err != nil { + return nil, err + } + switch len(r) { + case 0: + case 1: + if r[0].IsError() { + f.Rets.ReturnsError = true + } else { + f.Rets.Name = r[0].Name + f.Rets.Type = r[0].Type + } + case 2: + if !r[1].IsError() { + return nil, errors.New("Only last windows error is allowed as second return value in \"" + f.src + "\"") + } + f.Rets.ReturnsError = true + f.Rets.Name = r[0].Name + f.Rets.Type = r[0].Type + default: + return nil, errors.New("Too many return values in \"" + f.src + "\"") + } + } + // fail condition + _, body, s, found = extractSection(s, '[', ']') + if found { + f.Rets.FailCond = body + } + // dll and dll function names + s = trim(s) + if s == "" { + return f, nil + } + if !strings.HasPrefix(s, "=") { + return nil, errors.New("Could not extract dll name from \"" + f.src + "\"") + } + s = trim(s[1:]) + a := strings.Split(s, ".") + switch len(a) { + case 1: + f.dllfuncname = a[0] + case 2: + f.dllname = a[0] + f.dllfuncname = a[1] + default: + return nil, errors.New("Could not extract dll name from \"" + f.src + "\"") + } + if f.dllfuncname[len(f.dllfuncname)-1] == '?' { + f.confirmproc = true + f.dllfuncname = f.dllfuncname[0 : len(f.dllfuncname)-1] + } + return f, nil +} + +// DLLName returns DLL name for function f. +func (f *Fn) DLLName() string { + if f.dllname == "" { + return "kernel32" + } + return f.dllname +} + +// DLLName returns DLL function name for function f. +func (f *Fn) DLLFuncName() string { + if f.dllfuncname == "" { + return f.Name + } + return f.dllfuncname +} + +func (f *Fn) ConfirmProc() bool { + return f.confirmproc +} + +// ParamList returns source code for function f parameters. +func (f *Fn) ParamList() string { + return join(f.Params, func(p *Param) string { return p.Name + " " + p.Type }, ", ") +} + +// HelperParamList returns source code for helper function f parameters. +func (f *Fn) HelperParamList() string { + return join(f.Params, func(p *Param) string { return p.Name + " " + p.HelperType() }, ", ") +} + +// ParamPrintList returns source code of trace printing part correspondent +// to syscall input parameters. +func (f *Fn) ParamPrintList() string { + return join(f.Params, func(p *Param) string { return fmt.Sprintf(`"%s=", %s, `, p.Name, p.Name) }, `", ", `) +} + +// ParamCount return number of syscall parameters for function f. +func (f *Fn) ParamCount() int { + n := 0 + for _, p := range f.Params { + n += len(p.SyscallArgList()) + } + return n +} + +// SyscallParamCount determines which version of Syscall/Syscall6/Syscall9/... +// to use. It returns parameter count for correspondent SyscallX function. +func (f *Fn) SyscallParamCount() int { + n := f.ParamCount() + switch { + case n <= 3: + return 3 + case n <= 6: + return 6 + case n <= 9: + return 9 + case n <= 12: + return 12 + case n <= 15: + return 15 + default: + panic("too many arguments to system call") + } +} + +// Syscall determines which SyscallX function to use for function f. +func (f *Fn) Syscall() string { + c := f.SyscallParamCount() + if c == 3 { + return syscalldot() + "Syscall" + } + return syscalldot() + "Syscall" + strconv.Itoa(c) +} + +// SyscallParamList returns source code for SyscallX parameters for function f. +func (f *Fn) SyscallParamList() string { + a := make([]string, 0) + for _, p := range f.Params { + a = append(a, p.SyscallArgList()...) + } + for len(a) < f.SyscallParamCount() { + a = append(a, "0") + } + return strings.Join(a, ", ") +} + +// HelperCallParamList returns source code of call into function f helper. +func (f *Fn) HelperCallParamList() string { + a := make([]string, 0, len(f.Params)) + for _, p := range f.Params { + s := p.Name + if p.Type == "string" { + s = p.tmpVar() + } + a = append(a, s) + } + return strings.Join(a, ", ") +} + +// IsUTF16 is true, if f is W (utf16) function. It is false +// for all A (ascii) functions. +func (_ *Fn) IsUTF16() bool { + return true +} + +// StrconvFunc returns name of Go string to OS string function for f. +func (f *Fn) StrconvFunc() string { + if f.IsUTF16() { + return syscalldot() + "UTF16PtrFromString" + } + return syscalldot() + "BytePtrFromString" +} + +// StrconvType returns Go type name used for OS string for f. +func (f *Fn) StrconvType() string { + if f.IsUTF16() { + return "*uint16" + } + return "*byte" +} + +// HasStringParam is true, if f has at least one string parameter. +// Otherwise it is false. +func (f *Fn) HasStringParam() bool { + for _, p := range f.Params { + if p.Type == "string" { + return true + } + } + return false +} + +var uniqDllFuncName = make(map[string]bool) + +// IsNotDuplicate is true if f is not a duplicated function +func (f *Fn) IsNotDuplicate() bool { + funcName := f.DLLFuncName() + if uniqDllFuncName[funcName] == false { + uniqDllFuncName[funcName] = true + return true + } + return false +} + +// HelperName returns name of function f helper. +func (f *Fn) HelperName() string { + if !f.HasStringParam() { + return f.Name + } + return "_" + f.Name +} + +// Source files and functions. +type Source struct { + Funcs []*Fn + Files []string + StdLibImports []string + ExternalImports []string +} + +func (src *Source) Import(pkg string) { + src.StdLibImports = append(src.StdLibImports, pkg) + sort.Strings(src.StdLibImports) +} + +func (src *Source) ExternalImport(pkg string) { + src.ExternalImports = append(src.ExternalImports, pkg) + sort.Strings(src.ExternalImports) +} + +// ParseFiles parses files listed in fs and extracts all syscall +// functions listed in sys comments. It returns source files +// and functions collection *Source if successful. +func ParseFiles(fs []string) (*Source, error) { + src := &Source{ + Funcs: make([]*Fn, 0), + Files: make([]string, 0), + StdLibImports: []string{ + "unsafe", + }, + ExternalImports: make([]string, 0), + } + for _, file := range fs { + if err := src.ParseFile(file); err != nil { + return nil, err + } + } + return src, nil +} + +// DLLs return dll names for a source set src. +func (src *Source) DLLs() []string { + uniq := make(map[string]bool) + r := make([]string, 0) + for _, f := range src.Funcs { + name := f.DLLName() + if _, found := uniq[name]; !found { + uniq[name] = true + r = append(r, name) + } + } + return r +} + +// ParseFile adds additional file path to a source set src. +func (src *Source) ParseFile(path string) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + s := bufio.NewScanner(file) + for s.Scan() { + t := trim(s.Text()) + if len(t) < 7 { + continue + } + if !strings.HasPrefix(t, "//sys") { + continue + } + t = t[5:] + if !(t[0] == ' ' || t[0] == '\t') { + continue + } + f, err := newFn(t[1:]) + if err != nil { + return err + } + src.Funcs = append(src.Funcs, f) + } + if err := s.Err(); err != nil { + return err + } + src.Files = append(src.Files, path) + + // get package name + fset := token.NewFileSet() + _, err = file.Seek(0, 0) + if err != nil { + return err + } + pkg, err := parser.ParseFile(fset, "", file, parser.PackageClauseOnly) + if err != nil { + return err + } + packageName = pkg.Name.Name + + return nil +} + +// IsStdRepo returns true if src is part of standard library. +func (src *Source) IsStdRepo() (bool, error) { + if len(src.Files) == 0 { + return false, errors.New("no input files provided") + } + abspath, err := filepath.Abs(src.Files[0]) + if err != nil { + return false, err + } + goroot := runtime.GOROOT() + if runtime.GOOS == "windows" { + abspath = strings.ToLower(abspath) + goroot = strings.ToLower(goroot) + } + sep := string(os.PathSeparator) + if !strings.HasSuffix(goroot, sep) { + goroot += sep + } + return strings.HasPrefix(abspath, goroot), nil +} + +// Generate output source file from a source set src. +func (src *Source) Generate(w io.Writer) error { + const ( + pkgStd = iota // any package in std library + pkgXSysWindows // x/sys/windows package + pkgOther + ) + isStdRepo, err := src.IsStdRepo() + if err != nil { + return err + } + var pkgtype int + switch { + case isStdRepo: + pkgtype = pkgStd + case packageName == "windows": + // TODO: this needs better logic than just using package name + pkgtype = pkgXSysWindows + default: + pkgtype = pkgOther + } + if *systemDLL { + switch pkgtype { + case pkgStd: + src.Import("internal/syscall/windows/sysdll") + case pkgXSysWindows: + default: + src.ExternalImport("golang.org/x/sys/windows") + } + } + src.ExternalImport("github.com/Microsoft/go-winio") + if packageName != "syscall" { + src.Import("syscall") + } + funcMap := template.FuncMap{ + "packagename": packagename, + "syscalldot": syscalldot, + "newlazydll": func(dll string) string { + arg := "\"" + dll + ".dll\"" + if !*systemDLL { + return syscalldot() + "NewLazyDLL(" + arg + ")" + } + switch pkgtype { + case pkgStd: + return syscalldot() + "NewLazyDLL(sysdll.Add(" + arg + "))" + case pkgXSysWindows: + return "NewLazySystemDLL(" + arg + ")" + default: + return "windows.NewLazySystemDLL(" + arg + ")" + } + }, + } + t := template.Must(template.New("main").Funcs(funcMap).Parse(srcTemplate)) + err = t.Execute(w, src) + if err != nil { + return errors.New("Failed to execute template: " + err.Error()) + } + return nil +} + +func usage() { + fmt.Fprintf(os.Stderr, "usage: mksyscall_windows [flags] [path ...]\n") + flag.PrintDefaults() + os.Exit(1) +} + +func main() { + flag.Usage = usage + flag.Parse() + if len(flag.Args()) <= 0 { + fmt.Fprintf(os.Stderr, "no files to parse provided\n") + usage() + } + + src, err := ParseFiles(flag.Args()) + if err != nil { + log.Fatal(err) + } + + var buf bytes.Buffer + if err := src.Generate(&buf); err != nil { + log.Fatal(err) + } + + data, err := format.Source(buf.Bytes()) + if err != nil { + log.Fatal(err) + } + if *filename == "" { + _, err = os.Stdout.Write(data) + } else { + err = ioutil.WriteFile(*filename, data, 0644) + } + if err != nil { + log.Fatal(err) + } +} + +// TODO: use println instead to print in the following template +const srcTemplate = ` + +{{define "main"}}// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT + +package {{packagename}} + +import ( +{{range .StdLibImports}}"{{.}}" +{{end}} + +{{range .ExternalImports}}"{{.}}" +{{end}} +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = {{syscalldot}}Errno(errnoERROR_IO_PENDING) +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e {{syscalldot}}Errno) error { + switch e { + case 0: + return nil + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( +{{template "dlls" .}} +{{template "funcnames" .}}) +{{range .Funcs}}{{if .HasStringParam}}{{template "helperbody" .}}{{end}}{{template "funcbody" .}}{{end}} +{{end}} + +{{/* help functions */}} + +{{define "dlls"}}{{range .DLLs}} mod{{.}} = {{newlazydll .}} +{{end}}{{end}} + +{{define "funcnames"}}{{range .Funcs}}{{if .IsNotDuplicate}} proc{{.DLLFuncName}} = mod{{.DLLName}}.NewProc("{{.DLLFuncName}}"){{end}} +{{end}}{{end}} + +{{define "helperbody"}} +func {{.Name}}({{.ParamList}}) {{template "results" .}}{ +{{template "helpertmpvars" .}} return {{.HelperName}}({{.HelperCallParamList}}) +} +{{end}} + +{{define "funcbody"}} +func {{.HelperName}}({{.HelperParamList}}) {{template "results" .}}{ +{{template "tmpvars" .}} {{template "syscallcheck" .}}{{template "syscall" .}} +{{template "seterror" .}}{{template "printtrace" .}} return +} +{{end}} + +{{define "helpertmpvars"}}{{range .Params}}{{if .TmpVarHelperCode}} {{.TmpVarHelperCode}} +{{end}}{{end}}{{end}} + +{{define "tmpvars"}}{{range .Params}}{{if .TmpVarCode}} {{.TmpVarCode}} +{{end}}{{end}}{{end}} + +{{define "results"}}{{if .Rets.List}}{{.Rets.List}} {{end}}{{end}} + +{{define "syscall"}}{{.Rets.SetReturnValuesCode}}{{.Syscall}}(proc{{.DLLFuncName}}.Addr(), {{.ParamCount}}, {{.SyscallParamList}}){{end}} + +{{define "syscallcheck"}}{{if .ConfirmProc}}if {{.Rets.ErrorVarName}} = proc{{.DLLFuncName}}.Find(); {{.Rets.ErrorVarName}} != nil { + return +} +{{end}}{{end}} + + +{{define "seterror"}}{{if .Rets.SetErrorCode}} {{.Rets.SetErrorCode}} +{{end}}{{end}} + +{{define "printtrace"}}{{if .PrintTrace}} print("SYSCALL: {{.Name}}(", {{.ParamPrintList}}") (", {{.Rets.PrintList}}")\n") +{{end}}{{end}} + +` diff --git a/vendor/github.com/Microsoft/hcsshim/nametoguid.go b/vendor/github.com/Microsoft/hcsshim/nametoguid.go new file mode 100644 index 0000000..1a522f9 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/nametoguid.go @@ -0,0 +1,20 @@ +package hcsshim + +import "github.com/Sirupsen/logrus" + +// NameToGuid converts the given string into a GUID using the algorithm in the +// Host Compute Service, ensuring GUIDs generated with the same string are common +// across all clients. +func NameToGuid(name string) (id GUID, err error) { + title := "hcsshim::NameToGuid " + logrus.Debugf(title+"Name %s", name) + + err = nameToGuid(name, &id) + if err != nil { + err = makeErrorf(err, title, "name=%s", name) + logrus.Error(err) + return + } + + return +} diff --git a/vendor/github.com/Microsoft/hcsshim/preparelayer.go b/vendor/github.com/Microsoft/hcsshim/preparelayer.go new file mode 100644 index 0000000..2791683 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/preparelayer.go @@ -0,0 +1,46 @@ +package hcsshim + +import ( + "sync" + + "github.com/Sirupsen/logrus" +) + +var prepareLayerLock sync.Mutex + +// PrepareLayer finds a mounted read-write layer matching layerId and enables the +// the filesystem filter for use on that layer. This requires the paths to all +// parent layers, and is necessary in order to view or interact with the layer +// as an actual filesystem (reading and writing files, creating directories, etc). +// Disabling the filter must be done via UnprepareLayer. +func PrepareLayer(info DriverInfo, layerId string, parentLayerPaths []string) error { + title := "hcsshim::PrepareLayer " + logrus.Debugf(title+"flavour %d layerId %s", info.Flavour, layerId) + + // Generate layer descriptors + layers, err := layerPathsToDescriptors(parentLayerPaths) + if err != nil { + return err + } + + // Convert info to API calling convention + infop, err := convertDriverInfo(info) + if err != nil { + logrus.Error(err) + return err + } + + // This lock is a temporary workaround for a Windows bug. Only allowing one + // call to prepareLayer at a time vastly reduces the chance of a timeout. + prepareLayerLock.Lock() + defer prepareLayerLock.Unlock() + err = prepareLayer(&infop, layerId, layers) + if err != nil { + err = makeErrorf(err, title, "layerId=%s flavour=%d", layerId, info.Flavour) + logrus.Error(err) + return err + } + + logrus.Debugf(title+"succeeded flavour=%d layerId=%s", info.Flavour, layerId) + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/process.go b/vendor/github.com/Microsoft/hcsshim/process.go new file mode 100644 index 0000000..af3fab3 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/process.go @@ -0,0 +1,391 @@ +package hcsshim + +import ( + "encoding/json" + "io" + "runtime" + "sync" + "syscall" + "time" + + "github.com/Sirupsen/logrus" +) + +// ContainerError is an error encountered in HCS +type process struct { + handleLock sync.RWMutex + handle hcsProcess + processID int + container *container + cachedPipes *cachedPipes + callbackNumber uintptr +} + +type cachedPipes struct { + stdIn syscall.Handle + stdOut syscall.Handle + stdErr syscall.Handle +} + +type processModifyRequest struct { + Operation string + ConsoleSize *consoleSize `json:",omitempty"` + CloseHandle *closeHandle `json:",omitempty"` +} + +type consoleSize struct { + Height uint16 + Width uint16 +} + +type closeHandle struct { + Handle string +} + +type processStatus struct { + ProcessID uint32 + Exited bool + ExitCode uint32 + LastWaitResult int32 +} + +const ( + stdIn string = "StdIn" + stdOut string = "StdOut" + stdErr string = "StdErr" +) + +const ( + modifyConsoleSize string = "ConsoleSize" + modifyCloseHandle string = "CloseHandle" +) + +// Pid returns the process ID of the process within the container. +func (process *process) Pid() int { + return process.processID +} + +// Kill signals the process to terminate but does not wait for it to finish terminating. +func (process *process) Kill() error { + process.handleLock.RLock() + defer process.handleLock.RUnlock() + operation := "Kill" + title := "HCSShim::Process::" + operation + logrus.Debugf(title+" processid=%d", process.processID) + + if process.handle == 0 { + return makeProcessError(process, operation, "", ErrAlreadyClosed) + } + + var resultp *uint16 + err := hcsTerminateProcess(process.handle, &resultp) + err = processHcsResult(err, resultp) + if err != nil { + return makeProcessError(process, operation, "", err) + } + + logrus.Debugf(title+" succeeded processid=%d", process.processID) + return nil +} + +// Wait waits for the process to exit. +func (process *process) Wait() error { + operation := "Wait" + title := "HCSShim::Process::" + operation + logrus.Debugf(title+" processid=%d", process.processID) + + err := waitForNotification(process.callbackNumber, hcsNotificationProcessExited, nil) + if err != nil { + return makeProcessError(process, operation, "", err) + } + + logrus.Debugf(title+" succeeded processid=%d", process.processID) + return nil +} + +// WaitTimeout waits for the process to exit or the duration to elapse. It returns +// false if timeout occurs. +func (process *process) WaitTimeout(timeout time.Duration) error { + operation := "WaitTimeout" + title := "HCSShim::Process::" + operation + logrus.Debugf(title+" processid=%d", process.processID) + + err := waitForNotification(process.callbackNumber, hcsNotificationProcessExited, &timeout) + if err != nil { + return makeProcessError(process, operation, "", err) + } + + logrus.Debugf(title+" succeeded processid=%d", process.processID) + return nil +} + +// ExitCode returns the exit code of the process. The process must have +// already terminated. +func (process *process) ExitCode() (int, error) { + process.handleLock.RLock() + defer process.handleLock.RUnlock() + operation := "ExitCode" + title := "HCSShim::Process::" + operation + logrus.Debugf(title+" processid=%d", process.processID) + + if process.handle == 0 { + return 0, makeProcessError(process, operation, "", ErrAlreadyClosed) + } + + properties, err := process.properties() + if err != nil { + return 0, makeProcessError(process, operation, "", err) + } + + if properties.Exited == false { + return 0, makeProcessError(process, operation, "", ErrInvalidProcessState) + } + + if properties.LastWaitResult != 0 { + return 0, makeProcessError(process, operation, "", syscall.Errno(properties.LastWaitResult)) + } + + logrus.Debugf(title+" succeeded processid=%d exitCode=%d", process.processID, properties.ExitCode) + return int(properties.ExitCode), nil +} + +// ResizeConsole resizes the console of the process. +func (process *process) ResizeConsole(width, height uint16) error { + process.handleLock.RLock() + defer process.handleLock.RUnlock() + operation := "ResizeConsole" + title := "HCSShim::Process::" + operation + logrus.Debugf(title+" processid=%d", process.processID) + + if process.handle == 0 { + return makeProcessError(process, operation, "", ErrAlreadyClosed) + } + + modifyRequest := processModifyRequest{ + Operation: modifyConsoleSize, + ConsoleSize: &consoleSize{ + Height: height, + Width: width, + }, + } + + modifyRequestb, err := json.Marshal(modifyRequest) + if err != nil { + return err + } + + modifyRequestStr := string(modifyRequestb) + + var resultp *uint16 + err = hcsModifyProcess(process.handle, modifyRequestStr, &resultp) + err = processHcsResult(err, resultp) + if err != nil { + return makeProcessError(process, operation, "", err) + } + + logrus.Debugf(title+" succeeded processid=%d", process.processID) + return nil +} + +func (process *process) properties() (*processStatus, error) { + operation := "properties" + title := "HCSShim::Process::" + operation + logrus.Debugf(title+" processid=%d", process.processID) + + var ( + resultp *uint16 + propertiesp *uint16 + ) + err := hcsGetProcessProperties(process.handle, &propertiesp, &resultp) + err = processHcsResult(err, resultp) + if err != nil { + return nil, err + } + + if propertiesp == nil { + return nil, ErrUnexpectedValue + } + propertiesRaw := convertAndFreeCoTaskMemBytes(propertiesp) + + properties := &processStatus{} + if err := json.Unmarshal(propertiesRaw, properties); err != nil { + return nil, err + } + + logrus.Debugf(title+" succeeded processid=%d, properties=%s", process.processID, propertiesRaw) + return properties, nil +} + +// Stdio returns the stdin, stdout, and stderr pipes, respectively. Closing +// these pipes does not close the underlying pipes; it should be possible to +// call this multiple times to get multiple interfaces. +func (process *process) Stdio() (io.WriteCloser, io.ReadCloser, io.ReadCloser, error) { + process.handleLock.RLock() + defer process.handleLock.RUnlock() + operation := "Stdio" + title := "HCSShim::Process::" + operation + logrus.Debugf(title+" processid=%d", process.processID) + + if process.handle == 0 { + return nil, nil, nil, makeProcessError(process, operation, "", ErrAlreadyClosed) + } + + var stdIn, stdOut, stdErr syscall.Handle + + if process.cachedPipes == nil { + var ( + processInfo hcsProcessInformation + resultp *uint16 + ) + err := hcsGetProcessInfo(process.handle, &processInfo, &resultp) + err = processHcsResult(err, resultp) + if err != nil { + return nil, nil, nil, makeProcessError(process, operation, "", err) + } + + stdIn, stdOut, stdErr = processInfo.StdInput, processInfo.StdOutput, processInfo.StdError + } else { + // Use cached pipes + stdIn, stdOut, stdErr = process.cachedPipes.stdIn, process.cachedPipes.stdOut, process.cachedPipes.stdErr + + // Invalidate the cache + process.cachedPipes = nil + } + + pipes, err := makeOpenFiles([]syscall.Handle{stdIn, stdOut, stdErr}) + if err != nil { + return nil, nil, nil, makeProcessError(process, operation, "", err) + } + + logrus.Debugf(title+" succeeded processid=%d", process.processID) + return pipes[0], pipes[1], pipes[2], nil +} + +// CloseStdin closes the write side of the stdin pipe so that the process is +// notified on the read side that there is no more data in stdin. +func (process *process) CloseStdin() error { + process.handleLock.RLock() + defer process.handleLock.RUnlock() + operation := "CloseStdin" + title := "HCSShim::Process::" + operation + logrus.Debugf(title+" processid=%d", process.processID) + + if process.handle == 0 { + return makeProcessError(process, operation, "", ErrAlreadyClosed) + } + + modifyRequest := processModifyRequest{ + Operation: modifyCloseHandle, + CloseHandle: &closeHandle{ + Handle: stdIn, + }, + } + + modifyRequestb, err := json.Marshal(modifyRequest) + if err != nil { + return err + } + + modifyRequestStr := string(modifyRequestb) + + var resultp *uint16 + err = hcsModifyProcess(process.handle, modifyRequestStr, &resultp) + err = processHcsResult(err, resultp) + if err != nil { + return makeProcessError(process, operation, "", err) + } + + logrus.Debugf(title+" succeeded processid=%d", process.processID) + return nil +} + +// Close cleans up any state associated with the process but does not kill +// or wait on it. +func (process *process) Close() error { + process.handleLock.Lock() + defer process.handleLock.Unlock() + operation := "Close" + title := "HCSShim::Process::" + operation + logrus.Debugf(title+" processid=%d", process.processID) + + // Don't double free this + if process.handle == 0 { + return nil + } + + if err := process.unregisterCallback(); err != nil { + return makeProcessError(process, operation, "", err) + } + + if err := hcsCloseProcess(process.handle); err != nil { + return makeProcessError(process, operation, "", err) + } + + process.handle = 0 + runtime.SetFinalizer(process, nil) + + logrus.Debugf(title+" succeeded processid=%d", process.processID) + return nil +} + +// closeProcess wraps process.Close for use by a finalizer +func closeProcess(process *process) { + process.Close() +} + +func (process *process) registerCallback() error { + context := ¬ifcationWatcherContext{ + channels: newChannels(), + } + + callbackMapLock.Lock() + callbackNumber := nextCallback + nextCallback++ + callbackMap[callbackNumber] = context + callbackMapLock.Unlock() + + var callbackHandle hcsCallback + err := hcsRegisterProcessCallback(process.handle, notificationWatcherCallback, callbackNumber, &callbackHandle) + if err != nil { + return err + } + context.handle = callbackHandle + process.callbackNumber = callbackNumber + + return nil +} + +func (process *process) unregisterCallback() error { + callbackNumber := process.callbackNumber + + callbackMapLock.RLock() + context := callbackMap[callbackNumber] + callbackMapLock.RUnlock() + + if context == nil { + return nil + } + + handle := context.handle + + if handle == 0 { + return nil + } + + // hcsUnregisterProcessCallback has its own syncronization + // to wait for all callbacks to complete. We must NOT hold the callbackMapLock. + err := hcsUnregisterProcessCallback(handle) + if err != nil { + return err + } + + closeChannels(context.channels) + + callbackMapLock.Lock() + callbackMap[callbackNumber] = nil + callbackMapLock.Unlock() + + handle = 0 + + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/processimage.go b/vendor/github.com/Microsoft/hcsshim/processimage.go new file mode 100644 index 0000000..fadb1b9 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/processimage.go @@ -0,0 +1,23 @@ +package hcsshim + +import "os" + +// ProcessBaseLayer post-processes a base layer that has had its files extracted. +// The files should have been extracted to \Files. +func ProcessBaseLayer(path string) error { + err := processBaseImage(path) + if err != nil { + return &os.PathError{Op: "ProcessBaseLayer", Path: path, Err: err} + } + return nil +} + +// ProcessUtilityVMImage post-processes a utility VM image that has had its files extracted. +// The files should have been extracted to \Files. +func ProcessUtilityVMImage(path string) error { + err := processUtilityImage(path) + if err != nil { + return &os.PathError{Op: "ProcessUtilityVMImage", Path: path, Err: err} + } + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/unpreparelayer.go b/vendor/github.com/Microsoft/hcsshim/unpreparelayer.go new file mode 100644 index 0000000..d0ead0b --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/unpreparelayer.go @@ -0,0 +1,27 @@ +package hcsshim + +import "github.com/Sirupsen/logrus" + +// UnprepareLayer disables the filesystem filter for the read-write layer with +// the given id. +func UnprepareLayer(info DriverInfo, layerId string) error { + title := "hcsshim::UnprepareLayer " + logrus.Debugf(title+"flavour %d layerId %s", info.Flavour, layerId) + + // Convert info to API calling convention + infop, err := convertDriverInfo(info) + if err != nil { + logrus.Error(err) + return err + } + + err = unprepareLayer(&infop, layerId) + if err != nil { + err = makeErrorf(err, title, "layerId=%s flavour=%d", layerId, info.Flavour) + logrus.Error(err) + return err + } + + logrus.Debugf(title+"succeeded flavour %d layerId=%s", info.Flavour, layerId) + return nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/utils.go b/vendor/github.com/Microsoft/hcsshim/utils.go new file mode 100644 index 0000000..bd6e2d9 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/utils.go @@ -0,0 +1,33 @@ +package hcsshim + +import ( + "io" + "syscall" + + "github.com/Microsoft/go-winio" +) + +// makeOpenFiles calls winio.MakeOpenFile for each handle in a slice but closes all the handles +// if there is an error. +func makeOpenFiles(hs []syscall.Handle) (_ []io.ReadWriteCloser, err error) { + fs := make([]io.ReadWriteCloser, len(hs)) + for i, h := range hs { + if h != syscall.Handle(0) { + if err == nil { + fs[i], err = winio.MakeOpenFile(h) + } + if err != nil { + syscall.Close(h) + } + } + } + if err != nil { + for _, f := range fs { + if f != nil { + f.Close() + } + } + return nil, err + } + return fs, nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/version.go b/vendor/github.com/Microsoft/hcsshim/version.go new file mode 100644 index 0000000..ae10c23 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/version.go @@ -0,0 +1,7 @@ +package hcsshim + +// IsTP4 returns whether the currently running Windows build is at least TP4. +func IsTP4() bool { + // HNSCall was not present in TP4 + return procHNSCall.Find() != nil +} diff --git a/vendor/github.com/Microsoft/hcsshim/waithelper.go b/vendor/github.com/Microsoft/hcsshim/waithelper.go new file mode 100644 index 0000000..89c9461 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/waithelper.go @@ -0,0 +1,62 @@ +package hcsshim + +import ( + "time" + + "github.com/Sirupsen/logrus" +) + +func processAsyncHcsResult(err error, resultp *uint16, callbackNumber uintptr, expectedNotification hcsNotification, timeout *time.Duration) error { + err = processHcsResult(err, resultp) + if IsPending(err) { + return waitForNotification(callbackNumber, expectedNotification, timeout) + } + + return err +} + +func waitForNotification(callbackNumber uintptr, expectedNotification hcsNotification, timeout *time.Duration) error { + callbackMapLock.RLock() + channels := callbackMap[callbackNumber].channels + callbackMapLock.RUnlock() + + expectedChannel := channels[expectedNotification] + if expectedChannel == nil { + logrus.Errorf("unknown notification type in waitForNotification %x", expectedNotification) + return ErrInvalidNotificationType + } + + var c <-chan time.Time + if timeout != nil { + timer := time.NewTimer(*timeout) + c = timer.C + defer timer.Stop() + } + + select { + case err, ok := <-expectedChannel: + if !ok { + return ErrHandleClose + } + return err + case err, ok := <-channels[hcsNotificationSystemExited]: + if !ok { + return ErrHandleClose + } + // If the expected notification is hcsNotificationSystemExited which of the two selects + // chosen is random. Return the raw error if hcsNotificationSystemExited is expected + if channels[hcsNotificationSystemExited] == expectedChannel { + return err + } + return ErrUnexpectedContainerExit + case _, ok := <-channels[hcsNotificationServiceDisconnect]: + if !ok { + return ErrHandleClose + } + // hcsNotificationServiceDisconnect should never be an expected notification + // it does not need the same handling as hcsNotificationSystemExited + return ErrUnexpectedProcessAbort + case <-c: + return ErrTimeout + } +} diff --git a/vendor/github.com/Microsoft/hcsshim/zhcsshim.go b/vendor/github.com/Microsoft/hcsshim/zhcsshim.go new file mode 100644 index 0000000..5d1a851 --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/zhcsshim.go @@ -0,0 +1,1042 @@ +// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT + +package hcsshim + +import ( + "syscall" + "unsafe" + + "github.com/Microsoft/go-winio" + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return nil + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modole32 = windows.NewLazySystemDLL("ole32.dll") + modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll") + modvmcompute = windows.NewLazySystemDLL("vmcompute.dll") + + procCoTaskMemFree = modole32.NewProc("CoTaskMemFree") + procSetCurrentThreadCompartmentId = modiphlpapi.NewProc("SetCurrentThreadCompartmentId") + procActivateLayer = modvmcompute.NewProc("ActivateLayer") + procCopyLayer = modvmcompute.NewProc("CopyLayer") + procCreateLayer = modvmcompute.NewProc("CreateLayer") + procCreateSandboxLayer = modvmcompute.NewProc("CreateSandboxLayer") + procExpandSandboxSize = modvmcompute.NewProc("ExpandSandboxSize") + procDeactivateLayer = modvmcompute.NewProc("DeactivateLayer") + procDestroyLayer = modvmcompute.NewProc("DestroyLayer") + procExportLayer = modvmcompute.NewProc("ExportLayer") + procGetLayerMountPath = modvmcompute.NewProc("GetLayerMountPath") + procGetBaseImages = modvmcompute.NewProc("GetBaseImages") + procImportLayer = modvmcompute.NewProc("ImportLayer") + procLayerExists = modvmcompute.NewProc("LayerExists") + procNameToGuid = modvmcompute.NewProc("NameToGuid") + procPrepareLayer = modvmcompute.NewProc("PrepareLayer") + procUnprepareLayer = modvmcompute.NewProc("UnprepareLayer") + procProcessBaseImage = modvmcompute.NewProc("ProcessBaseImage") + procProcessUtilityImage = modvmcompute.NewProc("ProcessUtilityImage") + procImportLayerBegin = modvmcompute.NewProc("ImportLayerBegin") + procImportLayerNext = modvmcompute.NewProc("ImportLayerNext") + procImportLayerWrite = modvmcompute.NewProc("ImportLayerWrite") + procImportLayerEnd = modvmcompute.NewProc("ImportLayerEnd") + procExportLayerBegin = modvmcompute.NewProc("ExportLayerBegin") + procExportLayerNext = modvmcompute.NewProc("ExportLayerNext") + procExportLayerRead = modvmcompute.NewProc("ExportLayerRead") + procExportLayerEnd = modvmcompute.NewProc("ExportLayerEnd") + procHcsEnumerateComputeSystems = modvmcompute.NewProc("HcsEnumerateComputeSystems") + procHcsCreateComputeSystem = modvmcompute.NewProc("HcsCreateComputeSystem") + procHcsOpenComputeSystem = modvmcompute.NewProc("HcsOpenComputeSystem") + procHcsCloseComputeSystem = modvmcompute.NewProc("HcsCloseComputeSystem") + procHcsStartComputeSystem = modvmcompute.NewProc("HcsStartComputeSystem") + procHcsShutdownComputeSystem = modvmcompute.NewProc("HcsShutdownComputeSystem") + procHcsTerminateComputeSystem = modvmcompute.NewProc("HcsTerminateComputeSystem") + procHcsPauseComputeSystem = modvmcompute.NewProc("HcsPauseComputeSystem") + procHcsResumeComputeSystem = modvmcompute.NewProc("HcsResumeComputeSystem") + procHcsGetComputeSystemProperties = modvmcompute.NewProc("HcsGetComputeSystemProperties") + procHcsModifyComputeSystem = modvmcompute.NewProc("HcsModifyComputeSystem") + procHcsRegisterComputeSystemCallback = modvmcompute.NewProc("HcsRegisterComputeSystemCallback") + procHcsUnregisterComputeSystemCallback = modvmcompute.NewProc("HcsUnregisterComputeSystemCallback") + procHcsCreateProcess = modvmcompute.NewProc("HcsCreateProcess") + procHcsOpenProcess = modvmcompute.NewProc("HcsOpenProcess") + procHcsCloseProcess = modvmcompute.NewProc("HcsCloseProcess") + procHcsTerminateProcess = modvmcompute.NewProc("HcsTerminateProcess") + procHcsGetProcessInfo = modvmcompute.NewProc("HcsGetProcessInfo") + procHcsGetProcessProperties = modvmcompute.NewProc("HcsGetProcessProperties") + procHcsModifyProcess = modvmcompute.NewProc("HcsModifyProcess") + procHcsGetServiceProperties = modvmcompute.NewProc("HcsGetServiceProperties") + procHcsRegisterProcessCallback = modvmcompute.NewProc("HcsRegisterProcessCallback") + procHcsUnregisterProcessCallback = modvmcompute.NewProc("HcsUnregisterProcessCallback") + procHcsModifyServiceSettings = modvmcompute.NewProc("HcsModifyServiceSettings") + procHNSCall = modvmcompute.NewProc("HNSCall") +) + +func coTaskMemFree(buffer unsafe.Pointer) { + syscall.Syscall(procCoTaskMemFree.Addr(), 1, uintptr(buffer), 0, 0) + return +} + +func SetCurrentThreadCompartmentId(compartmentId uint32) (hr error) { + r0, _, _ := syscall.Syscall(procSetCurrentThreadCompartmentId.Addr(), 1, uintptr(compartmentId), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func activateLayer(info *driverInfo, id string) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + return _activateLayer(info, _p0) +} + +func _activateLayer(info *driverInfo, id *uint16) (hr error) { + if hr = procActivateLayer.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procActivateLayer.Addr(), 2, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(id)), 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func copyLayer(info *driverInfo, srcId string, dstId string, descriptors []WC_LAYER_DESCRIPTOR) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(srcId) + if hr != nil { + return + } + var _p1 *uint16 + _p1, hr = syscall.UTF16PtrFromString(dstId) + if hr != nil { + return + } + return _copyLayer(info, _p0, _p1, descriptors) +} + +func _copyLayer(info *driverInfo, srcId *uint16, dstId *uint16, descriptors []WC_LAYER_DESCRIPTOR) (hr error) { + var _p2 *WC_LAYER_DESCRIPTOR + if len(descriptors) > 0 { + _p2 = &descriptors[0] + } + if hr = procCopyLayer.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procCopyLayer.Addr(), 5, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(srcId)), uintptr(unsafe.Pointer(dstId)), uintptr(unsafe.Pointer(_p2)), uintptr(len(descriptors)), 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func createLayer(info *driverInfo, id string, parent string) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + var _p1 *uint16 + _p1, hr = syscall.UTF16PtrFromString(parent) + if hr != nil { + return + } + return _createLayer(info, _p0, _p1) +} + +func _createLayer(info *driverInfo, id *uint16, parent *uint16) (hr error) { + if hr = procCreateLayer.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procCreateLayer.Addr(), 3, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(parent))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func createSandboxLayer(info *driverInfo, id string, parent string, descriptors []WC_LAYER_DESCRIPTOR) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + var _p1 *uint16 + _p1, hr = syscall.UTF16PtrFromString(parent) + if hr != nil { + return + } + return _createSandboxLayer(info, _p0, _p1, descriptors) +} + +func _createSandboxLayer(info *driverInfo, id *uint16, parent *uint16, descriptors []WC_LAYER_DESCRIPTOR) (hr error) { + var _p2 *WC_LAYER_DESCRIPTOR + if len(descriptors) > 0 { + _p2 = &descriptors[0] + } + if hr = procCreateSandboxLayer.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procCreateSandboxLayer.Addr(), 5, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(parent)), uintptr(unsafe.Pointer(_p2)), uintptr(len(descriptors)), 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func expandSandboxSize(info *driverInfo, id string, size uint64) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + return _expandSandboxSize(info, _p0, size) +} + +func _expandSandboxSize(info *driverInfo, id *uint16, size uint64) (hr error) { + if hr = procExpandSandboxSize.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procExpandSandboxSize.Addr(), 3, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(id)), uintptr(size)) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func deactivateLayer(info *driverInfo, id string) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + return _deactivateLayer(info, _p0) +} + +func _deactivateLayer(info *driverInfo, id *uint16) (hr error) { + if hr = procDeactivateLayer.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procDeactivateLayer.Addr(), 2, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(id)), 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func destroyLayer(info *driverInfo, id string) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + return _destroyLayer(info, _p0) +} + +func _destroyLayer(info *driverInfo, id *uint16) (hr error) { + if hr = procDestroyLayer.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procDestroyLayer.Addr(), 2, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(id)), 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func exportLayer(info *driverInfo, id string, path string, descriptors []WC_LAYER_DESCRIPTOR) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + var _p1 *uint16 + _p1, hr = syscall.UTF16PtrFromString(path) + if hr != nil { + return + } + return _exportLayer(info, _p0, _p1, descriptors) +} + +func _exportLayer(info *driverInfo, id *uint16, path *uint16, descriptors []WC_LAYER_DESCRIPTOR) (hr error) { + var _p2 *WC_LAYER_DESCRIPTOR + if len(descriptors) > 0 { + _p2 = &descriptors[0] + } + if hr = procExportLayer.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procExportLayer.Addr(), 5, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(_p2)), uintptr(len(descriptors)), 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func getLayerMountPath(info *driverInfo, id string, length *uintptr, buffer *uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + return _getLayerMountPath(info, _p0, length, buffer) +} + +func _getLayerMountPath(info *driverInfo, id *uint16, length *uintptr, buffer *uint16) (hr error) { + if hr = procGetLayerMountPath.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procGetLayerMountPath.Addr(), 4, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(length)), uintptr(unsafe.Pointer(buffer)), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func getBaseImages(buffer **uint16) (hr error) { + if hr = procGetBaseImages.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procGetBaseImages.Addr(), 1, uintptr(unsafe.Pointer(buffer)), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func importLayer(info *driverInfo, id string, path string, descriptors []WC_LAYER_DESCRIPTOR) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + var _p1 *uint16 + _p1, hr = syscall.UTF16PtrFromString(path) + if hr != nil { + return + } + return _importLayer(info, _p0, _p1, descriptors) +} + +func _importLayer(info *driverInfo, id *uint16, path *uint16, descriptors []WC_LAYER_DESCRIPTOR) (hr error) { + var _p2 *WC_LAYER_DESCRIPTOR + if len(descriptors) > 0 { + _p2 = &descriptors[0] + } + if hr = procImportLayer.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procImportLayer.Addr(), 5, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(_p2)), uintptr(len(descriptors)), 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func layerExists(info *driverInfo, id string, exists *uint32) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + return _layerExists(info, _p0, exists) +} + +func _layerExists(info *driverInfo, id *uint16, exists *uint32) (hr error) { + if hr = procLayerExists.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procLayerExists.Addr(), 3, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(exists))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func nameToGuid(name string, guid *GUID) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(name) + if hr != nil { + return + } + return _nameToGuid(_p0, guid) +} + +func _nameToGuid(name *uint16, guid *GUID) (hr error) { + if hr = procNameToGuid.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procNameToGuid.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(guid)), 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func prepareLayer(info *driverInfo, id string, descriptors []WC_LAYER_DESCRIPTOR) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + return _prepareLayer(info, _p0, descriptors) +} + +func _prepareLayer(info *driverInfo, id *uint16, descriptors []WC_LAYER_DESCRIPTOR) (hr error) { + var _p1 *WC_LAYER_DESCRIPTOR + if len(descriptors) > 0 { + _p1 = &descriptors[0] + } + if hr = procPrepareLayer.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procPrepareLayer.Addr(), 4, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(_p1)), uintptr(len(descriptors)), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func unprepareLayer(info *driverInfo, id string) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + return _unprepareLayer(info, _p0) +} + +func _unprepareLayer(info *driverInfo, id *uint16) (hr error) { + if hr = procUnprepareLayer.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procUnprepareLayer.Addr(), 2, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(id)), 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func processBaseImage(path string) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(path) + if hr != nil { + return + } + return _processBaseImage(_p0) +} + +func _processBaseImage(path *uint16) (hr error) { + if hr = procProcessBaseImage.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procProcessBaseImage.Addr(), 1, uintptr(unsafe.Pointer(path)), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func processUtilityImage(path string) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(path) + if hr != nil { + return + } + return _processUtilityImage(_p0) +} + +func _processUtilityImage(path *uint16) (hr error) { + if hr = procProcessUtilityImage.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procProcessUtilityImage.Addr(), 1, uintptr(unsafe.Pointer(path)), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func importLayerBegin(info *driverInfo, id string, descriptors []WC_LAYER_DESCRIPTOR, context *uintptr) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + return _importLayerBegin(info, _p0, descriptors, context) +} + +func _importLayerBegin(info *driverInfo, id *uint16, descriptors []WC_LAYER_DESCRIPTOR, context *uintptr) (hr error) { + var _p1 *WC_LAYER_DESCRIPTOR + if len(descriptors) > 0 { + _p1 = &descriptors[0] + } + if hr = procImportLayerBegin.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procImportLayerBegin.Addr(), 5, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(_p1)), uintptr(len(descriptors)), uintptr(unsafe.Pointer(context)), 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func importLayerNext(context uintptr, fileName string, fileInfo *winio.FileBasicInfo) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(fileName) + if hr != nil { + return + } + return _importLayerNext(context, _p0, fileInfo) +} + +func _importLayerNext(context uintptr, fileName *uint16, fileInfo *winio.FileBasicInfo) (hr error) { + if hr = procImportLayerNext.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procImportLayerNext.Addr(), 3, uintptr(context), uintptr(unsafe.Pointer(fileName)), uintptr(unsafe.Pointer(fileInfo))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func importLayerWrite(context uintptr, buffer []byte) (hr error) { + var _p0 *byte + if len(buffer) > 0 { + _p0 = &buffer[0] + } + if hr = procImportLayerWrite.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procImportLayerWrite.Addr(), 3, uintptr(context), uintptr(unsafe.Pointer(_p0)), uintptr(len(buffer))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func importLayerEnd(context uintptr) (hr error) { + if hr = procImportLayerEnd.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procImportLayerEnd.Addr(), 1, uintptr(context), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func exportLayerBegin(info *driverInfo, id string, descriptors []WC_LAYER_DESCRIPTOR, context *uintptr) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + return _exportLayerBegin(info, _p0, descriptors, context) +} + +func _exportLayerBegin(info *driverInfo, id *uint16, descriptors []WC_LAYER_DESCRIPTOR, context *uintptr) (hr error) { + var _p1 *WC_LAYER_DESCRIPTOR + if len(descriptors) > 0 { + _p1 = &descriptors[0] + } + if hr = procExportLayerBegin.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procExportLayerBegin.Addr(), 5, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(_p1)), uintptr(len(descriptors)), uintptr(unsafe.Pointer(context)), 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func exportLayerNext(context uintptr, fileName **uint16, fileInfo *winio.FileBasicInfo, fileSize *int64, deleted *uint32) (hr error) { + if hr = procExportLayerNext.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procExportLayerNext.Addr(), 5, uintptr(context), uintptr(unsafe.Pointer(fileName)), uintptr(unsafe.Pointer(fileInfo)), uintptr(unsafe.Pointer(fileSize)), uintptr(unsafe.Pointer(deleted)), 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func exportLayerRead(context uintptr, buffer []byte, bytesRead *uint32) (hr error) { + var _p0 *byte + if len(buffer) > 0 { + _p0 = &buffer[0] + } + if hr = procExportLayerRead.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procExportLayerRead.Addr(), 4, uintptr(context), uintptr(unsafe.Pointer(_p0)), uintptr(len(buffer)), uintptr(unsafe.Pointer(bytesRead)), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func exportLayerEnd(context uintptr) (hr error) { + if hr = procExportLayerEnd.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procExportLayerEnd.Addr(), 1, uintptr(context), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsEnumerateComputeSystems(query string, computeSystems **uint16, result **uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(query) + if hr != nil { + return + } + return _hcsEnumerateComputeSystems(_p0, computeSystems, result) +} + +func _hcsEnumerateComputeSystems(query *uint16, computeSystems **uint16, result **uint16) (hr error) { + if hr = procHcsEnumerateComputeSystems.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsEnumerateComputeSystems.Addr(), 3, uintptr(unsafe.Pointer(query)), uintptr(unsafe.Pointer(computeSystems)), uintptr(unsafe.Pointer(result))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsCreateComputeSystem(id string, configuration string, identity syscall.Handle, computeSystem *hcsSystem, result **uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + var _p1 *uint16 + _p1, hr = syscall.UTF16PtrFromString(configuration) + if hr != nil { + return + } + return _hcsCreateComputeSystem(_p0, _p1, identity, computeSystem, result) +} + +func _hcsCreateComputeSystem(id *uint16, configuration *uint16, identity syscall.Handle, computeSystem *hcsSystem, result **uint16) (hr error) { + if hr = procHcsCreateComputeSystem.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procHcsCreateComputeSystem.Addr(), 5, uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(configuration)), uintptr(identity), uintptr(unsafe.Pointer(computeSystem)), uintptr(unsafe.Pointer(result)), 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsOpenComputeSystem(id string, computeSystem *hcsSystem, result **uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return + } + return _hcsOpenComputeSystem(_p0, computeSystem, result) +} + +func _hcsOpenComputeSystem(id *uint16, computeSystem *hcsSystem, result **uint16) (hr error) { + if hr = procHcsOpenComputeSystem.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsOpenComputeSystem.Addr(), 3, uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(computeSystem)), uintptr(unsafe.Pointer(result))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsCloseComputeSystem(computeSystem hcsSystem) (hr error) { + if hr = procHcsCloseComputeSystem.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsCloseComputeSystem.Addr(), 1, uintptr(computeSystem), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsStartComputeSystem(computeSystem hcsSystem, options string, result **uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(options) + if hr != nil { + return + } + return _hcsStartComputeSystem(computeSystem, _p0, result) +} + +func _hcsStartComputeSystem(computeSystem hcsSystem, options *uint16, result **uint16) (hr error) { + if hr = procHcsStartComputeSystem.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsStartComputeSystem.Addr(), 3, uintptr(computeSystem), uintptr(unsafe.Pointer(options)), uintptr(unsafe.Pointer(result))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsShutdownComputeSystem(computeSystem hcsSystem, options string, result **uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(options) + if hr != nil { + return + } + return _hcsShutdownComputeSystem(computeSystem, _p0, result) +} + +func _hcsShutdownComputeSystem(computeSystem hcsSystem, options *uint16, result **uint16) (hr error) { + if hr = procHcsShutdownComputeSystem.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsShutdownComputeSystem.Addr(), 3, uintptr(computeSystem), uintptr(unsafe.Pointer(options)), uintptr(unsafe.Pointer(result))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsTerminateComputeSystem(computeSystem hcsSystem, options string, result **uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(options) + if hr != nil { + return + } + return _hcsTerminateComputeSystem(computeSystem, _p0, result) +} + +func _hcsTerminateComputeSystem(computeSystem hcsSystem, options *uint16, result **uint16) (hr error) { + if hr = procHcsTerminateComputeSystem.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsTerminateComputeSystem.Addr(), 3, uintptr(computeSystem), uintptr(unsafe.Pointer(options)), uintptr(unsafe.Pointer(result))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsPauseComputeSystem(computeSystem hcsSystem, options string, result **uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(options) + if hr != nil { + return + } + return _hcsPauseComputeSystem(computeSystem, _p0, result) +} + +func _hcsPauseComputeSystem(computeSystem hcsSystem, options *uint16, result **uint16) (hr error) { + if hr = procHcsPauseComputeSystem.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsPauseComputeSystem.Addr(), 3, uintptr(computeSystem), uintptr(unsafe.Pointer(options)), uintptr(unsafe.Pointer(result))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsResumeComputeSystem(computeSystem hcsSystem, options string, result **uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(options) + if hr != nil { + return + } + return _hcsResumeComputeSystem(computeSystem, _p0, result) +} + +func _hcsResumeComputeSystem(computeSystem hcsSystem, options *uint16, result **uint16) (hr error) { + if hr = procHcsResumeComputeSystem.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsResumeComputeSystem.Addr(), 3, uintptr(computeSystem), uintptr(unsafe.Pointer(options)), uintptr(unsafe.Pointer(result))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsGetComputeSystemProperties(computeSystem hcsSystem, propertyQuery string, properties **uint16, result **uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(propertyQuery) + if hr != nil { + return + } + return _hcsGetComputeSystemProperties(computeSystem, _p0, properties, result) +} + +func _hcsGetComputeSystemProperties(computeSystem hcsSystem, propertyQuery *uint16, properties **uint16, result **uint16) (hr error) { + if hr = procHcsGetComputeSystemProperties.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procHcsGetComputeSystemProperties.Addr(), 4, uintptr(computeSystem), uintptr(unsafe.Pointer(propertyQuery)), uintptr(unsafe.Pointer(properties)), uintptr(unsafe.Pointer(result)), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsModifyComputeSystem(computeSystem hcsSystem, configuration string, result **uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(configuration) + if hr != nil { + return + } + return _hcsModifyComputeSystem(computeSystem, _p0, result) +} + +func _hcsModifyComputeSystem(computeSystem hcsSystem, configuration *uint16, result **uint16) (hr error) { + if hr = procHcsModifyComputeSystem.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsModifyComputeSystem.Addr(), 3, uintptr(computeSystem), uintptr(unsafe.Pointer(configuration)), uintptr(unsafe.Pointer(result))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsRegisterComputeSystemCallback(computeSystem hcsSystem, callback uintptr, context uintptr, callbackHandle *hcsCallback) (hr error) { + if hr = procHcsRegisterComputeSystemCallback.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procHcsRegisterComputeSystemCallback.Addr(), 4, uintptr(computeSystem), uintptr(callback), uintptr(context), uintptr(unsafe.Pointer(callbackHandle)), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsUnregisterComputeSystemCallback(callbackHandle hcsCallback) (hr error) { + if hr = procHcsUnregisterComputeSystemCallback.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsUnregisterComputeSystemCallback.Addr(), 1, uintptr(callbackHandle), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsCreateProcess(computeSystem hcsSystem, processParameters string, processInformation *hcsProcessInformation, process *hcsProcess, result **uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(processParameters) + if hr != nil { + return + } + return _hcsCreateProcess(computeSystem, _p0, processInformation, process, result) +} + +func _hcsCreateProcess(computeSystem hcsSystem, processParameters *uint16, processInformation *hcsProcessInformation, process *hcsProcess, result **uint16) (hr error) { + if hr = procHcsCreateProcess.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procHcsCreateProcess.Addr(), 5, uintptr(computeSystem), uintptr(unsafe.Pointer(processParameters)), uintptr(unsafe.Pointer(processInformation)), uintptr(unsafe.Pointer(process)), uintptr(unsafe.Pointer(result)), 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsOpenProcess(computeSystem hcsSystem, pid uint32, process *hcsProcess, result **uint16) (hr error) { + if hr = procHcsOpenProcess.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procHcsOpenProcess.Addr(), 4, uintptr(computeSystem), uintptr(pid), uintptr(unsafe.Pointer(process)), uintptr(unsafe.Pointer(result)), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsCloseProcess(process hcsProcess) (hr error) { + if hr = procHcsCloseProcess.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsCloseProcess.Addr(), 1, uintptr(process), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsTerminateProcess(process hcsProcess, result **uint16) (hr error) { + if hr = procHcsTerminateProcess.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsTerminateProcess.Addr(), 2, uintptr(process), uintptr(unsafe.Pointer(result)), 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsGetProcessInfo(process hcsProcess, processInformation *hcsProcessInformation, result **uint16) (hr error) { + if hr = procHcsGetProcessInfo.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsGetProcessInfo.Addr(), 3, uintptr(process), uintptr(unsafe.Pointer(processInformation)), uintptr(unsafe.Pointer(result))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsGetProcessProperties(process hcsProcess, processProperties **uint16, result **uint16) (hr error) { + if hr = procHcsGetProcessProperties.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsGetProcessProperties.Addr(), 3, uintptr(process), uintptr(unsafe.Pointer(processProperties)), uintptr(unsafe.Pointer(result))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsModifyProcess(process hcsProcess, settings string, result **uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(settings) + if hr != nil { + return + } + return _hcsModifyProcess(process, _p0, result) +} + +func _hcsModifyProcess(process hcsProcess, settings *uint16, result **uint16) (hr error) { + if hr = procHcsModifyProcess.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsModifyProcess.Addr(), 3, uintptr(process), uintptr(unsafe.Pointer(settings)), uintptr(unsafe.Pointer(result))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsGetServiceProperties(propertyQuery string, properties **uint16, result **uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(propertyQuery) + if hr != nil { + return + } + return _hcsGetServiceProperties(_p0, properties, result) +} + +func _hcsGetServiceProperties(propertyQuery *uint16, properties **uint16, result **uint16) (hr error) { + if hr = procHcsGetServiceProperties.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsGetServiceProperties.Addr(), 3, uintptr(unsafe.Pointer(propertyQuery)), uintptr(unsafe.Pointer(properties)), uintptr(unsafe.Pointer(result))) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsRegisterProcessCallback(process hcsProcess, callback uintptr, context uintptr, callbackHandle *hcsCallback) (hr error) { + if hr = procHcsRegisterProcessCallback.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procHcsRegisterProcessCallback.Addr(), 4, uintptr(process), uintptr(callback), uintptr(context), uintptr(unsafe.Pointer(callbackHandle)), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsUnregisterProcessCallback(callbackHandle hcsCallback) (hr error) { + if hr = procHcsUnregisterProcessCallback.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsUnregisterProcessCallback.Addr(), 1, uintptr(callbackHandle), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func hcsModifyServiceSettings(settings string, result **uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(settings) + if hr != nil { + return + } + return _hcsModifyServiceSettings(_p0, result) +} + +func _hcsModifyServiceSettings(settings *uint16, result **uint16) (hr error) { + if hr = procHcsModifyServiceSettings.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcsModifyServiceSettings.Addr(), 2, uintptr(unsafe.Pointer(settings)), uintptr(unsafe.Pointer(result)), 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} + +func _hnsCall(method string, path string, object string, response **uint16) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(method) + if hr != nil { + return + } + var _p1 *uint16 + _p1, hr = syscall.UTF16PtrFromString(path) + if hr != nil { + return + } + var _p2 *uint16 + _p2, hr = syscall.UTF16PtrFromString(object) + if hr != nil { + return + } + return __hnsCall(_p0, _p1, _p2, response) +} + +func __hnsCall(method *uint16, path *uint16, object *uint16, response **uint16) (hr error) { + if hr = procHNSCall.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procHNSCall.Addr(), 4, uintptr(unsafe.Pointer(method)), uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(object)), uintptr(unsafe.Pointer(response)), 0, 0) + if int32(r0) < 0 { + hr = syscall.Errno(win32FromHresult(r0)) + } + return +} diff --git a/vendor/github.com/crosbymichael/console/console.go b/vendor/github.com/crosbymichael/console/console.go index 5513a1e..a1d392c 100644 --- a/vendor/github.com/crosbymichael/console/console.go +++ b/vendor/github.com/crosbymichael/console/console.go @@ -38,9 +38,7 @@ type WinSize struct { // Current returns the current processes console func Current() Console { - return &master{ - f: os.Stdin, - } + return newMaster(os.Stdin) } // ConsoleFromFile returns a console using the provided file @@ -48,7 +46,5 @@ func ConsoleFromFile(f *os.File) (Console, error) { if err := checkConsole(f); err != nil { return nil, err } - return &master{ - f: f, - }, nil + return newMaster(f), nil } diff --git a/vendor/github.com/crosbymichael/console/console_linux.go b/vendor/github.com/crosbymichael/console/console_linux.go index 7c2c681..2496d0d 100644 --- a/vendor/github.com/crosbymichael/console/console_linux.go +++ b/vendor/github.com/crosbymichael/console/console_linux.go @@ -103,3 +103,9 @@ func checkConsole(f *os.File) error { } return nil } + +func newMaster(f *os.File) Console { + return &master{ + f: f, + } +} diff --git a/vendor/github.com/crosbymichael/console/console_windows.go b/vendor/github.com/crosbymichael/console/console_windows.go new file mode 100644 index 0000000..daaeaa7 --- /dev/null +++ b/vendor/github.com/crosbymichael/console/console_windows.go @@ -0,0 +1,187 @@ +package console + +import ( + "fmt" + "os" + + "github.com/Azure/go-ansiterm/winterm" + "github.com/pkg/errors" +) + +const ( + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx + enableVirtualTerminalInput = 0x0200 + enableVirtualTerminalProcessing = 0x0004 + disableNewlineAutoReturn = 0x0008 +) + +var ( + vtInputSupported bool + ErrNotImplemented = errors.New("not implemented") +) + +func (m *master) initStdios() { + m.in = os.Stdin.Fd() + if mode, err := winterm.GetConsoleMode(m.in); err == nil { + m.inMode = mode + // Validate that enableVirtualTerminalInput is supported, but do not set it. + if err = winterm.SetConsoleMode(m.in, mode|enableVirtualTerminalInput); err == nil { + vtInputSupported = true + } + // Unconditionally set the console mode back even on failure because SetConsoleMode + // remembers invalid bits on input handles. + winterm.SetConsoleMode(m.in, mode) + } else { + fmt.Printf("failed to get console mode for stdin: %v\n", err) + } + + m.out = os.Stdout.Fd() + if mode, err := winterm.GetConsoleMode(m.out); err == nil { + m.outMode = mode + if err := winterm.SetConsoleMode(m.out, mode|enableVirtualTerminalProcessing); err == nil { + m.outMode |= enableVirtualTerminalProcessing + } else { + winterm.SetConsoleMode(m.out, m.outMode) + } + } else { + fmt.Printf("failed to get console mode for stdout: %v\n", err) + } + + m.err = os.Stderr.Fd() + if mode, err := winterm.GetConsoleMode(m.err); err == nil { + m.errMode = mode + if err := winterm.SetConsoleMode(m.err, mode|enableVirtualTerminalProcessing); err == nil { + m.errMode |= enableVirtualTerminalProcessing + } else { + winterm.SetConsoleMode(m.err, m.errMode) + } + } else { + fmt.Printf("failed to get console mode for stderr: %v\n", err) + } +} + +type master struct { + in uintptr + inMode uint32 + + out uintptr + outMode uint32 + + err uintptr + errMode uint32 +} + +func (m *master) SetRaw() error { + if err := makeInputRaw(m.in, m.inMode); err != nil { + return err + } + + // Set StdOut and StdErr to raw mode, we ignore failures since + // disableNewlineAutoReturn might not be supported on this version of + // Windows. + + winterm.SetConsoleMode(m.out, m.outMode|disableNewlineAutoReturn) + + winterm.SetConsoleMode(m.err, m.errMode|disableNewlineAutoReturn) + + return nil +} + +func (m *master) Reset() error { + for _, s := range []struct { + fd uintptr + mode uint32 + }{ + {m.in, m.inMode}, + {m.out, m.outMode}, + {m.err, m.errMode}, + } { + if err := winterm.SetConsoleMode(s.fd, s.mode); err != nil { + return errors.Wrap(err, "unable to restore console mode") + } + } + + return nil +} + +func (m *master) Size() (WinSize, error) { + info, err := winterm.GetConsoleScreenBufferInfo(m.in) + if err != nil { + return WinSize{}, errors.Wrap(err, "unable to get console info") + } + + winsize := WinSize{ + Width: uint16(info.Window.Right - info.Window.Left + 1), + Height: uint16(info.Window.Bottom - info.Window.Top + 1), + } + + return winsize, nil +} + +func (m *master) Resize(ws WinSize) error { + return ErrNotImplemented +} + +func (m *master) ResizeFrom(c Console) error { + return ErrNotImplemented +} + +func (m *master) Close() error { + return nil +} + +func (m *master) Read(b []byte) (int, error) { + panic("not implemented on windows") +} + +func (m *master) Write(b []byte) (int, error) { + panic("not implemented on windows") +} + +// makeInputRaw puts the terminal (Windows Console) connected to the given +// file descriptor into raw mode +func makeInputRaw(fd uintptr, mode uint32) error { + // See + // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx + // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx + + // Disable these modes + mode &^= winterm.ENABLE_ECHO_INPUT + mode &^= winterm.ENABLE_LINE_INPUT + mode &^= winterm.ENABLE_MOUSE_INPUT + mode &^= winterm.ENABLE_WINDOW_INPUT + mode &^= winterm.ENABLE_PROCESSED_INPUT + + // Enable these modes + mode |= winterm.ENABLE_EXTENDED_FLAGS + mode |= winterm.ENABLE_INSERT_MODE + mode |= winterm.ENABLE_QUICK_EDIT_MODE + + if vtInputSupported { + mode |= enableVirtualTerminalInput + } + + if err := winterm.SetConsoleMode(fd, mode); err != nil { + return errors.Wrap(err, "unable to set console to raw mode") + } + + return nil +} + +func checkConsole(f *os.File) error { + if _, err := winterm.GetConsoleMode(f.Fd()); err != nil { + return err + } + return nil +} + +func newMaster(f *os.File) Console { + if f != os.Stdin && f != os.Stdout && f != os.Stderr { + panic("creating a console from a file is not supported on windows") + } + + m := &master{} + m.initStdios() + + return m +} diff --git a/windows/container.go b/windows/container.go new file mode 100644 index 0000000..8ba03b3 --- /dev/null +++ b/windows/container.go @@ -0,0 +1,201 @@ +// +build windows + +package windows + +import ( + "encoding/json" + "sync" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/windows/hcs" + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "golang.org/x/net/context" +) + +var ( + ErrLoadedContainer = errors.New("loaded container can only be terminated") +) + +type State struct { + pid uint32 + status containerd.Status +} + +func (s State) Pid() uint32 { + return s.pid +} + +func (s State) Status() containerd.Status { + return s.status +} + +type eventCallback func(id string, evType containerd.EventType, pid, exitStatus uint32) + +func loadContainers(ctx context.Context, rootDir string) ([]*container, error) { + hcs, err := hcs.LoadAll(ctx, owner, rootDir) + if err != nil { + return nil, err + } + + containers := make([]*container, 0) + for id, h := range hcs { + containers = append(containers, &container{ + id: id, + status: containerd.RunningStatus, + hcs: h, + }) + } + + return containers, nil +} + +func newContainer(id, rootDir string, pid uint32, spec RuntimeSpec, io containerd.IO, sendEvent eventCallback) (*container, error) { + hcs, err := hcs.New(rootDir, owner, id, spec.OCISpec, spec.Configuration, io) + if err != nil { + return nil, err + } + + return &container{ + runtimePid: pid, + id: id, + hcs: hcs, + status: containerd.CreatedStatus, + ecSync: make(chan struct{}), + sendEvent: sendEvent, + }, nil +} + +type container struct { + sync.Mutex + + runtimePid uint32 + id string + hcs *hcs.HCS + status containerd.Status + + ec uint32 + ecErr error + ecSync chan struct{} + sendEvent func(id string, evType containerd.EventType, pid, exitStatus uint32) +} + +func (c *container) Info() containerd.ContainerInfo { + return containerd.ContainerInfo{ + ID: c.id, + Runtime: runtimeName, + } +} + +func (c *container) Start(ctx context.Context) error { + if c.runtimePid == 0 { + return ErrLoadedContainer + } + + err := c.hcs.Start(ctx, false) + if err != nil { + c.hcs.Terminate(ctx) + c.sendEvent(c.id, containerd.ExitEvent, c.runtimePid, 255) + return err + } + + c.setStatus(containerd.RunningStatus) + c.sendEvent(c.id, containerd.StartEvent, c.runtimePid, 0) + + // Wait for our process to terminate + go func() { + c.ec, c.ecErr = c.hcs.ExitCode(context.Background()) + c.setStatus(containerd.StoppedStatus) + c.sendEvent(c.id, containerd.ExitEvent, c.runtimePid, c.ec) + close(c.ecSync) + }() + + return nil +} + +func (c *container) State(ctx context.Context) (containerd.State, error) { + return &State{ + pid: c.runtimePid, + status: c.getStatus(), + }, nil +} + +func (c *container) Kill(ctx context.Context, signal uint32, all bool) error { + return c.hcs.Terminate(ctx) +} + +func (c *container) Exec(ctx context.Context, opts containerd.ExecOpts) (containerd.Process, error) { + if c.runtimePid == 0 { + return nil, ErrLoadedContainer + } + + var procSpec specs.Process + if err := json.Unmarshal(opts.Spec, &procSpec); err != nil { + return nil, errors.Wrap(err, "failed to unmarshal oci spec") + } + + p, err := c.hcs.Exec(ctx, procSpec, opts.IO) + if err != nil { + return nil, err + } + + go func() { + ec, _ := p.ExitCode() + c.sendEvent(c.id, containerd.ExitEvent, p.Pid(), ec) + }() + + return &process{p}, nil +} + +func (c *container) setStatus(status containerd.Status) { + c.Lock() + c.status = status + c.Unlock() +} + +func (c *container) getStatus() containerd.Status { + c.Lock() + defer c.Unlock() + return c.status +} + +func (c *container) exitCode(ctx context.Context) (uint32, error) { + if c.runtimePid == 0 { + return 255, ErrLoadedContainer + } + + <-c.ecSync + return c.ec, c.ecErr +} + +func (c *container) remove(ctx context.Context) error { + return c.hcs.Remove(ctx) +} + +func (c *container) getRuntimePid() uint32 { + return c.runtimePid +} + +type process struct { + p *hcs.Process +} + +func (p *process) State(ctx context.Context) (containerd.State, error) { + return &processState{p.p}, nil +} + +func (p *process) Kill(ctx context.Context, sig uint32, all bool) error { + return p.p.Kill() +} + +type processState struct { + p *hcs.Process +} + +func (s *processState) Status() containerd.Status { + return s.p.Status() +} + +func (s *processState) Pid() uint32 { + return s.p.Pid() +} diff --git a/windows/hcs/hcs.go b/windows/hcs/hcs.go new file mode 100644 index 0000000..5cf24df --- /dev/null +++ b/windows/hcs/hcs.go @@ -0,0 +1,523 @@ +// +build windows + +package hcs + +import ( + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + "syscall" + "time" + + "github.com/Microsoft/hcsshim" + "github.com/Sirupsen/logrus" + "github.com/containerd/containerd" + "github.com/containerd/containerd/log" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" +) + +const ( + layerFile = "layer" + defaultTerminateTimeout = 5 * time.Minute +) + +func LoadAll(ctx context.Context, owner, rootDir string) (map[string]*HCS, error) { + ctrProps, err := hcsshim.GetContainers(hcsshim.ComputeSystemQuery{}) + if err != nil { + return nil, errors.Wrap(err, "failed to retrieve running containers") + } + + containers := make(map[string]*HCS) + for _, p := range ctrProps { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + if p.Owner != owner || p.SystemType != "Container" { + continue + } + + // TODO: take context in account + container, err := hcsshim.OpenContainer(p.ID) + if err != nil { + return nil, errors.Wrapf(err, "failed open container %s", p.ID) + } + stateDir := filepath.Join(rootDir, p.ID) + b, err := ioutil.ReadFile(filepath.Join(stateDir, layerFile)) + containers[p.ID] = &HCS{ + id: p.ID, + container: container, + stateDir: stateDir, + layerFolderPath: string(b), + conf: Configuration{ + TerminateDuration: defaultTerminateTimeout, + }, + } + } + + return containers, nil +} + +// New creates a new container (but doesn't start) it. +func New(rootDir, owner, containerID string, spec specs.Spec, conf Configuration, cio containerd.IO) (*HCS, error) { + stateDir := filepath.Join(rootDir, containerID) + if err := os.MkdirAll(stateDir, 0755); err != nil { + return nil, errors.Wrapf(err, "unable to create container state dir %s", stateDir) + } + + if conf.TerminateDuration == 0 { + conf.TerminateDuration = defaultTerminateTimeout + } + + h := &HCS{ + stateDir: stateDir, + owner: owner, + id: containerID, + spec: spec, + conf: conf, + } + + sio, err := newSIO(cio) + if err != nil { + return nil, err + } + h.io = sio + runtime.SetFinalizer(sio, func(s *shimIO) { + s.Close() + }) + + hcsConf, err := h.newHCSConfiguration() + if err != nil { + return nil, err + } + + ctr, err := hcsshim.CreateContainer(containerID, hcsConf) + if err != nil { + removeLayer(context.TODO(), hcsConf.LayerFolderPath) + return nil, err + } + h.container = ctr + h.layerFolderPath = hcsConf.LayerFolderPath + + return h, nil +} + +type HCS struct { + stateDir string + owner string + id string + spec specs.Spec + conf Configuration + io *shimIO + container hcsshim.Container + initProcess hcsshim.Process + layerFolderPath string +} + +// Start starts the associated container and instantiate the init +// process within it. +func (s *HCS) Start(ctx context.Context, servicing bool) error { + if s.initProcess != nil { + return errors.New("init process already started") + } + if err := s.container.Start(); err != nil { + if err := s.Terminate(ctx); err != nil { + log.G(ctx).WithError(err).Errorf("failed to terminate container %s", s.id) + } + return err + } + + proc, err := s.newProcess(ctx, s.io, s.spec.Process) + if err != nil { + s.Terminate(ctx) + return err + } + + s.initProcess = proc + + return nil +} + +// Pid returns the pid of the container init process +func (s *HCS) Pid() int { + return s.initProcess.Pid() +} + +// ExitCode waits for the container to exit and return the exit code +// of the init process +func (s *HCS) ExitCode(ctx context.Context) (uint32, error) { + // TODO: handle a context cancellation + if err := s.initProcess.Wait(); err != nil { + if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE { + return 255, errors.Wrapf(err, "failed to wait for container '%s' init process", s.id) + } + // container is probably dead, let's try to get its exit code + } + + ec, err := s.initProcess.ExitCode() + if err != nil { + if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE { + return 255, errors.Wrapf(err, "failed to get container '%s' init process exit code", s.id) + } + // Well, unknown exit code it is + ec = 255 + } + + return uint32(ec), err +} + +// Exec starts a new process within the container +func (s *HCS) Exec(ctx context.Context, procSpec specs.Process, io containerd.IO) (*Process, error) { + sio, err := newSIO(io) + if err != nil { + return nil, err + } + p, err := s.newProcess(ctx, sio, procSpec) + if err != nil { + return nil, err + } + + return &Process{ + containerID: s.id, + p: p, + status: containerd.RunningStatus, + }, nil +} + +// newProcess create a new process within a running container. This is +// used to create both the init process and subsequent 'exec' +// processes. +func (s *HCS) newProcess(ctx context.Context, sio *shimIO, procSpec specs.Process) (hcsshim.Process, error) { + conf := hcsshim.ProcessConfig{ + EmulateConsole: sio.terminal, + CreateStdInPipe: sio.stdin != nil, + CreateStdOutPipe: sio.stdout != nil, + CreateStdErrPipe: sio.stderr != nil, + User: procSpec.User.Username, + CommandLine: strings.Join(procSpec.Args, " "), + Environment: ociSpecEnvToHCSEnv(procSpec.Env), + WorkingDirectory: procSpec.Cwd, + } + conf.ConsoleSize[0] = procSpec.ConsoleSize.Height + conf.ConsoleSize[1] = procSpec.ConsoleSize.Width + + if conf.WorkingDirectory == "" { + conf.WorkingDirectory = s.spec.Process.Cwd + } + + proc, err := s.container.CreateProcess(&conf) + if err != nil { + return nil, errors.Wrapf(err, "failed to create process with conf %#v", conf) + + } + pid := proc.Pid() + + stdin, stdout, stderr, err := proc.Stdio() + if err != nil { + s.Terminate(ctx) + return nil, err + } + + if sio.stdin != nil { + go func() { + log.G(ctx).WithField("pid", pid).Debug("stdin: copy started") + io.Copy(stdin, sio.stdin) + log.G(ctx).WithField("pid", pid).Debug("stdin: copy done") + stdin.Close() + sio.stdin.Close() + }() + } else { + proc.CloseStdin() + } + + if sio.stdout != nil { + go func() { + log.G(ctx).WithField("pid", pid).Debug("stdout: copy started") + io.Copy(sio.stdout, stdout) + log.G(ctx).WithField("pid", pid).Debug("stdout: copy done") + stdout.Close() + sio.stdout.Close() + }() + } + + if sio.stderr != nil { + go func() { + log.G(ctx).WithField("pid", pid).Debug("stderr: copy started") + io.Copy(sio.stderr, stderr) + log.G(ctx).WithField("pid", pid).Debug("stderr: copy done") + stderr.Close() + sio.stderr.Close() + }() + } + + return proc, nil +} + +// Terminate stop a running container. +func (s *HCS) Terminate(ctx context.Context) error { + err := s.container.Terminate() + switch { + case hcsshim.IsPending(err): + // TODO: take the context into account + err = s.container.WaitTimeout(s.conf.TerminateDuration) + case hcsshim.IsAlreadyStopped(err): + err = nil + } + + return err +} + +func (s *HCS) Shutdown(ctx context.Context) error { + err := s.container.Shutdown() + switch { + case hcsshim.IsPending(err): + // TODO: take the context into account + err = s.container.WaitTimeout(s.conf.TerminateDuration) + case hcsshim.IsAlreadyStopped(err): + err = nil + } + + if err != nil { + log.G(ctx).WithError(err).Debugf("failed to shutdown container %s, calling terminate", s.id) + return s.Terminate(ctx) + } + + return nil +} + +// Remove start a servicing container if needed then cleanup the container +// resources +func (s *HCS) Remove(ctx context.Context) error { + defer func() { + if err := s.Shutdown(ctx); err != nil { + log.G(ctx).WithError(err).WithField("id", s.id). + Errorf("failed to shutdown/terminate container") + } + + if s.initProcess != nil { + if err := s.initProcess.Close(); err != nil { + log.G(ctx).WithError(err).WithFields(logrus.Fields{"pid": s.Pid(), "id": s.id}). + Errorf("failed to clean init process resources") + } + } + if err := s.container.Close(); err != nil { + log.G(ctx).WithError(err).WithField("id", s.id).Errorf("failed to clean container resources") + } + + // Cleanup folder layer + if err := removeLayer(ctx, s.layerFolderPath); err == nil { + os.RemoveAll(s.stateDir) + } + }() + + if update, err := s.container.HasPendingUpdates(); err != nil || !update { + return nil + } + + // TODO: take the context into account + serviceHCS, err := New(s.stateDir, s.owner, s.id+"_servicing", s.spec, s.conf, containerd.IO{}) + if err != nil { + log.G(ctx).WithError(err).WithField("id", s.id).Warn("could not create servicing container") + return nil + } + defer serviceHCS.container.Close() + + err = serviceHCS.Start(ctx, true) + if err != nil { + if err := serviceHCS.Terminate(ctx); err != nil { + log.G(ctx).WithError(err).WithField("id", s.id).Errorf("failed to terminate servicing container for %s") + } + log.G(ctx).WithError(err).WithField("id", s.id).Errorf("failed to start servicing container") + return nil + } + + // wait for the container to exit + _, err = serviceHCS.ExitCode(ctx) + if err != nil { + if err := serviceHCS.Terminate(ctx); err != nil { + log.G(ctx).WithError(err).WithField("id", s.id).Errorf("failed to terminate servicing container for %s") + } + log.G(ctx).WithError(err).WithField("id", s.id).Errorf("failed to get servicing container exit code") + } + + serviceHCS.container.WaitTimeout(s.conf.TerminateDuration) + + return nil +} + +// newHCSConfiguration generates a hcsshim configuration from the instance +// OCI Spec and hcs.Configuration. +func (s *HCS) newHCSConfiguration() (*hcsshim.ContainerConfig, error) { + configuration := &hcsshim.ContainerConfig{ + SystemType: "Container", + Name: s.id, + Owner: s.owner, + HostName: s.spec.Hostname, + IgnoreFlushesDuringBoot: s.conf.IgnoreFlushesDuringBoot, + HvPartition: s.conf.UseHyperV, + AllowUnqualifiedDNSQuery: s.conf.AllowUnqualifiedDNSQuery, + EndpointList: s.conf.NetworkEndpoints, + NetworkSharedContainerName: s.conf.NetworkSharedContainerID, + Credentials: s.conf.Credentials, + } + + // TODO: use the create request Mount for those + for _, layerPath := range s.conf.Layers { + _, filename := filepath.Split(layerPath) + guid, err := hcsshim.NameToGuid(filename) + if err != nil { + return nil, err + } + configuration.Layers = append(configuration.Layers, hcsshim.Layer{ + ID: guid.ToString(), + Path: layerPath, + }) + } + + if len(s.spec.Mounts) > 0 { + mds := make([]hcsshim.MappedDir, len(s.spec.Mounts)) + for i, mount := range s.spec.Mounts { + mds[i] = hcsshim.MappedDir{ + HostPath: mount.Source, + ContainerPath: mount.Destination, + ReadOnly: false, + } + for _, o := range mount.Options { + if strings.ToLower(o) == "ro" { + mds[i].ReadOnly = true + } + } + } + configuration.MappedDirectories = mds + } + + if s.conf.DNSSearchList != nil { + configuration.DNSSearchList = strings.Join(s.conf.DNSSearchList, ",") + } + + if configuration.HvPartition { + for _, layerPath := range s.conf.Layers { + utilityVMPath := filepath.Join(layerPath, "UtilityVM") + _, err := os.Stat(utilityVMPath) + if err == nil { + configuration.HvRuntime = &hcsshim.HvRuntime{ImagePath: utilityVMPath} + break + } else if !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "failed to access layer %s", layerPath) + } + } + } + + if len(configuration.Layers) == 0 { + // TODO: support starting with 0 layers, this mean we need the "filter" directory as parameter + return nil, errors.New("at least one layers must be provided") + } + + di := hcsshim.DriverInfo{ + Flavour: 1, // filter driver + } + + if len(configuration.Layers) > 0 { + di.HomeDir = filepath.Dir(s.conf.Layers[0]) + } + + // Windows doesn't support creating a container with a readonly + // filesystem, so always create a RW one + if err := hcsshim.CreateSandboxLayer(di, s.id, s.conf.Layers[0], s.conf.Layers); err != nil { + return nil, errors.Wrapf(err, "failed to create sandbox layer for %s: layers: %#v, driverInfo: %#v", + s.id, configuration.Layers, di) + } + + configuration.LayerFolderPath = filepath.Join(di.HomeDir, s.id) + if err := ioutil.WriteFile(filepath.Join(s.stateDir, layerFile), []byte(configuration.LayerFolderPath), 0644); err != nil { + log.L.WithError(err).Warnf("failed to save active layer %s", configuration.LayerFolderPath) + } + + err := hcsshim.ActivateLayer(di, s.id) + if err != nil { + removeLayer(context.TODO(), configuration.LayerFolderPath) + return nil, errors.Wrapf(err, "failed to active layer %s", configuration.LayerFolderPath) + } + + err = hcsshim.PrepareLayer(di, s.id, s.conf.Layers) + if err != nil { + removeLayer(context.TODO(), configuration.LayerFolderPath) + return nil, errors.Wrapf(err, "failed to prepare layer %s", configuration.LayerFolderPath) + } + + volumePath, err := hcsshim.GetLayerMountPath(di, s.id) + if err != nil { + if err := hcsshim.DestroyLayer(di, s.id); err != nil { + log.L.Warnf("failed to DestroyLayer %s: %s", s.id, err) + } + return nil, errors.Wrapf(err, "failed to getmount path for layer %s: driverInfo: %#v", s.id, di) + } + configuration.VolumePath = volumePath + + f, err := os.OpenFile(fmt.Sprintf("%s-hcs.json", s.id), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 066) + if err != nil { + fmt.Println("failed to create file:", err) + } else { + defer f.Close() + enc := json.NewEncoder(f) + enc.Encode(configuration) + } + + return configuration, nil +} + +// removeLayer delete the given layer, all associated containers must have +// been shutdown for this to succeed. +func removeLayer(ctx context.Context, path string) error { + layerID := filepath.Base(path) + parentPath := filepath.Dir(path) + di := hcsshim.DriverInfo{ + Flavour: 1, // filter driver + HomeDir: parentPath, + } + + err := hcsshim.UnprepareLayer(di, layerID) + if err != nil { + log.G(ctx).WithError(err).Warnf("failed to unprepare layer %s for removal", path) + } + + err = hcsshim.DeactivateLayer(di, layerID) + if err != nil { + log.G(ctx).WithError(err).Warnf("failed to deactivate layer %s for removal", path) + } + + removePath := filepath.Join(parentPath, fmt.Sprintf("%s-removing", layerID)) + err = os.Rename(path, removePath) + if err != nil { + log.G(ctx).WithError(err).Warnf("failed to rename container layer %s for removal", path) + removePath = path + } + if err := hcsshim.DestroyLayer(di, removePath); err != nil { + log.G(ctx).WithError(err).Errorf("failed to remove container layer %s", removePath) + return err + } + + return nil +} + +// ociSpecEnvToHCSEnv converts from the OCI Spec ENV format to the one +// expected by HCS. +func ociSpecEnvToHCSEnv(a []string) map[string]string { + env := make(map[string]string) + for _, s := range a { + arr := strings.SplitN(s, "=", 2) + if len(arr) == 2 { + env[arr[0]] = arr[1] + } + } + return env +} diff --git a/windows/hcs/process.go b/windows/hcs/process.go new file mode 100644 index 0000000..940033e --- /dev/null +++ b/windows/hcs/process.go @@ -0,0 +1,50 @@ +// +build windows + +package hcs + +import ( + "syscall" + + "github.com/Microsoft/hcsshim" + "github.com/containerd/containerd" + "github.com/pkg/errors" +) + +type Process struct { + containerID string + p hcsshim.Process + status containerd.Status +} + +func (h *Process) Pid() uint32 { + return uint32(h.p.Pid()) +} + +func (h *Process) Kill() error { + return h.p.Kill() +} + +func (h *Process) ExitCode() (uint32, error) { + if err := h.p.Wait(); err != nil { + if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE { + return 255, errors.Wrapf(err, "failed to wait for container '%s' process %d", h.containerID, h.p.Pid()) + } + // container is probably dead, let's try to get its exit code + } + h.status = containerd.StoppedStatus + + ec, err := h.p.ExitCode() + if err != nil { + if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE { + return 255, errors.Wrapf(err, "failed to get container '%s' process %d exit code", h.containerID, h.p.Pid()) + } + // Well, unknown exit code it is + ec = 255 + } + + return uint32(ec), err +} + +func (h *Process) Status() containerd.Status { + return h.status +} diff --git a/windows/hcs/shimio.go b/windows/hcs/shimio.go new file mode 100644 index 0000000..2dfb5e2 --- /dev/null +++ b/windows/hcs/shimio.go @@ -0,0 +1,77 @@ +// +build windows + +package hcs + +import ( + "net" + "time" + + "github.com/Microsoft/go-winio" + "github.com/containerd/containerd" + "github.com/pkg/errors" +) + +type shimIO struct { + stdin net.Conn + stdout net.Conn + stderr net.Conn + terminal bool +} + +// newSIO connects to the provided pipes +func newSIO(io containerd.IO) (*shimIO, error) { + var ( + c net.Conn + err error + sio shimIO + ) + + defer func() { + if err != nil { + sio.Close() + } + }() + + for _, p := range []struct { + name string + open bool + conn *net.Conn + }{ + { + name: io.Stdin, + open: io.Stdin != "", + conn: &sio.stdin, + }, + { + name: io.Stdout, + open: io.Stdout != "", + conn: &sio.stdout, + }, + { + name: io.Stderr, + open: !io.Terminal && io.Stderr != "", + conn: &sio.stderr, + }, + } { + if p.open { + dialTimeout := 3 * time.Second + c, err = winio.DialPipe(p.name, &dialTimeout) + if err != nil { + return nil, errors.Wrapf(err, "failed to connect to %s", p.name) + } + *p.conn = c + } + } + + return &sio, nil +} + +// Close terminates all successfully dialed IO connections +func (s *shimIO) Close() { + for _, cn := range []net.Conn{s.stdin, s.stdout, s.stderr} { + if cn != nil { + cn.Close() + cn = nil + } + } +} diff --git a/windows/hcs/types.go b/windows/hcs/types.go new file mode 100644 index 0000000..a477476 --- /dev/null +++ b/windows/hcs/types.go @@ -0,0 +1,22 @@ +// +build windows + +package hcs + +import "time" + +type Configuration struct { + UseHyperV bool `json:"useHyperV,omitempty"` + + Layers []string `json:"layers"` + + TerminateDuration time.Duration `json:"terminateDuration",omitempty` + + IgnoreFlushesDuringBoot bool `json:"ignoreFlushesDuringBoot,omitempty"` + + AllowUnqualifiedDNSQuery bool `json:"allowUnqualifiedDNSQuery,omitempty"` + DNSSearchList []string `json:"dnsSearchList,omitempty"` + NetworkEndpoints []string `json:"networkEndpoints,omitempty"` + NetworkSharedContainerID string + + Credentials string `json:"credentials,omitempty"` +} diff --git a/windows/runtime.go b/windows/runtime.go new file mode 100644 index 0000000..138f439 --- /dev/null +++ b/windows/runtime.go @@ -0,0 +1,211 @@ +// +build windows + +package windows + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "sync" + "syscall" + "time" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/log" + "github.com/containerd/containerd/plugin" + "github.com/containerd/containerd/windows/hcs" + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + + "golang.org/x/net/context" +) + +const ( + runtimeName = "windows" + owner = "containerd" + configFilename = "config.json" +) + +// Win32 error codes that are used for various workarounds +// These really should be ALL_CAPS to match golangs syscall library and standard +// Win32 error conventions, but golint insists on CamelCase. +const ( + CoEClassstring = syscall.Errno(0x800401F3) // Invalid class string + ErrorNoNetwork = syscall.Errno(1222) // The network is not present or not started + ErrorBadPathname = syscall.Errno(161) // The specified path is invalid + ErrorInvalidObject = syscall.Errno(0x800710D8) // The object identifier does not represent a valid object +) + +func init() { + plugin.Register(runtimeName, &plugin.Registration{ + Type: plugin.RuntimePlugin, + Init: New, + }) +} + +func New(ic *plugin.InitContext) (interface{}, error) { + c, cancel := context.WithCancel(ic.Context) + + rootDir := filepath.Join(ic.Root, runtimeName) + if err := os.MkdirAll(rootDir, 0755); err != nil { + return nil, errors.Wrapf(err, "could not create state directory at %s", rootDir) + } + + // Terminate all previous container that we may have started. We don't + // support restoring containers + + ctrs, err := loadContainers(ic.Context, rootDir) + if err != nil { + return nil, err + } + + for _, c := range ctrs { + c.remove(ic.Context) + } + + // Try to delete the old state dir and recreate it + stateDir := filepath.Join(ic.State, runtimeName) + if err := os.RemoveAll(stateDir); err != nil { + log.G(c).WithError(err).Warnf("failed to cleanup old state directory at %s", stateDir) + } + if err := os.MkdirAll(stateDir, 0755); err != nil { + return nil, errors.Wrapf(err, "could not create state directory at %s", stateDir) + } + + return &Runtime{ + containers: make(map[string]*container), + containersPid: make(map[uint32]struct{}), + events: make(chan *containerd.Event, 2048), + eventsContext: c, + eventsCancel: cancel, + stateDir: stateDir, + rootDir: rootDir, + }, nil +} + +type Runtime struct { + sync.Mutex + + rootDir string + stateDir string + + containers map[string]*container + containersPid map[uint32]struct{} + currentPid uint32 + + events chan *containerd.Event + eventsContext context.Context + eventsCancel func() +} + +type RuntimeSpec struct { + // Spec is the OCI spec + OCISpec specs.Spec + + // HCS specific options + hcs.Configuration +} + +func (r *Runtime) Create(ctx context.Context, id string, opts containerd.CreateOpts) (containerd.Container, error) { + var rtSpec RuntimeSpec + if err := json.Unmarshal(opts.Spec, &rtSpec); err != nil { + return nil, errors.Wrap(err, "failed to unmarshal oci spec") + } + + pid, err := r.getPid() + if err != nil { + return nil, err + } + + ctr, err := newContainer(id, r.rootDir, pid, rtSpec, opts.IO, func(id string, evType containerd.EventType, pid, exitStatus uint32) { + r.sendEvent(id, evType, pid, exitStatus) + }) + if err != nil { + r.putPid(pid) + return nil, err + } + + r.Lock() + r.containers[id] = ctr + r.containersPid[pid] = struct{}{} + r.Unlock() + + r.sendEvent(id, containerd.CreateEvent, pid, 0) + + return ctr, nil +} + +func (r *Runtime) Delete(ctx context.Context, c containerd.Container) (uint32, error) { + wc, ok := c.(*container) + if !ok { + return 0, fmt.Errorf("container cannot be cast as *windows.container") + } + ec, err := wc.exitCode(ctx) + if err != nil { + ec = 255 + log.G(ctx).WithError(err).Errorf("failed to retrieve exit code for container %s", c.Info().ID) + } + + if err = wc.remove(ctx); err == nil { + r.Lock() + delete(r.containers, c.Info().ID) + r.Unlock() + } + + r.putPid(wc.getRuntimePid()) + + return ec, err +} + +func (r *Runtime) Containers() ([]containerd.Container, error) { + r.Lock() + list := make([]containerd.Container, len(r.containers)) + for _, c := range r.containers { + list = append(list, c) + } + r.Unlock() + + return list, nil +} + +func (r *Runtime) Events(ctx context.Context) <-chan *containerd.Event { + return r.events +} + +func (r *Runtime) sendEvent(id string, evType containerd.EventType, pid, exitStatus uint32) { + r.events <- &containerd.Event{ + Timestamp: time.Now(), + Runtime: runtimeName, + Type: evType, + Pid: pid, + ID: id, + ExitStatus: exitStatus, + } +} + +func (r *Runtime) getPid() (uint32, error) { + r.Lock() + defer r.Unlock() + + pid := r.currentPid + 1 + for pid != r.currentPid { + // 0 is reserved and invalid + if pid == 0 { + pid = 1 + } + if _, ok := r.containersPid[pid]; !ok { + r.currentPid = pid + return pid, nil + } + pid++ + } + + return 0, errors.New("pid pool exhausted") +} + +func (r *Runtime) putPid(pid uint32) { + r.Lock() + delete(r.containersPid, pid) + r.Unlock() +}