From c992b3eb2bde365d0eea001a838ee79ad560f2a2 Mon Sep 17 00:00:00 2001 From: Andrew Reed Date: Thu, 12 Oct 2017 08:12:12 -0700 Subject: [PATCH] Plugin structure Parse yaml to get specs Planners from plugins convert specs to tasks Exec routine manages concurrency Plans produce data, transform, and write files --- Gopkg.lock | 6 +- bundle/exec.go | 33 + bundle/exec_test.go | 84 + bundle/generate.go | 114 +- bundle/planner.go | 33 + bundle/task.go | 15 - metrics/docker_info.go | 88 - metrics/docker_inspect.go | 90 - metrics/docker_logs.go | 81 - metrics/docker_ps.go | 89 - metrics/hostname.go | 55 - metrics/hostname_test.go | 1 - metrics/loadavg.go | 131 -- metrics/uptime.go | 125 -- plans/byte-source.go | 108 + plans/prepared.go | 48 + plans/stream-source.go | 84 + plans/structured-source.go | 77 + plans/util.go | 27 + plans/write.go | 109 + plugins/core/core.go | 14 + plugins/core/planners/hostname.go | 18 + plugins/core/planners/loadavg.go | 77 + .../core/planners}/loadavg_test.go | 4 +- plugins/core/planners/uptime.go | 65 + .../core/planners}/uptime_test.go | 2 +- plugins/core/producers/copy-file.go | 7 + plugins/core/producers/read-command.go | 47 + plugins/core/producers/read-file.go | 14 + plugins/docker/docker.go | 21 + plugins/docker/planners/daemon.go | 39 + plugins/docker/planners/docker.go | 15 + plugins/docker/planners/logs.go | 47 + plugins/docker/producers/docker.go | 13 + plugins/docker/producers/info.go | 9 + plugins/docker/producers/inspect.go | 13 + plugins/docker/producers/logs.go | 19 + plugins/docker/producers/ps.go | 11 + plugins/docker/producers/read-file.go | 18 + plugins/docker/producers/run-command.go | 64 + spec/parse.go | 20 + systemutil/dockerReadFile.go | 87 - systemutil/dockerRunCommand.go | 152 -- systemutil/readFile.go | 63 - systemutil/readFile_test.go | 18 - systemutil/runCommand.go | 64 - systemutil/runCommand_test.go | 37 - .../generate_test.go => tests/integration.go | 4 +- try/main.go | 39 + try/spec.yml | 9 + types/data.go | 6 - types/marshallableError.go | 9 - types/plugin.go | 17 + types/result.go | 31 +- types/spec.go | 18 + types/task.go | 9 + types/timeoutError.go | 9 - vendor/github.com/Microsoft/go-winio/ea.go | 274 +-- .../github.com/Microsoft/go-winio/ea_test.go | 178 +- .../go-winio/vhd/mksyscall_windows.go | 1802 ++++++++--------- .../github.com/Microsoft/go-winio/vhd/vhd.go | 164 +- .../docker-integration/run_multiversion.sh | 0 .../docker/distribution/coverpkg.sh | 0 .../project/hooks/configure-hooks.sh | 0 .../distribution/project/hooks/pre-commit | 0 .../docker/distribution/version/version.sh | 0 .../contrib/builder/deb/aarch64/build.sh | 0 .../contrib/builder/deb/aarch64/generate.sh | 0 .../docker/contrib/builder/deb/amd64/build.sh | 0 .../contrib/builder/deb/amd64/generate.sh | 0 .../contrib/builder/deb/armhf/generate.sh | 0 .../contrib/builder/deb/ppc64le/build.sh | 0 .../contrib/builder/deb/ppc64le/generate.sh | 0 .../docker/contrib/builder/deb/s390x/build.sh | 0 .../contrib/builder/deb/s390x/generate.sh | 0 .../docker/contrib/builder/rpm/amd64/build.sh | 0 .../contrib/builder/rpm/amd64/generate.sh | 0 .../docker/docker/contrib/check-config.sh | 0 .../docker/docker/contrib/dockerize-disk.sh | 0 .../contrib/download-frozen-image-v1.sh | 0 .../contrib/download-frozen-image-v2.sh | 0 .../docker/contrib/gitdm/generate_aliases.sh | 0 .../contrib/init/sysvinit-debian/docker | 0 .../contrib/init/sysvinit-redhat/docker | 0 .../docker/contrib/mac-install-bundle.sh | 0 .../docker/docker/contrib/mkimage-alpine.sh | 0 .../docker/docker/contrib/mkimage-arch.sh | 0 .../docker/docker/contrib/mkimage-busybox.sh | 0 .../docker/docker/contrib/mkimage-crux.sh | 0 .../docker/contrib/mkimage-debootstrap.sh | 0 .../docker/docker/contrib/mkimage-pld.sh | 0 .../docker/docker/contrib/mkimage-rinse.sh | 0 .../docker/docker/contrib/mkimage-yum.sh | 0 .../docker/docker/contrib/mkimage.sh | 0 .../contrib/mkimage/.febootstrap-minimize | 0 .../docker/contrib/mkimage/busybox-static | 0 .../docker/docker/contrib/mkimage/debootstrap | 0 .../docker/contrib/mkimage/mageia-urpmi | 0 .../docker/docker/contrib/mkimage/rinse | 0 .../docker/docker/contrib/mkimage/solaris | 0 .../docker/contrib/nuke-graph-directory.sh | 0 .../docker/docker/contrib/project-stats.sh | 0 .../docker/docker/contrib/report-issue.sh | 0 .../docker/docker/contrib/reprepro/suites.sh | 0 vendor/github.com/docker/docker/hack/dind | 0 .../docker/hack/dockerfile/binaries-commits | 0 .../hack/dockerfile/install-binaries.sh | 0 .../docker/docker/hack/generate-authors.sh | 0 .../docker/hack/generate-swagger-api.sh | 0 vendor/github.com/docker/docker/hack/make.sh | 0 .../.build-deb/docker-engine.docker.default | 0 .../make/.build-deb/docker-engine.docker.init | 0 .../.build-deb/docker-engine.docker.upstart | 0 .../hack/make/.build-deb/docker-engine.udev | 0 .../docker/docker/hack/make/.build-deb/rules | 0 .../docker/docker/hack/make/clean-apt-repo | 0 .../docker/docker/hack/make/clean-yum-repo | 0 .../docker/hack/make/generate-index-listing | 0 .../docker/docker/hack/make/release-deb | 0 .../docker/docker/hack/make/release-rpm | 0 .../docker/docker/hack/make/sign-repos | 0 .../docker/docker/hack/make/test-deb-install | 0 .../docker/hack/make/test-install-script | 0 .../docker/hack/make/test-integration-cli | 0 .../docker/docker/hack/make/test-old-apt-repo | 0 .../docker/docker/hack/make/update-apt-repo | 0 .../github.com/docker/docker/hack/release.sh | 0 .../docker/docker/hack/validate/all | 0 .../docker/hack/validate/compose-bindata | 0 .../docker/docker/hack/validate/dco | 0 .../docker/docker/hack/validate/default | 0 .../docker/hack/validate/default-seccomp | 0 .../docker/docker/hack/validate/gofmt | 0 .../docker/docker/hack/validate/lint | 0 .../docker/docker/hack/validate/pkg-imports | 0 .../docker/docker/hack/validate/swagger | 0 .../docker/docker/hack/validate/swagger-gen | 0 .../docker/docker/hack/validate/test-imports | 0 .../docker/docker/hack/validate/toml | 0 .../docker/docker/hack/validate/vendor | 0 .../docker/docker/hack/validate/vet | 0 .../github.com/docker/docker/hack/vendor.sh | 0 .../auth/docker-credential-shell-test | 0 .../integration-cli/fixtures/notary/gen.sh | 0 .../github.com/docker/docker/man/generate.sh | 0 .../docker/docker/man/md2man-all.sh | 0 .../docker/profiles/seccomp/default.json | 0 .../profiles/seccomp/fixtures/example.json | 0 .../docker/docker/project/CONTRIBUTORS.md | 0 .../docker/runconfig/opts/fixtures/utf16.env | Bin .../runconfig/opts/fixtures/utf16be.env | Bin .../docker/runconfig/opts/fixtures/utf8.env | 4 +- 152 files changed, 2493 insertions(+), 2436 deletions(-) create mode 100644 bundle/exec.go create mode 100644 bundle/exec_test.go create mode 100644 bundle/planner.go delete mode 100644 bundle/task.go delete mode 100644 metrics/docker_info.go delete mode 100644 metrics/docker_inspect.go delete mode 100644 metrics/docker_logs.go delete mode 100644 metrics/docker_ps.go delete mode 100644 metrics/hostname.go delete mode 100644 metrics/hostname_test.go delete mode 100644 metrics/loadavg.go delete mode 100644 metrics/uptime.go create mode 100644 plans/byte-source.go create mode 100644 plans/prepared.go create mode 100644 plans/stream-source.go create mode 100644 plans/structured-source.go create mode 100644 plans/util.go create mode 100644 plans/write.go create mode 100644 plugins/core/core.go create mode 100644 plugins/core/planners/hostname.go create mode 100644 plugins/core/planners/loadavg.go rename {metrics => plugins/core/planners}/loadavg_test.go (86%) create mode 100644 plugins/core/planners/uptime.go rename {metrics => plugins/core/planners}/uptime_test.go (96%) create mode 100644 plugins/core/producers/copy-file.go create mode 100644 plugins/core/producers/read-command.go create mode 100644 plugins/core/producers/read-file.go create mode 100644 plugins/docker/docker.go create mode 100644 plugins/docker/planners/daemon.go create mode 100644 plugins/docker/planners/docker.go create mode 100644 plugins/docker/planners/logs.go create mode 100644 plugins/docker/producers/docker.go create mode 100644 plugins/docker/producers/info.go create mode 100644 plugins/docker/producers/inspect.go create mode 100644 plugins/docker/producers/logs.go create mode 100644 plugins/docker/producers/ps.go create mode 100644 plugins/docker/producers/read-file.go create mode 100644 plugins/docker/producers/run-command.go create mode 100644 spec/parse.go delete mode 100644 systemutil/dockerReadFile.go delete mode 100644 systemutil/dockerRunCommand.go delete mode 100644 systemutil/readFile.go delete mode 100644 systemutil/readFile_test.go delete mode 100644 systemutil/runCommand.go delete mode 100644 systemutil/runCommand_test.go rename bundle/generate_test.go => tests/integration.go (98%) create mode 100644 try/main.go create mode 100644 try/spec.yml delete mode 100644 types/data.go delete mode 100644 types/marshallableError.go create mode 100644 types/plugin.go create mode 100644 types/spec.go create mode 100644 types/task.go delete mode 100644 types/timeoutError.go mode change 100644 => 100755 vendor/github.com/docker/distribution/contrib/docker-integration/run_multiversion.sh mode change 100644 => 100755 vendor/github.com/docker/distribution/coverpkg.sh mode change 100644 => 100755 vendor/github.com/docker/distribution/project/hooks/configure-hooks.sh mode change 100644 => 100755 vendor/github.com/docker/distribution/project/hooks/pre-commit mode change 100644 => 100755 vendor/github.com/docker/distribution/version/version.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/builder/deb/aarch64/build.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/builder/deb/aarch64/generate.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/builder/deb/amd64/build.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/builder/deb/amd64/generate.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/builder/deb/armhf/generate.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/builder/deb/ppc64le/build.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/builder/deb/ppc64le/generate.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/builder/deb/s390x/build.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/builder/deb/s390x/generate.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/builder/rpm/amd64/build.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/builder/rpm/amd64/generate.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/check-config.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/dockerize-disk.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/download-frozen-image-v1.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/download-frozen-image-v2.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/gitdm/generate_aliases.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/init/sysvinit-debian/docker mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/init/sysvinit-redhat/docker mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/mac-install-bundle.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/mkimage-alpine.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/mkimage-arch.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/mkimage-busybox.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/mkimage-crux.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/mkimage-debootstrap.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/mkimage-pld.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/mkimage-rinse.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/mkimage-yum.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/mkimage.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/mkimage/.febootstrap-minimize mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/mkimage/busybox-static mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/mkimage/debootstrap mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/mkimage/mageia-urpmi mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/mkimage/rinse mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/mkimage/solaris mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/nuke-graph-directory.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/project-stats.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/report-issue.sh mode change 100644 => 100755 vendor/github.com/docker/docker/contrib/reprepro/suites.sh mode change 100644 => 100755 vendor/github.com/docker/docker/hack/dind mode change 100644 => 100755 vendor/github.com/docker/docker/hack/dockerfile/binaries-commits mode change 100644 => 100755 vendor/github.com/docker/docker/hack/dockerfile/install-binaries.sh mode change 100644 => 100755 vendor/github.com/docker/docker/hack/generate-authors.sh mode change 100644 => 100755 vendor/github.com/docker/docker/hack/generate-swagger-api.sh mode change 100644 => 100755 vendor/github.com/docker/docker/hack/make.sh mode change 100644 => 120000 vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.default mode change 100644 => 120000 vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.init mode change 100644 => 120000 vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.upstart mode change 100644 => 120000 vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.udev mode change 100644 => 100755 vendor/github.com/docker/docker/hack/make/.build-deb/rules mode change 100644 => 100755 vendor/github.com/docker/docker/hack/make/clean-apt-repo mode change 100644 => 100755 vendor/github.com/docker/docker/hack/make/clean-yum-repo mode change 100644 => 100755 vendor/github.com/docker/docker/hack/make/generate-index-listing mode change 100644 => 100755 vendor/github.com/docker/docker/hack/make/release-deb mode change 100644 => 100755 vendor/github.com/docker/docker/hack/make/release-rpm mode change 100644 => 100755 vendor/github.com/docker/docker/hack/make/sign-repos mode change 100644 => 100755 vendor/github.com/docker/docker/hack/make/test-deb-install mode change 100644 => 100755 vendor/github.com/docker/docker/hack/make/test-install-script mode change 100644 => 100755 vendor/github.com/docker/docker/hack/make/test-integration-cli mode change 100644 => 100755 vendor/github.com/docker/docker/hack/make/test-old-apt-repo mode change 100644 => 100755 vendor/github.com/docker/docker/hack/make/update-apt-repo mode change 100644 => 100755 vendor/github.com/docker/docker/hack/release.sh mode change 100644 => 100755 vendor/github.com/docker/docker/hack/validate/all mode change 100644 => 100755 vendor/github.com/docker/docker/hack/validate/compose-bindata mode change 100644 => 100755 vendor/github.com/docker/docker/hack/validate/dco mode change 100644 => 100755 vendor/github.com/docker/docker/hack/validate/default mode change 100644 => 100755 vendor/github.com/docker/docker/hack/validate/default-seccomp mode change 100644 => 100755 vendor/github.com/docker/docker/hack/validate/gofmt mode change 100644 => 100755 vendor/github.com/docker/docker/hack/validate/lint mode change 100644 => 100755 vendor/github.com/docker/docker/hack/validate/pkg-imports mode change 100644 => 100755 vendor/github.com/docker/docker/hack/validate/swagger mode change 100644 => 100755 vendor/github.com/docker/docker/hack/validate/swagger-gen mode change 100644 => 100755 vendor/github.com/docker/docker/hack/validate/test-imports mode change 100644 => 100755 vendor/github.com/docker/docker/hack/validate/toml mode change 100644 => 100755 vendor/github.com/docker/docker/hack/validate/vendor mode change 100644 => 100755 vendor/github.com/docker/docker/hack/validate/vet mode change 100644 => 100755 vendor/github.com/docker/docker/hack/vendor.sh mode change 100644 => 100755 vendor/github.com/docker/docker/integration-cli/fixtures/auth/docker-credential-shell-test mode change 100644 => 100755 vendor/github.com/docker/docker/integration-cli/fixtures/notary/gen.sh mode change 100644 => 100755 vendor/github.com/docker/docker/man/generate.sh mode change 100644 => 100755 vendor/github.com/docker/docker/man/md2man-all.sh mode change 100644 => 100755 vendor/github.com/docker/docker/profiles/seccomp/default.json mode change 100644 => 100755 vendor/github.com/docker/docker/profiles/seccomp/fixtures/example.json mode change 100644 => 120000 vendor/github.com/docker/docker/project/CONTRIBUTORS.md mode change 100644 => 100755 vendor/github.com/docker/docker/runconfig/opts/fixtures/utf16.env mode change 100644 => 100755 vendor/github.com/docker/docker/runconfig/opts/fixtures/utf16be.env mode change 100644 => 100755 vendor/github.com/docker/docker/runconfig/opts/fixtures/utf8.env diff --git a/Gopkg.lock b/Gopkg.lock index 5288a3fb0..e36af6b30 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -16,7 +16,7 @@ [[projects]] branch = "master" name = "github.com/divolgin/archiver" - packages = ["compressor"] + packages = ["compressor","extractor"] revision = "d039d69aa3bc83a08569adf43b2a1c40866f4774" [[projects]] @@ -27,7 +27,7 @@ [[projects]] name = "github.com/docker/docker" - packages = ["api/types","api/types/blkiodev","api/types/container","api/types/events","api/types/filters","api/types/mount","api/types/network","api/types/reference","api/types/registry","api/types/strslice","api/types/swarm","api/types/time","api/types/versions","api/types/volume","client","pkg/tlsconfig"] + packages = ["api/types","api/types/blkiodev","api/types/container","api/types/events","api/types/filters","api/types/mount","api/types/network","api/types/reference","api/types/registry","api/types/strslice","api/types/swarm","api/types/time","api/types/versions","api/types/volume","client","pkg/stdcopy","pkg/tlsconfig"] revision = "092cba3727bb9b4a2f0e922cd6c0f93ea270e363" version = "v1.13.1" @@ -172,6 +172,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "07c1215290753ff1d888421c2a911c14c65efcb20c3156fd35ca781c75d11af0" + inputs-digest = "ececcb1982fe380f28854d39074eb3af15adc73733e99c4010b06cd456b83917" solver-name = "gps-cdcl" solver-version = 1 diff --git a/bundle/exec.go b/bundle/exec.go new file mode 100644 index 000000000..88cbd3521 --- /dev/null +++ b/bundle/exec.go @@ -0,0 +1,33 @@ +package bundle + +import ( + "context" + + "github.com/replicatedcom/support-bundle/types" +) + +func Exec(ctx context.Context, rootDir string, tasks []types.Task) []*types.Result { + results := make(chan []*types.Result) + + for _, task := range tasks { + go func(task types.Task) { + results <- task.Exec(ctx, rootDir) + }(task) + } + + pending := len(tasks) + var accm []*types.Result + + for { + select { + case r := <-results: + accm = append(accm, r...) + pending-- + if pending == 0 { + return accm + } + case <-ctx.Done(): + return accm + } + } +} diff --git a/bundle/exec_test.go b/bundle/exec_test.go new file mode 100644 index 000000000..54c45d132 --- /dev/null +++ b/bundle/exec_test.go @@ -0,0 +1,84 @@ +package bundle + +import ( + "context" + "testing" + "time" + + "github.com/pkg/errors" + "github.com/replicatedcom/support-bundle/types" + "github.com/stretchr/testify/assert" +) + +type taskStub struct { + elapse time.Duration + results []*types.Result +} + +func (t taskStub) Exec(ctx context.Context, rootDir string) []*types.Result { + time.Sleep(t.elapse) + return t.results +} + +func TestExec(t *testing.T) { + nilResults := taskStub{ + elapse: time.Nanosecond, + results: nil, + } + noResults := taskStub{ + elapse: time.Nanosecond, + results: []*types.Result{}, + } + singleResults := taskStub{ + elapse: time.Nanosecond, + results: []*types.Result{ + { + Description: "Logs from db container", + Path: "/docker/db.logs", + }, + }, + } + mixedResults := taskStub{ + elapse: time.Nanosecond, + results: []*types.Result{ + { + Description: "Stderr from app container", + Path: "/docker/app/stderr.txt", + }, + { + Description: "Stdout from app container", + Error: errors.New("Docker API 500"), + }, + { + Description: "Logs from app container", + Path: "/docker/app.logs", + Error: errors.New("Timedout"), + }, + }, + } + slowResults := taskStub{ + elapse: time.Second, + results: []*types.Result{ + { + Description: "/usr/bin/free", + Path: "/host/commands/free", + }, + }, + } + + ctx, _ := context.WithTimeout(context.Background(), 50*time.Millisecond) + results := Exec(ctx, "/dir", []types.Task{ + nilResults, + noResults, + singleResults, + mixedResults, + slowResults, + }) + + assert.Len(t, results, 4) + assert.Contains(t, results, singleResults.results[0]) + assert.Contains(t, results, mixedResults.results[0]) + assert.Contains(t, results, mixedResults.results[1]) + assert.Contains(t, results, mixedResults.results[2]) + assert.NotContains(t, results, slowResults.results[0]) +} diff --git a/bundle/generate.go b/bundle/generate.go index c7167362a..baf5eda6a 100644 --- a/bundle/generate.go +++ b/bundle/generate.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "os" "path/filepath" - "sync" "time" "github.com/pkg/errors" @@ -16,121 +15,52 @@ import ( jww "github.com/spf13/jwalterweatherman" ) -type resultInfo struct { - Paths []string `json:"paths"` - Task string `json:"task"` - Args []string `json:"arguments"` -} - -type errorInfo struct { - Task string `json:"task"` - Args []string `json:"arguments"` - Error string `json:"error"` -} - -// Generate is called to start a new support bundle generation -func Generate(tasks []Task, timeout time.Duration) (string, error) { - var wg sync.WaitGroup - - var resultMutex = &sync.Mutex{} - var allResultInfo []resultInfo - var allErrorInfo []errorInfo - - wg.Add(len(tasks)) - - collectDir, err := ioutil.TempDir("", "support-bundle") +// Generate a new support bundle and write the results as an archive at pathname +func Generate(tasks []types.Task, timeout time.Duration, pathname string) error { + collectDir, err := ioutil.TempDir(filepath.Dir(pathname), "") if err != nil { err = errors.Wrap(err, "Creating a temporary directory to store results failed") - jww.ERROR.Print(err) - return "", err + return err } defer os.RemoveAll(collectDir) - ctx := context.Background() - defaultCtx, cancel := context.WithTimeout(ctx, timeout) + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - for _, task := range tasks { - go func(task Task) { - var datas []types.Data - var result types.Result - var err error - - if task.Timeout == 0 { - // use the default context for this task - datas, result, err = task.ExecFunc(defaultCtx, task.Args) - } else { - // use a unique context+timeout for this task - uniqueCtx, cancel := context.WithTimeout(ctx, task.Timeout) - defer cancel() - datas, result, err = task.ExecFunc(uniqueCtx, task.Args) - } + results := Exec(ctx, collectDir, tasks) - if err != nil { - jww.ERROR.Printf(err.Error() + "\n") - } + // any result that wrote a file, whether it has an error or not + var resultsWithOutput []*types.Result + // any result with an error, whether or not it wrote a file + var resultsWithError []*types.Result - for _, data := range datas { - dataFile := filepath.Join(collectDir, data.Filename) - if err := os.MkdirAll(filepath.Dir(dataFile), 0700); err != nil { - jww.ERROR.Print(err) - } - if err := ioutil.WriteFile(dataFile, data.Data, 0666); err != nil { - jww.ERROR.Print(err) - } - } - - resultMutex.Lock() - allResultInfo = append(allResultInfo, resultInfo{ - Paths: result.Filenames, - Task: result.Task, - Args: result.Args, - }) - if result.Error != nil { - allErrorInfo = append(allErrorInfo, errorInfo{ - Task: result.Task, - Args: result.Args, - Error: result.Error.Error(), - }) - } - resultMutex.Unlock() - - wg.Done() - }(task) + for _, r := range results { + if r.Path != "" { + resultsWithOutput = append(resultsWithOutput, r) + } + if r.Error != nil { + resultsWithError = append(resultsWithError, r) + } } - wg.Wait() - //write index and error json files - indexJSON, err := json.MarshalIndent(allResultInfo, "", " ") + indexJSON, err := json.MarshalIndent(resultsWithOutput, "", " ") if err != nil { jww.ERROR.Print(err) } ioutil.WriteFile(filepath.Join(collectDir, "index.json"), indexJSON, 0666) - errorJSON, err := json.MarshalIndent(allErrorInfo, "", " ") + errorJSON, err := json.MarshalIndent(resultsWithError, "", " ") if err != nil { jww.ERROR.Print(err) } ioutil.WriteFile(filepath.Join(collectDir, "error.json"), errorJSON, 0666) - // Build the output tar file - archiveFile, err := ioutil.TempFile("", "support-bundle") - if err != nil { - err = errors.Wrap(err, "Creating a temporary file to compress results failed") - jww.ERROR.Print(err) - return "", err - } - comp := compressor.NewTgz() comp.SetTarConfig(compressor.Tar{TruncateLongFiles: true}) - if err := comp.Compress(collectDir, archiveFile.Name()); err != nil { - err = errors.Wrap(err, "Compressing results directory failed") - jww.ERROR.Print(err) - return "", err + if err := comp.Compress(collectDir, pathname); err != nil { + return errors.Wrap(err, "Compressing results directory failed") } - jww.TRACE.Printf("Created support bundle at %q\n", archiveFile.Name()) - - return archiveFile.Name(), nil + return nil } diff --git a/bundle/planner.go b/bundle/planner.go new file mode 100644 index 000000000..0649c2af2 --- /dev/null +++ b/bundle/planner.go @@ -0,0 +1,33 @@ +package bundle + +import ( + "strings" + + "github.com/replicatedcom/support-bundle/types" +) + +type Planner struct { + Plugins map[string]types.Plugin +} + +func (p Planner) Plan(specs []types.Spec) []types.Task { + var tasks []types.Task + + for _, spec := range specs { + parts := strings.Split(spec.Builtin, ".") + if len(parts) != 2 { + continue + } + plugin, ok := p.Plugins[parts[0]] + if !ok { + continue + } + planner, ok := plugin[parts[1]] + if !ok { + continue + } + tasks = append(tasks, planner(spec)...) + } + + return tasks +} diff --git a/bundle/task.go b/bundle/task.go deleted file mode 100644 index 3fba931b0..000000000 --- a/bundle/task.go +++ /dev/null @@ -1,15 +0,0 @@ -package bundle - -import ( - "context" - "time" - - "github.com/replicatedcom/support-bundle/types" -) - -type Task struct { - Description string - ExecFunc func(context.Context, []string) ([]types.Data, types.Result, error) - Timeout time.Duration - Args []string -} diff --git a/metrics/docker_info.go b/metrics/docker_info.go deleted file mode 100644 index 5cf2388db..000000000 --- a/metrics/docker_info.go +++ /dev/null @@ -1,88 +0,0 @@ -package metrics - -import ( - "context" - "encoding/json" - "fmt" - "path/filepath" - - "github.com/replicatedcom/support-bundle/types" - - jww "github.com/spf13/jwalterweatherman" - - "github.com/docker/docker/client" -) - -func DockerInfo(ctx context.Context, args []string) ([]types.Data, types.Result, error) { - filename := "/docker/metrics/info" - - var err error - - var datas []types.Data - var paths []string - - completeChan := make(chan error, 1) - - go func() { - cli, err := client.NewEnvClient() - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - info, err := cli.Info(ctx) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - infoJSON, err := json.Marshal(info) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - // Send the json - datas = append(datas, types.Data{ - Filename: filepath.Join("/json/", filename+".json"), - Data: infoJSON, - }) - paths = append(paths, filepath.Join("/json/", filename+".json")) - - infoIndentJSON, err := json.MarshalIndent(info, "", " ") - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - // Human readable version - datas = append(datas, types.Data{ - Filename: filepath.Join("/human/", filename+".json"), - Data: infoIndentJSON, - }) - paths = append(paths, filepath.Join("/human/", filename+".json")) - - completeChan <- nil - }() - - select { - case err = <-completeChan: - //completed on time - case <-ctx.Done(): - //failed to complete on time - err = types.TimeoutError{Message: fmt.Sprintf(`Docker info failed due to: %s`, ctx.Err().Error())} - } - - result := types.Result{ - Task: "dockerInfo", - Args: args, - Filenames: paths, - Error: err, - } - - return datas, result, err -} diff --git a/metrics/docker_inspect.go b/metrics/docker_inspect.go deleted file mode 100644 index 71560880d..000000000 --- a/metrics/docker_inspect.go +++ /dev/null @@ -1,90 +0,0 @@ -package metrics - -import ( - "context" - "encoding/json" - "fmt" - "path/filepath" - - "github.com/replicatedcom/support-bundle/types" - - jww "github.com/spf13/jwalterweatherman" - - "github.com/docker/docker/client" -) - -// DockerInspect - inspects a given docker container -// args[0] should be container id -func DockerInspect(ctx context.Context, args []string) ([]types.Data, types.Result, error) { - filename := "/docker/inspect/" + args[0] - - var err error - - var datas []types.Data - var paths []string - - completeChan := make(chan error, 1) - - go func() { - cli, err := client.NewEnvClient() - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - container, err := cli.ContainerInspect(ctx, args[0]) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - containerJSON, err := json.Marshal(container) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - // Send the json - datas = append(datas, types.Data{ - Filename: filepath.Join("/json/", filename+".json"), - Data: containerJSON, - }) - paths = append(paths, filepath.Join("/json/", filename+".json")) - - containerIndentJSON, err := json.MarshalIndent(container, "", " ") - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - // Human readable version - datas = append(datas, types.Data{ - Filename: filepath.Join("/human/", filename+".json"), - Data: containerIndentJSON, - }) - paths = append(paths, filepath.Join("/human/", filename+".json")) - - completeChan <- nil - }() - - select { - case err = <-completeChan: - //completed on time - case <-ctx.Done(): - //failed to complete on time - err = types.TimeoutError{Message: fmt.Sprintf(`Inspecting host:%s failed due to: %s`, args[0], ctx.Err().Error())} - } - - result := types.Result{ - Task: "dockerInspect", - Args: args, - Filenames: paths, - Error: err, - } - - return datas, result, err -} diff --git a/metrics/docker_logs.go b/metrics/docker_logs.go deleted file mode 100644 index 29cd12549..000000000 --- a/metrics/docker_logs.go +++ /dev/null @@ -1,81 +0,0 @@ -package metrics - -import ( - "context" - "fmt" - "io/ioutil" - "path/filepath" - - "github.com/replicatedcom/support-bundle/types" - - jww "github.com/spf13/jwalterweatherman" - - dockertypes "github.com/docker/docker/api/types" - "github.com/docker/docker/client" -) - -// DockerLogs - reads the logs of a given docker container -// args[0] should be container id -func DockerLogs(ctx context.Context, args []string) ([]types.Data, types.Result, error) { - filename := "/docker/logs/" + args[0] - - var err error - - var datas []types.Data - var paths []string - - completeChan := make(chan error, 1) - - go func() { - cli, err := client.NewEnvClient() - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - options := dockertypes.ContainerLogsOptions{ - ShowStdout: true, - ShowStderr: true, - } - logsReader, err := cli.ContainerLogs(ctx, args[0], options) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - logs, err := ioutil.ReadAll(logsReader) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - // Send the raw - datas = append(datas, types.Data{ - Filename: filepath.Join("/raw/", filename), - Data: logs, - }) - paths = append(paths, filepath.Join("/raw/", filename)) - - completeChan <- nil - }() - - select { - case err = <-completeChan: - //completed on time - case <-ctx.Done(): - //failed to complete on time - err = types.TimeoutError{Message: fmt.Sprintf(`Getting logs from host:%s failed due to: %s`, args[0], ctx.Err().Error())} - } - - result := types.Result{ - Task: "dockerLogs", - Args: args, - Filenames: paths, - Error: err, - } - - return datas, result, err -} diff --git a/metrics/docker_ps.go b/metrics/docker_ps.go deleted file mode 100644 index f4582b51e..000000000 --- a/metrics/docker_ps.go +++ /dev/null @@ -1,89 +0,0 @@ -package metrics - -import ( - "context" - "encoding/json" - "fmt" - "path/filepath" - - "github.com/replicatedcom/support-bundle/types" - - jww "github.com/spf13/jwalterweatherman" - - dockertypes "github.com/docker/docker/api/types" - "github.com/docker/docker/client" -) - -func Dockerps(ctx context.Context, args []string) ([]types.Data, types.Result, error) { - filename := "/docker/metrics/ps" - - var err error - - var datas []types.Data - var paths []string - - completeChan := make(chan error, 1) - - go func() { - cli, err := client.NewEnvClient() - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - containers, err := cli.ContainerList(ctx, dockertypes.ContainerListOptions{}) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - containersJSON, err := json.Marshal(containers) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - // Send the json - datas = append(datas, types.Data{ - Filename: filepath.Join("/json/", filename+".json"), - Data: containersJSON, - }) - paths = append(paths, filepath.Join("/json/", filename+".json")) - - containerIndentJSON, err := json.MarshalIndent(containers, "", " ") - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - // Human readable version - datas = append(datas, types.Data{ - Filename: filepath.Join("/human/", filename+".json"), - Data: containerIndentJSON, - }) - paths = append(paths, filepath.Join("/human/", filename+".json")) - - completeChan <- nil - }() - - select { - case err = <-completeChan: - //completed on time - case <-ctx.Done(): - //failed to complete on time - err = types.TimeoutError{Message: fmt.Sprintf(`Docker ps failed due to: %s`, ctx.Err().Error())} - } - - result := types.Result{ - Task: "dockerps", - Args: args, - Filenames: paths, - Error: err, - } - - return datas, result, err -} diff --git a/metrics/hostname.go b/metrics/hostname.go deleted file mode 100644 index 9f8f2547c..000000000 --- a/metrics/hostname.go +++ /dev/null @@ -1,55 +0,0 @@ -package metrics - -import ( - "context" - "fmt" - "log" - "os/exec" - "path/filepath" - - "github.com/replicatedcom/support-bundle/types" -) - -func Hostname(ctx context.Context, args []string) ([]types.Data, types.Result, error) { - filename := "/system/metrics/hostname" - - var err error - - var datas []types.Data - var paths []string - - completeChan := make(chan error, 1) - - go func() { - b, err := exec.Command("hostname").Output() - if err != nil { - log.Fatal(err) - } - - // Send the raw - datas = append(datas, types.Data{ - Filename: filepath.Join("/raw/", filename), - Data: b, - }) - paths = append(paths, filepath.Join("/raw/", filename)) - - completeChan <- nil - }() - - select { - case err = <-completeChan: - //completed on time - case <-ctx.Done(): - //failed to complete on time - err = types.TimeoutError{Message: fmt.Sprintf(`Fetching hostname failed due to: %s`, ctx.Err().Error())} - } - - result := types.Result{ - Task: "hostname", - Args: args, - Filenames: paths, - Error: err, - } - - return datas, result, err -} diff --git a/metrics/hostname_test.go b/metrics/hostname_test.go deleted file mode 100644 index 123de304e..000000000 --- a/metrics/hostname_test.go +++ /dev/null @@ -1 +0,0 @@ -package metrics \ No newline at end of file diff --git a/metrics/loadavg.go b/metrics/loadavg.go deleted file mode 100644 index a8887f2c9..000000000 --- a/metrics/loadavg.go +++ /dev/null @@ -1,131 +0,0 @@ -package metrics - -import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - "path/filepath" - "strconv" - "strings" - - "github.com/replicatedcom/support-bundle/types" - - jww "github.com/spf13/jwalterweatherman" -) - -type LoadAverage struct { - minuteOne float64 - minuteFive float64 - minuteTen float64 - processCountRunning int - processCountTotal int -} - -func LoadAvg(ctx context.Context, args []string) ([]types.Data, types.Result, error) { - filename := "/system/metrics/loadavg" - - var err error - - var datas []types.Data - var paths []string - - completeChan := make(chan error, 1) - - go func() { - b, err := ioutil.ReadFile("/proc/loadavg") - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - // Send the raw - datas = append(datas, types.Data{ - Filename: filepath.Join("/raw/", filename), - Data: b, - }) - paths = append(paths, filepath.Join("/raw/", filename)) - - loadAverage, err := parseLoadAvg(b) - if err != nil { - completeChan <- err - return - } - - human := fmt.Sprintf("%f %f %f", loadAverage.minuteOne, loadAverage.minuteFive, loadAverage.minuteTen) - // Convert to human readable - datas = append(datas, types.Data{ - Filename: filepath.Join("/human/", filename+".txt"), - Data: []byte(human), - }) - paths = append(paths, filepath.Join("/human/", filename+".txt")) - - j, err := json.Marshal(loadAverage) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - datas = append(datas, types.Data{ - Filename: filepath.Join("/json/", filename+".json"), - Data: j, - }) - paths = append(paths, filepath.Join("/json/", filename+".json")) - - completeChan <- nil - }() - - select { - case err = <-completeChan: - //completed on time - case <-ctx.Done(): - //failed to complete on time - err = types.TimeoutError{Message: fmt.Sprintf(`Fetching load averages failed due to: %s`, ctx.Err().Error())} - } - - result := types.Result{ - Task: "loadavg", - Args: args, - Filenames: paths, - Error: err, - } - - return datas, result, err -} - -func parseLoadAvg(contents []byte) (*LoadAverage, error) { - - // # cat /proc/loadavg - // 0.02 0.01 0.00 4/229 5 - - parts := strings.Split(string(contents), " ") - if len(parts) != 5 { - err := fmt.Errorf("Expected 5 values in loadavg but found %d", len(parts)) - jww.ERROR.Print(err) - return nil, err - } - - oneMin, err := strconv.ParseFloat(parts[0], 64) - if err != nil { - jww.ERROR.Print(err) - return nil, err - } - fiveMin, err := strconv.ParseFloat(parts[1], 64) - if err != nil { - jww.ERROR.Print(err) - return nil, err - } - tenMin, err := strconv.ParseFloat(parts[2], 64) - if err != nil { - jww.ERROR.Print(err) - return nil, err - } - - return &LoadAverage{ - minuteOne: oneMin, - minuteFive: fiveMin, - minuteTen: tenMin, - }, nil -} diff --git a/metrics/uptime.go b/metrics/uptime.go deleted file mode 100644 index 71f772a3e..000000000 --- a/metrics/uptime.go +++ /dev/null @@ -1,125 +0,0 @@ -package metrics - -import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - "path/filepath" - "strconv" - "strings" - - "github.com/replicatedcom/support-bundle/types" - - jww "github.com/spf13/jwalterweatherman" -) - -func Uptime(ctx context.Context, args []string) ([]types.Data, types.Result, error) { - filename := "/system/metrics/uptime" - - var err error - - var datas []types.Data - var paths []string - - completeChan := make(chan error, 1) - - go func() { - b, err := ioutil.ReadFile("/proc/uptime") - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - // Send the raw - datas = append(datas, types.Data{ - Filename: filepath.Join("/raw/", filename), - Data: b, - }) - paths = append(paths, filepath.Join("/raw/", filename)) - - uptimeSeconds, err := parseUptime(b) - if err != nil { - completeChan <- err - return - } - - human := fmt.Sprintf("Total Time (seconds): %f\nIdle Time (seconds): %f", uptimeSeconds[0], uptimeSeconds[1]) - // Convert to human readable - datas = append(datas, types.Data{ - Filename: filepath.Join("/human/", filename+".txt"), - Data: []byte(human), - }) - paths = append(paths, filepath.Join("/human/", filename+".txt")) - - type uptime struct { - TotalSeconds float64 `json:"total_seconds"` - IdleSeconds float64 `json:"idle_seconds"` - } - u := uptime{ - TotalSeconds: uptimeSeconds[0], - IdleSeconds: uptimeSeconds[1], - } - j, err := json.Marshal(u) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - datas = append(datas, types.Data{ - Filename: filepath.Join("/json/", filename+".json"), - Data: j, - }) - paths = append(paths, filepath.Join("/json/", filename+".json")) - - completeChan <- nil - }() - - select { - case err = <-completeChan: - //completed on time - case <-ctx.Done(): - //failed to complete on time - err = types.TimeoutError{Message: fmt.Sprintf(`Fetching uptime failed due to: %s`, ctx.Err().Error())} - } - - result := types.Result{ - Task: "uptime", - Args: args, - Filenames: paths, - Error: err, - } - - return datas, result, err -} - -func parseUptime(contents []byte) ([]float64, error) { - - // # cat /proc/uptime - // 33524.72 66785.42 - - parts := strings.Split(strings.TrimSpace(string(contents)), " ") - if len(parts) != 2 { - err := fmt.Errorf("Expected 2 values in uptime but found %d", len(parts)) - jww.ERROR.Print(err) - return nil, err - } - - totalSeconds, err := strconv.ParseFloat(parts[0], 64) - if err != nil { - jww.ERROR.Print(err) - return nil, err - } - idleSeconds, err := strconv.ParseFloat(parts[1], 64) - if err != nil { - jww.ERROR.Print(err) - return nil, err - } - - return []float64{ - totalSeconds, - idleSeconds, - }, nil -} diff --git a/plans/byte-source.go b/plans/byte-source.go new file mode 100644 index 000000000..06ba16d09 --- /dev/null +++ b/plans/byte-source.go @@ -0,0 +1,108 @@ +package plans + +import ( + "context" + "errors" + + "github.com/replicatedcom/support-bundle/types" +) + +// ByteSource is a Task that gets its data source as a []byte. +type ByteSource struct { + // Producer provides the seed data for this task + Producer func(context.Context) ([]byte, error) + // Parser, if defined, structures the raw data for json and human sinks + Parser func([]byte) (interface{}, error) + // Template, if defined, renders structured data in a human-readable format + Template string + // If RawPath is defined it will get a copy of the raw data []byte + RawPath string + // If JSONPath is defined and Parser is defined, it will get a jsonified + // copy of the strucutred data. If JSONPath is defined and Parser is not, + // it will get a copy of the raw data. + JSONPath string + // If HumanPath is defined and Parser and Template are defined, it will get + // the output of the template rendered with the structure context. If + // HumanPath and Parser are defined but Template is not, it will get a YAML + // copy of the structured data. If HumanPath is defined and Parser is not, + // it will get a copy of the raw data. + HumanPath string +} + +func (task *ByteSource) Exec(ctx context.Context, rootDir string) []*types.Result { + parser := task.Parser != nil + + raw := task.RawPath != "" + + jsonify := task.JSONPath != "" + jsonParsed := jsonify && parser + jsonRaw := jsonify && !jsonParsed + + human := task.HumanPath != "" + humanTemplated := human && parser && task.Template != "" + humanYAML := human && parser && !humanTemplated + humanRaw := human && !humanTemplated && !humanYAML + + rawResult := &types.Result{} + jsonResult := &types.Result{} + humanResult := &types.Result{} + results := []*types.Result{} + + if raw { + results = append(results, rawResult) + } + if jsonify { + results = append(results, jsonResult) + } + if human { + results = append(results, humanResult) + } + + if task.Producer == nil { + err := errors.New("no data source defined for task") + return resultsWithErr(err, results) + } + + data, err := task.Producer(ctx) + if err != nil { + return resultsWithErr(err, results) + } + + if raw { + write(rootDir, task.RawPath, data, rawResult) + } + + var structured interface{} + if parser { + structured, err = task.Parser(data) + if err != nil { + jsonResult.Error = err + + if humanTemplated || humanYAML { + humanResult.Error = err + // nothing left to do + return results + } + } else { + writeJSON(rootDir, task.JSONPath, structured, jsonResult) + } + } + + if jsonRaw { + write(rootDir, task.JSONPath, data, jsonResult) + } + + if humanTemplated { + writeTemplate(rootDir, task.HumanPath, task.Template, structured, humanResult) + } + + if humanYAML { + writeYAML(rootDir, task.HumanPath, structured, humanResult) + } + + if humanRaw { + write(rootDir, task.HumanPath, data, humanResult) + } + + return results +} diff --git a/plans/prepared.go b/plans/prepared.go new file mode 100644 index 000000000..96ba4bd44 --- /dev/null +++ b/plans/prepared.go @@ -0,0 +1,48 @@ +package plans + +import ( + "context" + "path/filepath" + + "github.com/replicatedcom/support-bundle/types" +) + +// Prepared is a Task that returns preconfigured Results. +type Prepared struct { + results []*types.Result +} + +func (p Prepared) Exec(ctx context.Context, rootDir string) []*types.Result { + for _, r := range p.results { + if r.Path != "" { + r.Path = filepath.Join(rootDir, r.Path) + } + } + + return p.results +} + +// Prepare results for an incomplete spec +func PreparedError(err error, spec types.Spec) Prepared { + results := []*types.Result{} + + if spec.Raw != "" { + results = append(results, &types.Result{ + Error: err, + }) + } + if spec.JSON != "" { + results = append(results, &types.Result{ + Error: err, + }) + } + if spec.Human != "" { + results = append(results, &types.Result{ + Error: err, + }) + } + + return Prepared{ + results: results, + } +} diff --git a/plans/stream-source.go b/plans/stream-source.go new file mode 100644 index 000000000..80d94c2fe --- /dev/null +++ b/plans/stream-source.go @@ -0,0 +1,84 @@ +package plans + +import ( + "context" + "errors" + "io" + "os" + + "github.com/replicatedcom/support-bundle/types" +) + +// StreamSource is a Task that gets its data as an io.Reader +type StreamSource struct { + // Producer provides the seed data for this task as an io.Reader + Producer func(context.Context) (io.Reader, error) + // Template, if defined, renders structured data in a human-readable format + Template string + // If RawPath is defined it will get a copy of the data + RawPath string + // If JSONPath is defined it will get a copy of the data + JSONPath string + // If HumanPath is defined it will get a copy of the data + HumanPath string +} + +func (task *StreamSource) Exec(ctx context.Context, rootDir string) []*types.Result { + raw := task.RawPath != "" + jsonify := task.JSONPath != "" + human := task.HumanPath != "" + + rawResult := &types.Result{} + jsonResult := &types.Result{} + humanResult := &types.Result{} + + results := []*types.Result{} + if raw { + results = append(results, rawResult) + } + if jsonify { + results = append(results, jsonResult) + } + if human { + results = append(results, humanResult) + } + + if task.Producer == nil { + err := errors.New("no data source defined for task") + return resultsWithErr(err, results) + } + + if !(raw || jsonify || human) { + return results + } + + data, err := task.Producer(ctx) + if err != nil { + return resultsWithErr(err, results) + } + if closer, ok := data.(io.Closer); ok { + defer closeLogErr(closer) + } + + // first write to one file + if raw { + ioCopyContext(ctx, rootDir, task.RawPath, data, rawResult) + } else if jsonify { + ioCopyContext(ctx, rootDir, task.JSONPath, data, jsonResult) + } else if human { + ioCopyContext(ctx, rootDir, task.HumanPath, data, humanResult) + } + + // then link to any other requested paths + if raw && jsonify { + os.Link(task.RawPath, task.HumanPath) + } + if raw && human { + os.Link(task.RawPath, task.HumanPath) + } + if jsonify && human { + os.Link(task.JSONPath, task.HumanPath) + } + + return results +} diff --git a/plans/structured-source.go b/plans/structured-source.go new file mode 100644 index 000000000..1bae94d1e --- /dev/null +++ b/plans/structured-source.go @@ -0,0 +1,77 @@ +package plans + +import ( + "context" + "errors" + + "github.com/replicatedcom/support-bundle/types" +) + +// StructuredSource is a Task that gets its data as a structured object ready to +// be jsonified or used as a context in a human template. +type StructuredSource struct { + // Producer provides the seed data for this task + Producer func(context.Context) (interface{}, error) + // Template, if defined, renders the task's data in a human-readable format + Template string + // RawPath, if defined, gets the tasks's data as json. + RawPath string + // JSONPath, if defined, gets a jsonified copy of the task's data. + JSONPath string + // If HumanPath is defined and a Template is defined, it will get the + // output of the template rendered with the task's data as context. If + // HumanPath is defined and a Template is not, it will get the data as + // YAML. + HumanPath string +} + +func (task *StructuredSource) Exec(ctx context.Context, rootDir string) []*types.Result { + raw := task.RawPath != "" + jsonify := task.JSONPath != "" + human := task.HumanPath != "" + humanTemplated := human && task.Template != "" + humanYAML := human && !humanTemplated + + rawResult := &types.Result{} + jsonResult := &types.Result{} + humanResult := &types.Result{} + results := []*types.Result{} + + if raw { + results = append(results, rawResult) + } + if jsonify { + results = append(results, jsonResult) + } + if human { + results = append(results, humanResult) + } + + if task.Producer == nil { + err := errors.New("no data source defined for task") + return resultsWithErr(err, results) + } + + data, err := task.Producer(ctx) + if err != nil { + return resultsWithErr(err, results) + } + + if jsonify { + writeJSON(rootDir, task.JSONPath, data, jsonResult) + } + + if raw { + writeJSON(rootDir, task.RawPath, data, rawResult) + } + + if humanTemplated { + writeTemplate(rootDir, task.HumanPath, task.Template, data, humanResult) + } + + if humanYAML { + writeYAML(rootDir, task.HumanPath, data, humanResult) + } + + return results +} diff --git a/plans/util.go b/plans/util.go new file mode 100644 index 000000000..626aaae30 --- /dev/null +++ b/plans/util.go @@ -0,0 +1,27 @@ +package plans + +import ( + "io" + + "github.com/replicatedcom/support-bundle/types" + jww "github.com/spf13/jwalterweatherman" +) + +// add an error to every result, returning the results argument +// skips results that already have an error +func resultsWithErr(err error, results []*types.Result) []*types.Result { + for _, r := range results { + if r.Error == nil { + r.Error = err + } + } + + return results +} + +// closseLogErr +func closeLogErr(c io.Closer) { + if err := c.Close(); err != nil { + jww.ERROR.Print(err) + } +} diff --git a/plans/write.go b/plans/write.go new file mode 100644 index 000000000..bfff538e0 --- /dev/null +++ b/plans/write.go @@ -0,0 +1,109 @@ +package plans + +import ( + "context" + "encoding/json" + "html/template" + "io" + "io/ioutil" + "os" + "path/filepath" + + yaml "gopkg.in/yaml.v2" + + "github.com/replicatedcom/support-bundle/types" +) + +func write(rootDir string, path string, data []byte, result *types.Result) { + dest := filepath.Join(rootDir, path) + if err := os.MkdirAll(filepath.Dir(dest), 0744); err != nil { + result.Error = err + return + } + if err := ioutil.WriteFile(dest, data, 0644); err != nil { + result.Error = err + } else { + result.Path = path + } +} + +func writeJSON(rootDir string, path string, data interface{}, result *types.Result) { + jsonPath := filepath.Join(rootDir, path) + if err := os.MkdirAll(filepath.Dir(jsonPath), 0744); err != nil { + result.Error = err + return + } + marshaled, err := json.Marshal(data) + if err != nil { + result.Error = err + return + } + if err := ioutil.WriteFile(jsonPath, marshaled, 0644); err != nil { + result.Error = err + return + } + result.Path = path +} + +func writeTemplate(rootDir string, path string, tmpl string, data interface{}, result *types.Result) { + dest := filepath.Join(rootDir, path) + if err := os.MkdirAll(filepath.Dir(dest), 0744); err != nil { + result.Error = err + return + } + t, err := template.New("template").Parse(tmpl) + if err != nil { + result.Error = err + return + } + f, err := os.Create(dest) + if err != nil { + result.Error = err + return + } + defer closeLogErr(f) + if err := t.Execute(f, data); err != nil { + result.Error = err + return + } + result.Path = path +} + +func writeYAML(rootDir string, path string, data interface{}, result *types.Result) { + marshaled, err := yaml.Marshal(data) + if err != nil { + result.Error = err + return + } + dest := filepath.Join(rootDir, path) + if err := os.MkdirAll(filepath.Dir(dest), 0744); err != nil { + result.Error = err + return + } + if err := ioutil.WriteFile(dest, marshaled, 0644); err != nil { + result.Error = err + return + } + result.Path = path +} + +// TODO context interruptible +func ioCopyContext(ctx context.Context, rootDir string, path string, data io.Reader, result *types.Result) { + dest := filepath.Join(rootDir, path) + if err := os.MkdirAll(filepath.Dir(dest), 0744); err != nil { + result.Error = err + return + } + f, err := os.Create(dest) + if err != nil { + result.Error = err + return + } + n, err := io.Copy(f, data) + if err != nil { + result.Error = err + } + if n != 0 { + result.Path = path + } +} diff --git a/plugins/core/core.go b/plugins/core/core.go new file mode 100644 index 000000000..5b6bdd686 --- /dev/null +++ b/plugins/core/core.go @@ -0,0 +1,14 @@ +package core + +import ( + "github.com/replicatedcom/support-bundle/plugins/core/planners" + "github.com/replicatedcom/support-bundle/types" +) + +func New() types.Plugin { + return map[string]types.Planner{ + "loadavg": planners.PlanLoadAverage, + "hostname": planners.Hostname, + "uptime": planners.Uptime, + } +} diff --git a/plugins/core/planners/hostname.go b/plugins/core/planners/hostname.go new file mode 100644 index 000000000..5a7750acb --- /dev/null +++ b/plugins/core/planners/hostname.go @@ -0,0 +1,18 @@ +package planners + +import ( + "github.com/replicatedcom/support-bundle/plans" + "github.com/replicatedcom/support-bundle/plugins/core/producers" + "github.com/replicatedcom/support-bundle/types" +) + +func Hostname(spec types.Spec) []types.Task { + task := &plans.ByteSource{ + Producer: producers.ReadCommand("hostname"), + RawPath: spec.Raw, + JSONPath: spec.JSON, + HumanPath: spec.Human, + } + + return []types.Task{task} +} diff --git a/plugins/core/planners/loadavg.go b/plugins/core/planners/loadavg.go new file mode 100644 index 000000000..f9743dfdc --- /dev/null +++ b/plugins/core/planners/loadavg.go @@ -0,0 +1,77 @@ +package planners + +import ( + "fmt" + "strconv" + "strings" + + "github.com/replicatedcom/support-bundle/plans" + "github.com/replicatedcom/support-bundle/plugins/core/producers" + "github.com/replicatedcom/support-bundle/types" + + jww "github.com/spf13/jwalterweatherman" +) + +type LoadAverage struct { + MinuteOne float64 + MinuteFive float64 + MinuteTen float64 + ProcessCountRunning int + ProcessCountTotal int +} + +const loadAverageTemplate = ` + 1 Minute: {{ .MinuteOne }} + 5 Minute: {{ .MinuteFive }} +10 Minute: {{ .MinuteTen }} + +{{ with .ProcessCountRunning }}Running Processes: {{ .}}{{ end }} +{{ with .ProcessCountTotal }}Total Processes: {{ . }}{{ end }}` + +func PlanLoadAverage(spec types.Spec) []types.Task { + task := &plans.ByteSource{ + Producer: producers.ReadFile("/proc/loadavg"), + Template: loadAverageTemplate, + Parser: parseLoadAvg, + RawPath: spec.Raw, + JSONPath: spec.JSON, + HumanPath: spec.Human, + } + + return []types.Task{task} +} + +func parseLoadAvg(contents []byte) (interface{}, error) { + + // # cat /proc/loadavg + // 0.02 0.01 0.00 4/229 5 + + parts := strings.Split(string(contents), " ") + if len(parts) != 5 { + err := fmt.Errorf("Expected 5 values in loadavg but found %d", len(parts)) + jww.ERROR.Print(err) + return nil, err + } + + oneMin, err := strconv.ParseFloat(parts[0], 64) + if err != nil { + jww.ERROR.Print(err) + return nil, err + } + fiveMin, err := strconv.ParseFloat(parts[1], 64) + if err != nil { + jww.ERROR.Print(err) + return nil, err + } + tenMin, err := strconv.ParseFloat(parts[2], 64) + if err != nil { + jww.ERROR.Print(err) + return nil, err + } + + return &LoadAverage{ + MinuteOne: oneMin, + MinuteFive: fiveMin, + MinuteTen: tenMin, + }, nil +} diff --git a/metrics/loadavg_test.go b/plugins/core/planners/loadavg_test.go similarity index 86% rename from metrics/loadavg_test.go rename to plugins/core/planners/loadavg_test.go index 48332a02b..f6662a9e0 100644 --- a/metrics/loadavg_test.go +++ b/plugins/core/planners/loadavg_test.go @@ -1,4 +1,4 @@ -package metrics +package planners import ( "testing" @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestParseLoadAverage(t *testing.T) { +func TestParseLoadAvg(t *testing.T) { loadAvgValues, err := parseLoadAvg([]byte("0.02 0.01 0.00 4/229 5")) require.NoError(t, err) assert.Equal(t, loadAvgValues.minuteOne, float64(0.02)) diff --git a/plugins/core/planners/uptime.go b/plugins/core/planners/uptime.go new file mode 100644 index 000000000..3efe2458b --- /dev/null +++ b/plugins/core/planners/uptime.go @@ -0,0 +1,65 @@ +package planners + +import ( + "fmt" + "strconv" + "strings" + + "github.com/replicatedcom/support-bundle/plans" + "github.com/replicatedcom/support-bundle/plugins/core/producers" + "github.com/replicatedcom/support-bundle/types" + + jww "github.com/spf13/jwalterweatherman" +) + +type uptime struct { + TotalSeconds float64 `json:"total_seconds"` + IdleSeconds float64 `json:"idle_seconds"` +} + +const uptimeTmpl = ` +Total Time (seconds): {{ .TotalSeconds }} +Idle Time (seconds): {{ .IdleSeconds }}` + +func Uptime(spec types.Spec) []types.Task { + task := &plans.ByteSource{ + Producer: producers.ReadFile("/proc/uptime"), + Parser: parseUptime, + Template: uptimeTmpl, + + RawPath: spec.Raw, + JSONPath: spec.JSON, + HumanPath: spec.Human, + } + + return []types.Task{task} +} + +func parseUptime(contents []byte) (interface{}, error) { + + // # cat /proc/uptime + // 33524.72 66785.42 + + parts := strings.Split(strings.TrimSpace(string(contents)), " ") + if len(parts) != 2 { + err := fmt.Errorf("Expected 2 values in uptime but found %d", len(parts)) + jww.ERROR.Print(err) + return nil, err + } + + totalSeconds, err := strconv.ParseFloat(parts[0], 64) + if err != nil { + jww.ERROR.Print(err) + return nil, err + } + idleSeconds, err := strconv.ParseFloat(parts[1], 64) + if err != nil { + jww.ERROR.Print(err) + return nil, err + } + + return uptime{ + TotalSeconds: totalSeconds, + IdleSeconds: idleSeconds, + }, nil +} diff --git a/metrics/uptime_test.go b/plugins/core/planners/uptime_test.go similarity index 96% rename from metrics/uptime_test.go rename to plugins/core/planners/uptime_test.go index 7674e4048..4bc0346e6 100644 --- a/metrics/uptime_test.go +++ b/plugins/core/planners/uptime_test.go @@ -1,4 +1,4 @@ -package metrics +package planners import ( "testing" diff --git a/plugins/core/producers/copy-file.go b/plugins/core/producers/copy-file.go new file mode 100644 index 000000000..8170130b9 --- /dev/null +++ b/plugins/core/producers/copy-file.go @@ -0,0 +1,7 @@ +package producers + +import "os" + +func CopyFile(dest, src string) error { + return os.Link(src, dest) +} diff --git a/plugins/core/producers/read-command.go b/plugins/core/producers/read-command.go new file mode 100644 index 000000000..c30678d7e --- /dev/null +++ b/plugins/core/producers/read-command.go @@ -0,0 +1,47 @@ +package producers + +import ( + "context" + "os/exec" + "strings" + + "github.com/replicatedcom/support-bundle/types" +) + +func ReadCommand(command string, args ...string) types.BytesProducer { + return func(ctx context.Context) ([]byte, error) { + return exec.CommandContext(ctx, command, strings.Join(args, " ")).Output() + } +} + +/* + stdoutDest := filepath.Join(rootDir, cr.StdoutDest) + stdout, err := os.Create(stdoutDest) + if err != nil { + result.Error = err + } else { + cmd.Stdout = stdout + } + + var stderr bytes.Buffer + cmd.Stderr = &stderr + + if err := cmd.Start(); err != nil { + result.Error = err + return []types.Result{result} + } + + if err := cmd.Wait(); err != nil { + result.Error = err + return []types.Result{result} + } + result.Pathname = stdoutDest + + errOut := stderr.String() + if errOut != "" { + result.Error = errors.New(errOut) + } + + return []types.Result{result} +} +*/ diff --git a/plugins/core/producers/read-file.go b/plugins/core/producers/read-file.go new file mode 100644 index 000000000..7876e5868 --- /dev/null +++ b/plugins/core/producers/read-file.go @@ -0,0 +1,14 @@ +package producers + +import ( + "context" + "io/ioutil" + + "github.com/replicatedcom/support-bundle/types" +) + +func ReadFile(src string) types.BytesProducer { + return func(ctx context.Context) ([]byte, error) { + return ioutil.ReadFile(src) + } +} diff --git a/plugins/docker/docker.go b/plugins/docker/docker.go new file mode 100644 index 000000000..98cec575c --- /dev/null +++ b/plugins/docker/docker.go @@ -0,0 +1,21 @@ +package docker + +import ( + "github.com/docker/docker/client" + "github.com/replicatedcom/support-bundle/plugins/docker/planners" + "github.com/replicatedcom/support-bundle/plugins/docker/producers" + "github.com/replicatedcom/support-bundle/types" +) + +func New() (types.Plugin, error) { + c, err := client.NewEnvClient() + if err != nil { + return nil, err + } + producers := producers.New(c) + docker := planners.New(producers) + return map[string]types.Planner{ + "daemon": docker.Daemon, + "logs": docker.Logs, + }, nil +} diff --git a/plugins/docker/planners/daemon.go b/plugins/docker/planners/daemon.go new file mode 100644 index 000000000..2fcf2fadf --- /dev/null +++ b/plugins/docker/planners/daemon.go @@ -0,0 +1,39 @@ +package planners + +import ( + "path/filepath" + + "github.com/replicatedcom/support-bundle/plans" + "github.com/replicatedcom/support-bundle/types" +) + +// path returns "" if dir is empty, otherwise returns the joined pathnme +func maybePath(dir, filename string) string { + if dir == "" { + return "" + } + return filepath.Join(dir, filename) +} + +// Daemon generates tasks to collect general information from Docker. The paths +// in the spec are interpreted as directories. +func (d *Docker) Daemon(spec types.Spec) []types.Task { + info := &plans.StructuredSource{ + Producer: d.producers.Info, + RawPath: maybePath(spec.Raw, "docker_info"), + JSONPath: maybePath(spec.JSON, "docker_info.json"), + HumanPath: maybePath(spec.Human, "docker_info"), + } + + ps := &plans.StructuredSource{ + Producer: d.producers.PSAll, + RawPath: maybePath(spec.Raw, "docker_ps_all"), + JSONPath: maybePath(spec.JSON, "docker_ps_all.json"), + HumanPath: maybePath(spec.Human, "docker_ps_all"), + } + + return []types.Task{ + info, + ps, + } +} diff --git a/plugins/docker/planners/docker.go b/plugins/docker/planners/docker.go new file mode 100644 index 000000000..1950c92c8 --- /dev/null +++ b/plugins/docker/planners/docker.go @@ -0,0 +1,15 @@ +package planners + +import ( + "github.com/replicatedcom/support-bundle/plugins/docker/producers" +) + +type Docker struct { + producers *producers.Docker +} + +func New(docker *producers.Docker) *Docker { + return &Docker{ + producers: docker, + } +} diff --git a/plugins/docker/planners/logs.go b/plugins/docker/planners/logs.go new file mode 100644 index 000000000..86da48fd9 --- /dev/null +++ b/plugins/docker/planners/logs.go @@ -0,0 +1,47 @@ +package planners + +import ( + "errors" + + "github.com/replicatedcom/support-bundle/plans" + "github.com/replicatedcom/support-bundle/types" +) + +func parseContainerConfig(src interface{}) types.ContainerConfig { + config := types.ContainerConfig{} + + m, ok := src.(map[interface{}]interface{}) + if !ok { + return config + } + for k, v := range m { + if key, ok := k.(string); ok { + switch key { + case "container_id": + if val, ok := v.(string); ok { + config.ContainerID = val + } + } + } + } + return config +} + +func (d *Docker) Logs(spec types.Spec) []types.Task { + config := parseContainerConfig(spec.Config) + if config.ContainerID == "" { + err := errors.New("spec requires container config") + task := plans.PreparedError(err, spec) + + return []types.Task{task} + } + + task := &plans.StreamSource{ + Producer: d.producers.Logs(config.ContainerID), + RawPath: spec.Raw, + JSONPath: spec.JSON, + HumanPath: spec.Human, + } + + return []types.Task{task} +} diff --git a/plugins/docker/producers/docker.go b/plugins/docker/producers/docker.go new file mode 100644 index 000000000..ec4c2c480 --- /dev/null +++ b/plugins/docker/producers/docker.go @@ -0,0 +1,13 @@ +package producers + +import ( + docker "github.com/docker/docker/client" +) + +type Docker struct { + client *docker.Client +} + +func New(client *docker.Client) *Docker { + return &Docker{client} +} diff --git a/plugins/docker/producers/info.go b/plugins/docker/producers/info.go new file mode 100644 index 000000000..7a4921221 --- /dev/null +++ b/plugins/docker/producers/info.go @@ -0,0 +1,9 @@ +package producers + +import ( + "context" +) + +func (d *Docker) Info(ctx context.Context) (interface{}, error) { + return d.client.Info(ctx) +} diff --git a/plugins/docker/producers/inspect.go b/plugins/docker/producers/inspect.go new file mode 100644 index 000000000..cc0d593c3 --- /dev/null +++ b/plugins/docker/producers/inspect.go @@ -0,0 +1,13 @@ +package producers + +import ( + "context" + + "github.com/replicatedcom/support-bundle/types" +) + +func (d *Docker) Inspect(containerID string) types.StructuredProducer { + return func(ctx context.Context) (interface{}, error) { + return d.client.ContainerInspect(ctx, containerID) + } +} diff --git a/plugins/docker/producers/logs.go b/plugins/docker/producers/logs.go new file mode 100644 index 000000000..75a7d98a6 --- /dev/null +++ b/plugins/docker/producers/logs.go @@ -0,0 +1,19 @@ +package producers + +import ( + "context" + "io" + + dockertypes "github.com/docker/docker/api/types" + "github.com/replicatedcom/support-bundle/types" +) + +func (d *Docker) Logs(containerID string) types.StreamProducer { + return func(ctx context.Context) (io.Reader, error) { + options := dockertypes.ContainerLogsOptions{ + ShowStdout: true, + ShowStderr: true, + } + return d.client.ContainerLogs(ctx, containerID, options) + } +} diff --git a/plugins/docker/producers/ps.go b/plugins/docker/producers/ps.go new file mode 100644 index 000000000..826922ab3 --- /dev/null +++ b/plugins/docker/producers/ps.go @@ -0,0 +1,11 @@ +package producers + +import ( + "context" + + dockertypes "github.com/docker/docker/api/types" +) + +func (d *Docker) PSAll(ctx context.Context) (interface{}, error) { + return d.client.ContainerList(ctx, dockertypes.ContainerListOptions{All: true}) +} diff --git a/plugins/docker/producers/read-file.go b/plugins/docker/producers/read-file.go new file mode 100644 index 000000000..8a0d09a53 --- /dev/null +++ b/plugins/docker/producers/read-file.go @@ -0,0 +1,18 @@ +package producers + +import ( + "context" + "io" + + "github.com/replicatedcom/support-bundle/types" +) + +func (d *Docker) ReadFile(containerID string, path string) types.StreamProducer { + return func(ctx context.Context) (io.Reader, error) { + readcloser, _, err := d.client.CopyFromContainer(ctx, containerID, path) + if err != nil { + return nil, err + } + return readcloser, err + } +} diff --git a/plugins/docker/producers/run-command.go b/plugins/docker/producers/run-command.go new file mode 100644 index 000000000..3e49b66bf --- /dev/null +++ b/plugins/docker/producers/run-command.go @@ -0,0 +1,64 @@ +package producers + +import ( + "context" + "io" + "log" + + dockertypes "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/stdcopy" + "github.com/replicatedcom/support-bundle/types" +) + +// RunCommand returns stdout and stderr results. +func (d *Docker) RunCommand(containerID string, cmd []string) types.StreamsProducer { + return func(ctx context.Context) (io.Reader, io.Reader, error) { + execOpts := dockertypes.ExecConfig{ + Cmd: cmd, + AttachStderr: true, + AttachStdout: true, + AttachStdin: false, + } + + execInstance, err := d.client.ContainerExecCreate(ctx, containerID, execOpts) + if err != nil { + return nil, nil, err + } + + att, err := d.client.ContainerExecAttach(ctx, execInstance.ID, execOpts) + if err != nil { + return nil, nil, err + } + + execStartOpts := dockertypes.ExecStartCheck{ + Detach: false, + Tty: false, + } + if err := d.client.ContainerExecStart(ctx, execInstance.ID, execStartOpts); err != nil { + att.Close() + return nil, nil, err + } + + stdoutR, stdoutW := io.Pipe() + stderrR, stderrW := io.Pipe() + + go func() { + // TODO context interruptible + _, err = stdcopy.StdCopy(stdoutW, stderrW, att.Reader) + att.Close() + if err != nil { + stdoutW.CloseWithError(err) + stderrW.CloseWithError(err) + return + } + if err := stdoutW.Close(); err != nil { + log.Print(err) + } + if err := stderrW.Close(); err != nil { + log.Print(err) + } + }() + + return stdoutR, stderrR, nil + } +} diff --git a/spec/parse.go b/spec/parse.go new file mode 100644 index 000000000..970cf28cf --- /dev/null +++ b/spec/parse.go @@ -0,0 +1,20 @@ +package spec + +import ( + "github.com/pkg/errors" + "github.com/replicatedcom/support-bundle/types" + yaml "gopkg.in/yaml.v2" +) + +type Doc struct { + Specs []types.Spec +} + +func Parse(doc []byte) ([]types.Spec, error) { + d := &Doc{} + if err := yaml.Unmarshal(doc, d); err != nil { + return nil, errors.Wrap(err, "parse yaml spec") + } + + return d.Specs, nil +} diff --git a/systemutil/dockerReadFile.go b/systemutil/dockerReadFile.go deleted file mode 100644 index db9991c97..000000000 --- a/systemutil/dockerReadFile.go +++ /dev/null @@ -1,87 +0,0 @@ -package systemutil - -import ( - "context" - "fmt" - "io/ioutil" - "path/filepath" - "regexp" - - "github.com/replicatedcom/support-bundle/types" - - jww "github.com/spf13/jwalterweatherman" - - "github.com/docker/docker/client" -) - -// DockerReadFile Read a file from a docker instance -// args: ["id", "path_to_file"] -func DockerReadFile(ctx context.Context, args []string) ([]types.Data, types.Result, error) { - filename := "/docker/readfile/" - - r, _ := regexp.Compile(`[^\w]`) - - for _, arg := range args { - filename += r.ReplaceAllString(arg, "_") - } - - var err error - - var datas []types.Data - var paths []string - - completeChan := make(chan error, 1) - - go func() { - cli, err := client.NewEnvClient() - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - readcloser, _, err := cli.CopyFromContainer(ctx, args[0], args[1]) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - // read everything - response, err := ioutil.ReadAll(readcloser) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - //close connection - readcloser.Close() - - // Send the raw - datas = append(datas, types.Data{ - Filename: filepath.Join("/raw/", filename+".tar"), - Data: response, - }) - paths = append(paths, filepath.Join("/raw/", filename+".tar")) - - completeChan <- nil - }() - - select { - case err = <-completeChan: - //completed on time - case <-ctx.Done(): - //failed to complete on time - err = types.TimeoutError{Message: fmt.Sprintf(`Reading a docker file at from host:%s and path:%s errored out with %s`, args[0], args[1], ctx.Err().Error())} - } - - result := types.Result{ - Task: "dockerReadFile", - Args: args, - Filenames: paths, - Error: err, - } - - return datas, result, err -} diff --git a/systemutil/dockerRunCommand.go b/systemutil/dockerRunCommand.go deleted file mode 100644 index 8002f8432..000000000 --- a/systemutil/dockerRunCommand.go +++ /dev/null @@ -1,152 +0,0 @@ -package systemutil - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "path/filepath" - "regexp" - - "github.com/replicatedcom/support-bundle/types" - - jww "github.com/spf13/jwalterweatherman" - - dockertypes "github.com/docker/docker/api/types" - "github.com/docker/docker/client" - "github.com/docker/docker/pkg/stdcopy" -) - -// DockerRunCommand Run a command on a specified docker instance. -// args: ["id", "user", "cmd", "arg1", "arg2"...] -func DockerRunCommand(ctx context.Context, args []string) ([]types.Data, types.Result, error) { - filename := "/docker/runcommand/" - commandString := "" - r, _ := regexp.Compile(`[^\w]`) - - for _, arg := range args { - commandString += arg + "_" - } - commandString = commandString[:len(commandString)-1] - - filename += r.ReplaceAllString(commandString, "_") - - var err error - - var datas []types.Data - var paths []string - - completeChan := make(chan error, 1) - - go func() { - cli, err := client.NewEnvClient() - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - execOpts := dockertypes.ExecConfig{ - User: args[1], - Cmd: args[2:], - AttachStderr: true, - AttachStdout: true, - AttachStdin: true, - } - - execInstance, err := cli.ContainerExecCreate(ctx, args[0], execOpts) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - att, err := cli.ContainerExecAttach(ctx, execInstance.ID, execOpts) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - execStartOpts := dockertypes.ExecStartCheck{ - Detach: false, - Tty: false, - } - err = cli.ContainerExecStart(ctx, execInstance.ID, execStartOpts) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - var dstdout, dstderr bytes.Buffer - - //read and demultiplex - _, err = stdcopy.StdCopy(&dstdout, &dstderr, att.Reader) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - // close connection - att.Close() - - // get stdout and stderr byte arrays - stdoutResult := dstdout.Bytes() - stderrResult := dstderr.Bytes() - - // Send the raw result - datas = append(datas, types.Data{ - Filename: filepath.Join("/raw/", filename+".out.txt"), - Data: stdoutResult, - }) - paths = append(paths, filepath.Join("/raw/", filename+".out.txt")) - datas = append(datas, types.Data{ - Filename: filepath.Join("/raw/", filename+".err.txt"), - Data: stderrResult, - }) - paths = append(paths, filepath.Join("/raw/", filename+".err.txt")) - - type runCommandStruct struct { - Out string `json:"stdout"` - Err string `json:"stderr"` - } - u := runCommandStruct{ - Out: string(stdoutResult), - Err: string(stderrResult), - } - j, err := json.Marshal(u) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - // Send the json - datas = append(datas, types.Data{ - Filename: filepath.Join("/json/", filename+".json"), - Data: j, - }) - paths = append(paths, filepath.Join("/json/", filename+".json")) - completeChan <- nil - }() - - select { - case err = <-completeChan: - //completed on time - case <-ctx.Done(): - //failed to complete on time - err = types.TimeoutError{Message: fmt.Sprintf(`Command "%s" failed due to: %s`, commandString, ctx.Err().Error())} - // err := errors.Wrap(ctx.Err(), `Command "`+commandString+`" failed`) //would be nice to use but doesn't convert to json - } - - results := types.Result{ - Task: "dockerRunCommand", - Args: args, - Filenames: paths, - Error: err, - } - - return datas, results, err -} diff --git a/systemutil/readFile.go b/systemutil/readFile.go deleted file mode 100644 index 04fcbd266..000000000 --- a/systemutil/readFile.go +++ /dev/null @@ -1,63 +0,0 @@ -package systemutil - -import ( - "context" - "fmt" - "io/ioutil" - "path/filepath" - "regexp" - - "github.com/replicatedcom/support-bundle/types" - - jww "github.com/spf13/jwalterweatherman" -) - -func ReadFile(ctx context.Context, args []string) ([]types.Data, types.Result, error) { - readFile := args[0] - - // make a sanatized version of the filename we're searching for - replace forward slash, backslash colon and space with _ - r, _ := regexp.Compile(`[^\w]`) - filename := "/system/readfile/" + r.ReplaceAllString(readFile, "_") - - var err error - - var datas []types.Data - var paths []string - - completeChan := make(chan error, 1) - - go func() { - b, err := ioutil.ReadFile(readFile) - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - // Send the raw - datas = append(datas, types.Data{ - Filename: filepath.Join("/raw/", filename), - Data: b, - }) - paths = append(paths, filepath.Join("/raw/", filename)) - - completeChan <- nil - }() - - select { - case err = <-completeChan: - //completed on time - case <-ctx.Done(): - //failed to complete on time - err = types.TimeoutError{Message: fmt.Sprintf("Reading file at %s errored out due to %s", args[0], ctx.Err().Error())} - } - - result := types.Result{ - Task: "readFile", - Args: args, - Filenames: paths, - Error: err, - } - - return datas, result, err -} diff --git a/systemutil/readFile_test.go b/systemutil/readFile_test.go deleted file mode 100644 index c7fba0850..000000000 --- a/systemutil/readFile_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package systemutil - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestReadFile(t *testing.T) { - - commandStrings := []string{"readFile_test.go"} - - datas, result, err := ReadFile(context.Background(), commandStrings) - require.NoError(t, err) - require.Equal(t, 1, len(datas), "Expected 1 data struct to be returned") - require.NoError(t, result.Error) -} diff --git a/systemutil/runCommand.go b/systemutil/runCommand.go deleted file mode 100644 index c71b11bee..000000000 --- a/systemutil/runCommand.go +++ /dev/null @@ -1,64 +0,0 @@ -package systemutil - -import ( - "context" - "fmt" - "os/exec" - "path/filepath" - "regexp" - - "github.com/replicatedcom/support-bundle/types" - - jww "github.com/spf13/jwalterweatherman" -) - -func RunCommand(ctx context.Context, args []string) ([]types.Data, types.Result, error) { - command := args[0] - arg := args[1] - - // make a sanatized version of the filename we're searching for - replace forward slash, backslash colon and space with _ - r, _ := regexp.Compile(`[^\w]`) - filename := "/system/runcommand/" + r.ReplaceAllString(command, "_") + "_" + r.ReplaceAllString(arg, "_") - - var err error - - var datas []types.Data - var paths []string - - completeChan := make(chan error, 1) - - go func() { - b, err := exec.Command(command, arg).Output() - if err != nil { - jww.ERROR.Print(err) - completeChan <- err - return - } - - // Send the raw - datas = append(datas, types.Data{ - Filename: filepath.Join("/raw/", filename), - Data: b, - }) - paths = append(paths, filepath.Join("/raw/", filename)) - - completeChan <- nil - }() - - select { - case err = <-completeChan: - //completed on time - case <-ctx.Done(): - //failed to complete on time - err = types.TimeoutError{Message: fmt.Sprintf(`Command "%s" errored out with %s`, command+"_"+arg, ctx.Err().Error())} - } - - result := types.Result{ - Task: "runCommand", - Args: args, - Filenames: paths, - Error: err, - } - - return datas, result, err -} diff --git a/systemutil/runCommand_test.go b/systemutil/runCommand_test.go deleted file mode 100644 index 24e4fd0d5..000000000 --- a/systemutil/runCommand_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package systemutil - -import ( - "context" - "runtime" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestRunCommand(t *testing.T) { - - // it's a command that is sure to be installed AND will be the same on both windows and linux - commandStrings := []string{"go", "help"} - - datas, result, err := RunCommand(context.Background(), commandStrings) - require.NoError(t, err) - require.Equal(t, 1, len(datas), "Expected 1 data struct to be returned") - require.NoError(t, result.Error) -} - -func TestRunCommandTimeout(t *testing.T) { - - commandStrings := []string{"sleep", "10s"} - if runtime.GOOS == "windows" { - t.Skipf("This test is not yet compatible with windows") - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*1) - defer cancel() - - datas, result, err := RunCommand(ctx, commandStrings) - require.Error(t, err) - require.Equal(t, 0, len(datas), "Expected no data structs to be returned due to timeout") - require.Error(t, result.Error) -} diff --git a/bundle/generate_test.go b/tests/integration.go similarity index 98% rename from bundle/generate_test.go rename to tests/integration.go index 21b141068..431e44e1d 100644 --- a/bundle/generate_test.go +++ b/tests/integration.go @@ -1,4 +1,4 @@ -package bundle +package tests import ( "encoding/json" @@ -11,8 +11,6 @@ import ( "time" "github.com/divolgin/archiver/extractor" - "github.com/replicatedcom/support-bundle/metrics" - "github.com/replicatedcom/support-bundle/systemutil" "github.com/stretchr/testify/require" ) diff --git a/try/main.go b/try/main.go new file mode 100644 index 000000000..df62e4746 --- /dev/null +++ b/try/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "io/ioutil" + "log" + "time" + + "github.com/replicatedcom/support-bundle/bundle" + "github.com/replicatedcom/support-bundle/plugins/core" + "github.com/replicatedcom/support-bundle/plugins/docker" + "github.com/replicatedcom/support-bundle/spec" + "github.com/replicatedcom/support-bundle/types" +) + +func main() { + yml, err := ioutil.ReadFile("./spec.yml") + if err != nil { + log.Fatal(err) + } + specs, err := spec.Parse(yml) + if err != nil { + log.Fatal(err) + } + + d, err := docker.New() + if err != nil { + log.Fatal(err) + } + planner := bundle.Planner{ + Plugins: map[string]types.Plugin{ + "core": core.New(), + "docker": d, + }, + } + tasks := planner.Plan(specs) + if err := bundle.Generate(tasks, time.Minute, "/tmp/bundle.tar.gz"); err != nil { + log.Fatal(err) + } +} diff --git a/try/spec.yml b/try/spec.yml new file mode 100644 index 000000000..c03deb5f7 --- /dev/null +++ b/try/spec.yml @@ -0,0 +1,9 @@ +specs: + - builtin: core.loadavg + raw: /raw/metrics/loadavg + json: /json/metrics/loadavg.json + human: /human/metrics/loadavg + - builtin: docker.logs + raw: /raw/containers/e0095c302413/logs.txt + config: + container_id: e0095c302413 diff --git a/types/data.go b/types/data.go deleted file mode 100644 index 8b24874a0..000000000 --- a/types/data.go +++ /dev/null @@ -1,6 +0,0 @@ -package types - -type Data struct { - Filename string - Data []byte -} diff --git a/types/marshallableError.go b/types/marshallableError.go deleted file mode 100644 index 96108b38d..000000000 --- a/types/marshallableError.go +++ /dev/null @@ -1,9 +0,0 @@ -package types - -type MarshallableError struct { - Message string `json:"error,omitempty"` -} - -func (err MarshallableError) Error() string { - return err.Message -} diff --git a/types/plugin.go b/types/plugin.go new file mode 100644 index 000000000..cf4d5b22a --- /dev/null +++ b/types/plugin.go @@ -0,0 +1,17 @@ +package types + +import ( + "context" + "io" +) + +type BytesProducer func(context.Context) ([]byte, error) +type StructuredProducer func(context.Context) (interface{}, error) +type StreamProducer func(context.Context) (io.Reader, error) + +// probably stdout and stderr +type StreamsProducer func(context.Context) (io.Reader, io.Reader, error) + +type Planner func(Spec) []Task + +type Plugin map[string]Planner diff --git a/types/result.go b/types/result.go index 215381188..ee285f98a 100644 --- a/types/result.go +++ b/types/result.go @@ -1,8 +1,31 @@ package types +import "encoding/json" + +// Result represents a single file within a support bundle or the failure to +// collect the data for a single file within a support bundle. A Result may have +// both a Pathname and an Error if the file written was corrupted or incomplete. type Result struct { - Task string `json:"task"` - Args []string `json:"arguments"` - Filenames []string `json:"filenames"` - Error error `json:"error,omitempty"` + Description string `json:"description"` + // The subpath within the bundle + Path string `json:"path"` + Error error `json:"error,omitempty"` +} + +// Result.Error will be {} if it has no exported fields, so replace it with a +// string. +func (r *Result) MarshalJSON() ([]byte, error) { + intermediate := map[string]string{} + + if r.Description != "" { + intermediate["description"] = r.Description + } + if r.Path != "" { + intermediate["path"] = r.Path + } + if r.Error != nil { + intermediate["error"] = r.Error.Error() + } + + return json.Marshal(intermediate) } diff --git a/types/spec.go b/types/spec.go new file mode 100644 index 000000000..800a947c9 --- /dev/null +++ b/types/spec.go @@ -0,0 +1,18 @@ +package types + +type Spec struct { + ID string + Builtin string + TimeoutSeconds int + // paths + Raw string + JSON string + Human string + + // Plan-specific config + Config interface{} +} + +type ContainerConfig struct { + ContainerID string `json="container_id"` +} diff --git a/types/task.go b/types/task.go new file mode 100644 index 000000000..d5a450df9 --- /dev/null +++ b/types/task.go @@ -0,0 +1,9 @@ +package types + +import ( + "context" +) + +type Task interface { + Exec(ctx context.Context, rootDir string) []*Result +} diff --git a/types/timeoutError.go b/types/timeoutError.go deleted file mode 100644 index 2c9877556..000000000 --- a/types/timeoutError.go +++ /dev/null @@ -1,9 +0,0 @@ -package types - -type TimeoutError struct { - Message string `json:"timeout_error,omitempty"` -} - -func (err TimeoutError) Error() string { - return err.Message -} diff --git a/vendor/github.com/Microsoft/go-winio/ea.go b/vendor/github.com/Microsoft/go-winio/ea.go index 4051c1b33..b37e930d6 100644 --- a/vendor/github.com/Microsoft/go-winio/ea.go +++ b/vendor/github.com/Microsoft/go-winio/ea.go @@ -1,137 +1,137 @@ -package winio - -import ( - "bytes" - "encoding/binary" - "errors" -) - -type fileFullEaInformation struct { - NextEntryOffset uint32 - Flags uint8 - NameLength uint8 - ValueLength uint16 -} - -var ( - fileFullEaInformationSize = binary.Size(&fileFullEaInformation{}) - - errInvalidEaBuffer = errors.New("invalid extended attribute buffer") - errEaNameTooLarge = errors.New("extended attribute name too large") - errEaValueTooLarge = errors.New("extended attribute value too large") -) - -// ExtendedAttribute represents a single Windows EA. -type ExtendedAttribute struct { - Name string - Value []byte - Flags uint8 -} - -func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { - var info fileFullEaInformation - err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) - if err != nil { - err = errInvalidEaBuffer - return - } - - nameOffset := fileFullEaInformationSize - nameLen := int(info.NameLength) - valueOffset := nameOffset + int(info.NameLength) + 1 - valueLen := int(info.ValueLength) - nextOffset := int(info.NextEntryOffset) - if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) { - err = errInvalidEaBuffer - return - } - - ea.Name = string(b[nameOffset : nameOffset+nameLen]) - ea.Value = b[valueOffset : valueOffset+valueLen] - ea.Flags = info.Flags - if info.NextEntryOffset != 0 { - nb = b[info.NextEntryOffset:] - } - return -} - -// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION -// buffer retrieved from BackupRead, ZwQueryEaFile, etc. -func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) { - for len(b) != 0 { - ea, nb, err := parseEa(b) - if err != nil { - return nil, err - } - - eas = append(eas, ea) - b = nb - } - return -} - -func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error { - if int(uint8(len(ea.Name))) != len(ea.Name) { - return errEaNameTooLarge - } - if int(uint16(len(ea.Value))) != len(ea.Value) { - return errEaValueTooLarge - } - entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value)) - withPadding := (entrySize + 3) &^ 3 - nextOffset := uint32(0) - if !last { - nextOffset = withPadding - } - info := fileFullEaInformation{ - NextEntryOffset: nextOffset, - Flags: ea.Flags, - NameLength: uint8(len(ea.Name)), - ValueLength: uint16(len(ea.Value)), - } - - err := binary.Write(buf, binary.LittleEndian, &info) - if err != nil { - return err - } - - _, err = buf.Write([]byte(ea.Name)) - if err != nil { - return err - } - - err = buf.WriteByte(0) - if err != nil { - return err - } - - _, err = buf.Write(ea.Value) - if err != nil { - return err - } - - _, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize]) - if err != nil { - return err - } - - return nil -} - -// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION -// buffer for use with BackupWrite, ZwSetEaFile, etc. -func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) { - var buf bytes.Buffer - for i := range eas { - last := false - if i == len(eas)-1 { - last = true - } - - err := writeEa(&buf, &eas[i], last) - if err != nil { - return nil, err - } - } - return buf.Bytes(), nil -} +package winio + +import ( + "bytes" + "encoding/binary" + "errors" +) + +type fileFullEaInformation struct { + NextEntryOffset uint32 + Flags uint8 + NameLength uint8 + ValueLength uint16 +} + +var ( + fileFullEaInformationSize = binary.Size(&fileFullEaInformation{}) + + errInvalidEaBuffer = errors.New("invalid extended attribute buffer") + errEaNameTooLarge = errors.New("extended attribute name too large") + errEaValueTooLarge = errors.New("extended attribute value too large") +) + +// ExtendedAttribute represents a single Windows EA. +type ExtendedAttribute struct { + Name string + Value []byte + Flags uint8 +} + +func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { + var info fileFullEaInformation + err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) + if err != nil { + err = errInvalidEaBuffer + return + } + + nameOffset := fileFullEaInformationSize + nameLen := int(info.NameLength) + valueOffset := nameOffset + int(info.NameLength) + 1 + valueLen := int(info.ValueLength) + nextOffset := int(info.NextEntryOffset) + if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) { + err = errInvalidEaBuffer + return + } + + ea.Name = string(b[nameOffset : nameOffset+nameLen]) + ea.Value = b[valueOffset : valueOffset+valueLen] + ea.Flags = info.Flags + if info.NextEntryOffset != 0 { + nb = b[info.NextEntryOffset:] + } + return +} + +// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION +// buffer retrieved from BackupRead, ZwQueryEaFile, etc. +func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) { + for len(b) != 0 { + ea, nb, err := parseEa(b) + if err != nil { + return nil, err + } + + eas = append(eas, ea) + b = nb + } + return +} + +func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error { + if int(uint8(len(ea.Name))) != len(ea.Name) { + return errEaNameTooLarge + } + if int(uint16(len(ea.Value))) != len(ea.Value) { + return errEaValueTooLarge + } + entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value)) + withPadding := (entrySize + 3) &^ 3 + nextOffset := uint32(0) + if !last { + nextOffset = withPadding + } + info := fileFullEaInformation{ + NextEntryOffset: nextOffset, + Flags: ea.Flags, + NameLength: uint8(len(ea.Name)), + ValueLength: uint16(len(ea.Value)), + } + + err := binary.Write(buf, binary.LittleEndian, &info) + if err != nil { + return err + } + + _, err = buf.Write([]byte(ea.Name)) + if err != nil { + return err + } + + err = buf.WriteByte(0) + if err != nil { + return err + } + + _, err = buf.Write(ea.Value) + if err != nil { + return err + } + + _, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize]) + if err != nil { + return err + } + + return nil +} + +// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION +// buffer for use with BackupWrite, ZwSetEaFile, etc. +func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) { + var buf bytes.Buffer + for i := range eas { + last := false + if i == len(eas)-1 { + last = true + } + + err := writeEa(&buf, &eas[i], last) + if err != nil { + return nil, err + } + } + return buf.Bytes(), nil +} diff --git a/vendor/github.com/Microsoft/go-winio/ea_test.go b/vendor/github.com/Microsoft/go-winio/ea_test.go index 27db14ff8..92d9d4572 100644 --- a/vendor/github.com/Microsoft/go-winio/ea_test.go +++ b/vendor/github.com/Microsoft/go-winio/ea_test.go @@ -1,89 +1,89 @@ -package winio - -import ( - "io/ioutil" - "os" - "reflect" - "syscall" - "testing" - "unsafe" -) - -var ( - testEas = []ExtendedAttribute{ - {Name: "foo", Value: []byte("bar")}, - {Name: "fizz", Value: []byte("buzz")}, - } - - testEasEncoded = []byte{16, 0, 0, 0, 0, 3, 3, 0, 102, 111, 111, 0, 98, 97, 114, 0, 0, 0, 0, 0, 0, 4, 4, 0, 102, 105, 122, 122, 0, 98, 117, 122, 122, 0, 0, 0} - testEasNotPadded = testEasEncoded[0 : len(testEasEncoded)-3] - testEasTruncated = testEasEncoded[0:20] -) - -func Test_RoundTripEas(t *testing.T) { - b, err := EncodeExtendedAttributes(testEas) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(testEasEncoded, b) { - t.Fatalf("encoded mismatch %v %v", testEasEncoded, b) - } - eas, err := DecodeExtendedAttributes(b) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(testEas, eas) { - t.Fatalf("mismatch %+v %+v", testEas, eas) - } -} - -func Test_EasDontNeedPaddingAtEnd(t *testing.T) { - eas, err := DecodeExtendedAttributes(testEasNotPadded) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(testEas, eas) { - t.Fatalf("mismatch %+v %+v", testEas, eas) - } -} - -func Test_TruncatedEasFailCorrectly(t *testing.T) { - _, err := DecodeExtendedAttributes(testEasTruncated) - if err == nil { - t.Fatal("expected error") - } -} - -func Test_NilEasEncodeAndDecodeAsNil(t *testing.T) { - b, err := EncodeExtendedAttributes(nil) - if err != nil { - t.Fatal(err) - } - if len(b) != 0 { - t.Fatal("expected empty") - } - eas, err := DecodeExtendedAttributes(nil) - if err != nil { - t.Fatal(err) - } - if len(eas) != 0 { - t.Fatal("expected empty") - } -} - -// Test_SetFileEa makes sure that the test buffer is actually parsable by NtSetEaFile. -func Test_SetFileEa(t *testing.T) { - f, err := ioutil.TempFile("", "winio") - if err != nil { - t.Fatal(err) - } - defer os.Remove(f.Name()) - defer f.Close() - ntdll := syscall.MustLoadDLL("ntdll.dll") - ntSetEaFile := ntdll.MustFindProc("NtSetEaFile") - var iosb [2]uintptr - r, _, _ := ntSetEaFile.Call(f.Fd(), uintptr(unsafe.Pointer(&iosb[0])), uintptr(unsafe.Pointer(&testEasEncoded[0])), uintptr(len(testEasEncoded))) - if r != 0 { - t.Fatalf("NtSetEaFile failed with %08x", r) - } -} +package winio + +import ( + "io/ioutil" + "os" + "reflect" + "syscall" + "testing" + "unsafe" +) + +var ( + testEas = []ExtendedAttribute{ + {Name: "foo", Value: []byte("bar")}, + {Name: "fizz", Value: []byte("buzz")}, + } + + testEasEncoded = []byte{16, 0, 0, 0, 0, 3, 3, 0, 102, 111, 111, 0, 98, 97, 114, 0, 0, 0, 0, 0, 0, 4, 4, 0, 102, 105, 122, 122, 0, 98, 117, 122, 122, 0, 0, 0} + testEasNotPadded = testEasEncoded[0 : len(testEasEncoded)-3] + testEasTruncated = testEasEncoded[0:20] +) + +func Test_RoundTripEas(t *testing.T) { + b, err := EncodeExtendedAttributes(testEas) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(testEasEncoded, b) { + t.Fatalf("encoded mismatch %v %v", testEasEncoded, b) + } + eas, err := DecodeExtendedAttributes(b) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(testEas, eas) { + t.Fatalf("mismatch %+v %+v", testEas, eas) + } +} + +func Test_EasDontNeedPaddingAtEnd(t *testing.T) { + eas, err := DecodeExtendedAttributes(testEasNotPadded) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(testEas, eas) { + t.Fatalf("mismatch %+v %+v", testEas, eas) + } +} + +func Test_TruncatedEasFailCorrectly(t *testing.T) { + _, err := DecodeExtendedAttributes(testEasTruncated) + if err == nil { + t.Fatal("expected error") + } +} + +func Test_NilEasEncodeAndDecodeAsNil(t *testing.T) { + b, err := EncodeExtendedAttributes(nil) + if err != nil { + t.Fatal(err) + } + if len(b) != 0 { + t.Fatal("expected empty") + } + eas, err := DecodeExtendedAttributes(nil) + if err != nil { + t.Fatal(err) + } + if len(eas) != 0 { + t.Fatal("expected empty") + } +} + +// Test_SetFileEa makes sure that the test buffer is actually parsable by NtSetEaFile. +func Test_SetFileEa(t *testing.T) { + f, err := ioutil.TempFile("", "winio") + if err != nil { + t.Fatal(err) + } + defer os.Remove(f.Name()) + defer f.Close() + ntdll := syscall.MustLoadDLL("ntdll.dll") + ntSetEaFile := ntdll.MustFindProc("NtSetEaFile") + var iosb [2]uintptr + r, _, _ := ntSetEaFile.Call(f.Fd(), uintptr(unsafe.Pointer(&iosb[0])), uintptr(unsafe.Pointer(&testEasEncoded[0])), uintptr(len(testEasEncoded))) + if r != 0 { + t.Fatalf("NtSetEaFile failed with %08x", r) + } +} diff --git a/vendor/github.com/Microsoft/go-winio/vhd/mksyscall_windows.go b/vendor/github.com/Microsoft/go-winio/vhd/mksyscall_windows.go index 93a9e33e1..977fef815 100644 --- a/vendor/github.com/Microsoft/go-winio/vhd/mksyscall_windows.go +++ b/vendor/github.com/Microsoft/go-winio/vhd/mksyscall_windows.go @@ -1,901 +1,901 @@ -// 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. - -// Hard-coding unicode mode for VHD library. - -// +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) - }` - if r.Name == "" && !r.ReturnsError { - return "" - } - if r.Name == "" { - return r.useLongHandleErrorCode("r1") - } - if r.Type == "error" { - 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 - 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 + "\"") - } - 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 -} - -// 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 (f *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 -} - -// 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") - } - } - 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}} proc{{.DLLFuncName}} = mod{{.DLLName}}.NewProc("{{.DLLFuncName}}") -{{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 "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 "seterror"}}{{if .Rets.SetErrorCode}} {{.Rets.SetErrorCode}} -{{end}}{{end}} - -{{define "printtrace"}}{{if .PrintTrace}} print("SYSCALL: {{.Name}}(", {{.ParamPrintList}}") (", {{.Rets.PrintList}}")\n") -{{end}}{{end}} - -` +// 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. + +// Hard-coding unicode mode for VHD library. + +// +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) + }` + if r.Name == "" && !r.ReturnsError { + return "" + } + if r.Name == "" { + return r.useLongHandleErrorCode("r1") + } + if r.Type == "error" { + 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 + 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 + "\"") + } + 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 +} + +// 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 (f *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 +} + +// 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") + } + } + 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}} proc{{.DLLFuncName}} = mod{{.DLLName}}.NewProc("{{.DLLFuncName}}") +{{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 "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 "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/go-winio/vhd/vhd.go b/vendor/github.com/Microsoft/go-winio/vhd/vhd.go index fb9add41f..32f0701ea 100644 --- a/vendor/github.com/Microsoft/go-winio/vhd/vhd.go +++ b/vendor/github.com/Microsoft/go-winio/vhd/vhd.go @@ -1,82 +1,82 @@ -// +build windows - -package vhd - -import "syscall" - -//go:generate go run mksyscall_windows.go -output zvhd.go vhd.go - -//sys createVirtualDisk(virtualStorageType *virtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, flags uint32, providerSpecificFlags uint32, parameters *createVirtualDiskParameters, o *syscall.Overlapped, handle *syscall.Handle) (err error) [failretval != 0] = VirtDisk.CreateVirtualDisk - -type virtualStorageType struct { - DeviceID uint32 - VendorID [16]byte -} - -const virtualDiskAccessNONE uint32 = 0 -const virtualDiskAccessATTACHRO uint32 = 65536 -const virtualDiskAccessATTACHRW uint32 = 131072 -const virtualDiskAccessDETACH uint32 = 262144 -const virtualDiskAccessGETINFO uint32 = 524288 -const virtualDiskAccessCREATE uint32 = 1048576 -const virtualDiskAccessMETAOPS uint32 = 2097152 -const virtualDiskAccessREAD uint32 = 851968 -const virtualDiskAccessALL uint32 = 4128768 -const virtualDiskAccessWRITABLE uint32 = 3276800 - -const createVirtualDiskFlagNone uint32 = 0 -const createVirtualDiskFlagFullPhysicalAllocation uint32 = 1 -const createVirtualDiskFlagPreventWritesToSourceDisk uint32 = 2 -const createVirtualDiskFlagDoNotCopyMetadataFromParent uint32 = 4 - -type version2 struct { - UniqueID [16]byte // GUID - MaximumSize uint64 - BlockSizeInBytes uint32 - SectorSizeInBytes uint32 - ParentPath *uint16 // string - SourcePath *uint16 // string - OpenFlags uint32 - ParentVirtualStorageType virtualStorageType - SourceVirtualStorageType virtualStorageType - ResiliencyGUID [16]byte // GUID -} - -type createVirtualDiskParameters struct { - Version uint32 // Must always be set to 2 - Version2 version2 -} - -// CreateVhdx will create a simple vhdx file at the given path using default values. -func CreateVhdx(path string, maxSizeInGb, blockSizeInMb uint32) error { - var defaultType virtualStorageType - - parameters := createVirtualDiskParameters{ - Version: 2, - Version2: version2{ - MaximumSize: uint64(maxSizeInGb) * 1024 * 1024 * 1024, - BlockSizeInBytes: blockSizeInMb * 1024 * 1024, - }, - } - - var handle syscall.Handle - - if err := createVirtualDisk( - &defaultType, - path, - virtualDiskAccessNONE, - nil, - createVirtualDiskFlagNone, - 0, - ¶meters, - nil, - &handle); err != nil { - return err - } - - if err := syscall.CloseHandle(handle); err != nil { - return err - } - - return nil -} +// +build windows + +package vhd + +import "syscall" + +//go:generate go run mksyscall_windows.go -output zvhd.go vhd.go + +//sys createVirtualDisk(virtualStorageType *virtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, flags uint32, providerSpecificFlags uint32, parameters *createVirtualDiskParameters, o *syscall.Overlapped, handle *syscall.Handle) (err error) [failretval != 0] = VirtDisk.CreateVirtualDisk + +type virtualStorageType struct { + DeviceID uint32 + VendorID [16]byte +} + +const virtualDiskAccessNONE uint32 = 0 +const virtualDiskAccessATTACHRO uint32 = 65536 +const virtualDiskAccessATTACHRW uint32 = 131072 +const virtualDiskAccessDETACH uint32 = 262144 +const virtualDiskAccessGETINFO uint32 = 524288 +const virtualDiskAccessCREATE uint32 = 1048576 +const virtualDiskAccessMETAOPS uint32 = 2097152 +const virtualDiskAccessREAD uint32 = 851968 +const virtualDiskAccessALL uint32 = 4128768 +const virtualDiskAccessWRITABLE uint32 = 3276800 + +const createVirtualDiskFlagNone uint32 = 0 +const createVirtualDiskFlagFullPhysicalAllocation uint32 = 1 +const createVirtualDiskFlagPreventWritesToSourceDisk uint32 = 2 +const createVirtualDiskFlagDoNotCopyMetadataFromParent uint32 = 4 + +type version2 struct { + UniqueID [16]byte // GUID + MaximumSize uint64 + BlockSizeInBytes uint32 + SectorSizeInBytes uint32 + ParentPath *uint16 // string + SourcePath *uint16 // string + OpenFlags uint32 + ParentVirtualStorageType virtualStorageType + SourceVirtualStorageType virtualStorageType + ResiliencyGUID [16]byte // GUID +} + +type createVirtualDiskParameters struct { + Version uint32 // Must always be set to 2 + Version2 version2 +} + +// CreateVhdx will create a simple vhdx file at the given path using default values. +func CreateVhdx(path string, maxSizeInGb, blockSizeInMb uint32) error { + var defaultType virtualStorageType + + parameters := createVirtualDiskParameters{ + Version: 2, + Version2: version2{ + MaximumSize: uint64(maxSizeInGb) * 1024 * 1024 * 1024, + BlockSizeInBytes: blockSizeInMb * 1024 * 1024, + }, + } + + var handle syscall.Handle + + if err := createVirtualDisk( + &defaultType, + path, + virtualDiskAccessNONE, + nil, + createVirtualDiskFlagNone, + 0, + ¶meters, + nil, + &handle); err != nil { + return err + } + + if err := syscall.CloseHandle(handle); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/docker/distribution/contrib/docker-integration/run_multiversion.sh b/vendor/github.com/docker/distribution/contrib/docker-integration/run_multiversion.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/distribution/coverpkg.sh b/vendor/github.com/docker/distribution/coverpkg.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/distribution/project/hooks/configure-hooks.sh b/vendor/github.com/docker/distribution/project/hooks/configure-hooks.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/distribution/project/hooks/pre-commit b/vendor/github.com/docker/distribution/project/hooks/pre-commit old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/distribution/version/version.sh b/vendor/github.com/docker/distribution/version/version.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/builder/deb/aarch64/build.sh b/vendor/github.com/docker/docker/contrib/builder/deb/aarch64/build.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/builder/deb/aarch64/generate.sh b/vendor/github.com/docker/docker/contrib/builder/deb/aarch64/generate.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/builder/deb/amd64/build.sh b/vendor/github.com/docker/docker/contrib/builder/deb/amd64/build.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/builder/deb/amd64/generate.sh b/vendor/github.com/docker/docker/contrib/builder/deb/amd64/generate.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/builder/deb/armhf/generate.sh b/vendor/github.com/docker/docker/contrib/builder/deb/armhf/generate.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/builder/deb/ppc64le/build.sh b/vendor/github.com/docker/docker/contrib/builder/deb/ppc64le/build.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/builder/deb/ppc64le/generate.sh b/vendor/github.com/docker/docker/contrib/builder/deb/ppc64le/generate.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/builder/deb/s390x/build.sh b/vendor/github.com/docker/docker/contrib/builder/deb/s390x/build.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/builder/deb/s390x/generate.sh b/vendor/github.com/docker/docker/contrib/builder/deb/s390x/generate.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/builder/rpm/amd64/build.sh b/vendor/github.com/docker/docker/contrib/builder/rpm/amd64/build.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/builder/rpm/amd64/generate.sh b/vendor/github.com/docker/docker/contrib/builder/rpm/amd64/generate.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/check-config.sh b/vendor/github.com/docker/docker/contrib/check-config.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/dockerize-disk.sh b/vendor/github.com/docker/docker/contrib/dockerize-disk.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/download-frozen-image-v1.sh b/vendor/github.com/docker/docker/contrib/download-frozen-image-v1.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/download-frozen-image-v2.sh b/vendor/github.com/docker/docker/contrib/download-frozen-image-v2.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/gitdm/generate_aliases.sh b/vendor/github.com/docker/docker/contrib/gitdm/generate_aliases.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/init/sysvinit-debian/docker b/vendor/github.com/docker/docker/contrib/init/sysvinit-debian/docker old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/init/sysvinit-redhat/docker b/vendor/github.com/docker/docker/contrib/init/sysvinit-redhat/docker old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/mac-install-bundle.sh b/vendor/github.com/docker/docker/contrib/mac-install-bundle.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/mkimage-alpine.sh b/vendor/github.com/docker/docker/contrib/mkimage-alpine.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/mkimage-arch.sh b/vendor/github.com/docker/docker/contrib/mkimage-arch.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/mkimage-busybox.sh b/vendor/github.com/docker/docker/contrib/mkimage-busybox.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/mkimage-crux.sh b/vendor/github.com/docker/docker/contrib/mkimage-crux.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/mkimage-debootstrap.sh b/vendor/github.com/docker/docker/contrib/mkimage-debootstrap.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/mkimage-pld.sh b/vendor/github.com/docker/docker/contrib/mkimage-pld.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/mkimage-rinse.sh b/vendor/github.com/docker/docker/contrib/mkimage-rinse.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/mkimage-yum.sh b/vendor/github.com/docker/docker/contrib/mkimage-yum.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/mkimage.sh b/vendor/github.com/docker/docker/contrib/mkimage.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/mkimage/.febootstrap-minimize b/vendor/github.com/docker/docker/contrib/mkimage/.febootstrap-minimize old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/mkimage/busybox-static b/vendor/github.com/docker/docker/contrib/mkimage/busybox-static old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/mkimage/debootstrap b/vendor/github.com/docker/docker/contrib/mkimage/debootstrap old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/mkimage/mageia-urpmi b/vendor/github.com/docker/docker/contrib/mkimage/mageia-urpmi old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/mkimage/rinse b/vendor/github.com/docker/docker/contrib/mkimage/rinse old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/mkimage/solaris b/vendor/github.com/docker/docker/contrib/mkimage/solaris old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/nuke-graph-directory.sh b/vendor/github.com/docker/docker/contrib/nuke-graph-directory.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/project-stats.sh b/vendor/github.com/docker/docker/contrib/project-stats.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/report-issue.sh b/vendor/github.com/docker/docker/contrib/report-issue.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/contrib/reprepro/suites.sh b/vendor/github.com/docker/docker/contrib/reprepro/suites.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/dind b/vendor/github.com/docker/docker/hack/dind old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/dockerfile/binaries-commits b/vendor/github.com/docker/docker/hack/dockerfile/binaries-commits old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/dockerfile/install-binaries.sh b/vendor/github.com/docker/docker/hack/dockerfile/install-binaries.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/generate-authors.sh b/vendor/github.com/docker/docker/hack/generate-authors.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/generate-swagger-api.sh b/vendor/github.com/docker/docker/hack/generate-swagger-api.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/make.sh b/vendor/github.com/docker/docker/hack/make.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.default b/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.default deleted file mode 100644 index 4278533d6..000000000 --- a/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.default +++ /dev/null @@ -1 +0,0 @@ -../../../contrib/init/sysvinit-debian/docker.default \ No newline at end of file diff --git a/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.default b/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.default new file mode 120000 index 000000000..4278533d6 --- /dev/null +++ b/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.default @@ -0,0 +1 @@ +../../../contrib/init/sysvinit-debian/docker.default \ No newline at end of file diff --git a/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.init b/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.init deleted file mode 100644 index 8cb89d30d..000000000 --- a/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.init +++ /dev/null @@ -1 +0,0 @@ -../../../contrib/init/sysvinit-debian/docker \ No newline at end of file diff --git a/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.init b/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.init new file mode 120000 index 000000000..8cb89d30d --- /dev/null +++ b/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.init @@ -0,0 +1 @@ +../../../contrib/init/sysvinit-debian/docker \ No newline at end of file diff --git a/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.upstart b/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.upstart deleted file mode 100644 index 7e1b64a3e..000000000 --- a/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.upstart +++ /dev/null @@ -1 +0,0 @@ -../../../contrib/init/upstart/docker.conf \ No newline at end of file diff --git a/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.upstart b/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.upstart new file mode 120000 index 000000000..7e1b64a3e --- /dev/null +++ b/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.docker.upstart @@ -0,0 +1 @@ +../../../contrib/init/upstart/docker.conf \ No newline at end of file diff --git a/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.udev b/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.udev deleted file mode 100644 index 914a36195..000000000 --- a/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.udev +++ /dev/null @@ -1 +0,0 @@ -../../../contrib/udev/80-docker.rules \ No newline at end of file diff --git a/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.udev b/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.udev new file mode 120000 index 000000000..914a36195 --- /dev/null +++ b/vendor/github.com/docker/docker/hack/make/.build-deb/docker-engine.udev @@ -0,0 +1 @@ +../../../contrib/udev/80-docker.rules \ No newline at end of file diff --git a/vendor/github.com/docker/docker/hack/make/.build-deb/rules b/vendor/github.com/docker/docker/hack/make/.build-deb/rules old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/make/clean-apt-repo b/vendor/github.com/docker/docker/hack/make/clean-apt-repo old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/make/clean-yum-repo b/vendor/github.com/docker/docker/hack/make/clean-yum-repo old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/make/generate-index-listing b/vendor/github.com/docker/docker/hack/make/generate-index-listing old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/make/release-deb b/vendor/github.com/docker/docker/hack/make/release-deb old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/make/release-rpm b/vendor/github.com/docker/docker/hack/make/release-rpm old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/make/sign-repos b/vendor/github.com/docker/docker/hack/make/sign-repos old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/make/test-deb-install b/vendor/github.com/docker/docker/hack/make/test-deb-install old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/make/test-install-script b/vendor/github.com/docker/docker/hack/make/test-install-script old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/make/test-integration-cli b/vendor/github.com/docker/docker/hack/make/test-integration-cli old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/make/test-old-apt-repo b/vendor/github.com/docker/docker/hack/make/test-old-apt-repo old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/make/update-apt-repo b/vendor/github.com/docker/docker/hack/make/update-apt-repo old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/release.sh b/vendor/github.com/docker/docker/hack/release.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/validate/all b/vendor/github.com/docker/docker/hack/validate/all old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/validate/compose-bindata b/vendor/github.com/docker/docker/hack/validate/compose-bindata old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/validate/dco b/vendor/github.com/docker/docker/hack/validate/dco old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/validate/default b/vendor/github.com/docker/docker/hack/validate/default old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/validate/default-seccomp b/vendor/github.com/docker/docker/hack/validate/default-seccomp old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/validate/gofmt b/vendor/github.com/docker/docker/hack/validate/gofmt old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/validate/lint b/vendor/github.com/docker/docker/hack/validate/lint old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/validate/pkg-imports b/vendor/github.com/docker/docker/hack/validate/pkg-imports old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/validate/swagger b/vendor/github.com/docker/docker/hack/validate/swagger old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/validate/swagger-gen b/vendor/github.com/docker/docker/hack/validate/swagger-gen old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/validate/test-imports b/vendor/github.com/docker/docker/hack/validate/test-imports old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/validate/toml b/vendor/github.com/docker/docker/hack/validate/toml old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/validate/vendor b/vendor/github.com/docker/docker/hack/validate/vendor old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/validate/vet b/vendor/github.com/docker/docker/hack/validate/vet old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/hack/vendor.sh b/vendor/github.com/docker/docker/hack/vendor.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/integration-cli/fixtures/auth/docker-credential-shell-test b/vendor/github.com/docker/docker/integration-cli/fixtures/auth/docker-credential-shell-test old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/integration-cli/fixtures/notary/gen.sh b/vendor/github.com/docker/docker/integration-cli/fixtures/notary/gen.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/man/generate.sh b/vendor/github.com/docker/docker/man/generate.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/man/md2man-all.sh b/vendor/github.com/docker/docker/man/md2man-all.sh old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/profiles/seccomp/default.json b/vendor/github.com/docker/docker/profiles/seccomp/default.json old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/profiles/seccomp/fixtures/example.json b/vendor/github.com/docker/docker/profiles/seccomp/fixtures/example.json old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/project/CONTRIBUTORS.md b/vendor/github.com/docker/docker/project/CONTRIBUTORS.md deleted file mode 100644 index 44fcc6343..000000000 --- a/vendor/github.com/docker/docker/project/CONTRIBUTORS.md +++ /dev/null @@ -1 +0,0 @@ -../CONTRIBUTING.md \ No newline at end of file diff --git a/vendor/github.com/docker/docker/project/CONTRIBUTORS.md b/vendor/github.com/docker/docker/project/CONTRIBUTORS.md new file mode 120000 index 000000000..44fcc6343 --- /dev/null +++ b/vendor/github.com/docker/docker/project/CONTRIBUTORS.md @@ -0,0 +1 @@ +../CONTRIBUTING.md \ No newline at end of file diff --git a/vendor/github.com/docker/docker/runconfig/opts/fixtures/utf16.env b/vendor/github.com/docker/docker/runconfig/opts/fixtures/utf16.env old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/runconfig/opts/fixtures/utf16be.env b/vendor/github.com/docker/docker/runconfig/opts/fixtures/utf16be.env old mode 100644 new mode 100755 diff --git a/vendor/github.com/docker/docker/runconfig/opts/fixtures/utf8.env b/vendor/github.com/docker/docker/runconfig/opts/fixtures/utf8.env old mode 100644 new mode 100755 index 2b3a9dc46..1ce45055b --- a/vendor/github.com/docker/docker/runconfig/opts/fixtures/utf8.env +++ b/vendor/github.com/docker/docker/runconfig/opts/fixtures/utf8.env @@ -1,3 +1,3 @@ -FOO=BAR -HELLO=您好 +FOO=BAR +HELLO=您好 BAR=FOO \ No newline at end of file