diff --git a/pkg/commands/exec_live_win.go b/pkg/commands/exec_live_win.go index 419eef5f2..343e0dd9f 100644 --- a/pkg/commands/exec_live_win.go +++ b/pkg/commands/exec_live_win.go @@ -3,6 +3,7 @@ package commands // RunCommandWithOutputLiveWrapper runs a command live but because of windows compatibility this command can't be ran there +// TODO: Remove this hack and replace it with a propper way to run commands live on windows func RunCommandWithOutputLiveWrapper(c *OSCommand, command string, output func(string) string) error { return c.RunCommand(command) } diff --git a/vendor/github.com/ionrock/procs/Gopkg.toml b/vendor/github.com/ionrock/procs/Gopkg.toml new file mode 100644 index 000000000..2521101cd --- /dev/null +++ b/vendor/github.com/ionrock/procs/Gopkg.toml @@ -0,0 +1,30 @@ + +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" + + +[[constraint]] + branch = "master" + name = "github.com/apoydence/onpar" + +[[constraint]] + branch = "master" + name = "github.com/flynn/go-shlex" diff --git a/vendor/github.com/ionrock/procs/LICENSE.md b/vendor/github.com/ionrock/procs/LICENSE.md new file mode 100644 index 000000000..9fb415ad4 --- /dev/null +++ b/vendor/github.com/ionrock/procs/LICENSE.md @@ -0,0 +1,13 @@ +Copyright 2017 Eric Larson + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/github.com/ionrock/procs/Makefile b/vendor/github.com/ionrock/procs/Makefile new file mode 100644 index 000000000..9e82d411d --- /dev/null +++ b/vendor/github.com/ionrock/procs/Makefile @@ -0,0 +1,32 @@ +SOURCEDIR=. +SOURCES := $(shell find $(SOURCEDIR) -name '*.go') + +test: dep + dep ensure + go test . + +race: dep + dep ensure + go test -race . + + +dep: +ifeq (, $(shell which dep)) + go get -u github.com/golang/dep/cmd/dep +endif + +all: prelog cmdtmpl procmon + +prelog: $(SOURCES) + go build ./cmd/prelog + +cmdtmpl: $(SOURCES) + go build ./cmd/cmdtmpl + +procmon: $(SOURCES) + go build ./cmd/procmon + +clean: + rm -f prelog + rm -f cmdtmpl + rm -f procmon diff --git a/vendor/github.com/ionrock/procs/README.md b/vendor/github.com/ionrock/procs/README.md new file mode 100644 index 000000000..0efd1252b --- /dev/null +++ b/vendor/github.com/ionrock/procs/README.md @@ -0,0 +1,168 @@ +# Procs + +[![](https://travis-ci.org/ionrock/procs.svg?branch=master)](https://travis-ci.org/ionrock/procs) +[![Go Report Card](https://goreportcard.com/badge/github.com/ionrock/procs)](https://goreportcard.com/report/github.com/ionrock/procs) +[![GoDoc](https://godoc.org/github.com/ionrock/procs?status.svg)](https://godoc.org/github.com/ionrock/procs) + +Procs is a library to make working with command line applications a +little nicer. + +The primary use case is when you have to use a command line client in +place of an API. Often times you want to do things like output stdout +within your own logs or ensure that every time the command is called, +there are a standard set of flags that are used. + +## Basic Usage + +The majority of this functionality is intended to be included the +procs.Process. + +### Defining a Command + +A command can be defined by a string rather than a []string. Normally, +this also implies that the library will run the command in a shell, +exposing a potential man in the middle attack. Rather than using a +shell, procs [lexically +parses](https://github.com/flynn-archive/go-shlex) the command for the +different arguments. It also allows for pipes in order to string +commands together. + +```go +p := procs.NewProcess("kubectl get events | grep dev") +``` + +You can also define a new `Process` by passing in predefined commands. + +```go +cmds := []*exec.Cmd{ + exec.Command("kubectl", "get", "events"), + exec.Command("grep", "dev"), +} + +p := procs.Process{Cmds: cmds} +``` + +### Output Handling + +One use case that is cumbersome is using the piped output from a +command. For example, lets say we wanted to start a couple commands +and have each command have its own prefix in stdout, while still +capturing the output of the command as-is. + +```go +p := procs.NewProcess("cmd1") +p.OutputHandler = func(line string) string { + fmt.Printf("cmd1 | %s\n") + return line +} +out, _ := p.Run() +fmt.Println(out) +``` + +Whatever is returned from the `OutputHandler` will be in the buffered +output. In this way you can choose to filter or skip output buffering +completely. + +You can also define a `ErrHandler` using the same signature to get the +same filtering for stderr. + +### Environment Variables + +Rather than use the `exec.Cmd` `[]string` environment variables, a +`procs.Process` uses a `map[string]string` for environment variables. + +```go +p := procs.NewProcess("echo $FOO") +p.Env = map[string]string{"FOO": "foo"} +``` + +Also, environment variables defined by the `Process.Env` can be +expanded automatically using the `os.Expand` semantics and the +provided environment. + +There is a `ParseEnv` function that can help to merge the parent +processes' environment with any new values. + +```go +env := ParseEnv(os.Environ()) +env["USER"] = "foo" +``` + +Finally, if you are building commands manually, the `Env` function can +take a `map[string]string` and convert it to a `[]string` for use with +an `exec.Cmd`. The `Env` function also accepts a `useEnv` bool to help +include the parent process environment. + +```go +cmd := exec.Command("knife", "cookbook", "show", cb) +cmd.Env = Env(map[string]string{"USER": "knife-user"}, true) +``` + +## Example Applications + +Take a look in the [`cmd`](./cmd/) dir for some simple applications +that use the library. You can also `make all` to build them. The +examples below assume you've built them locally. + +### Prelog + +The `prelog` command allows running a command and prefixing the output +with a value. + +```bash +$ ./prelog -prefix foo -- echo 'hello world!' +Running the command +foo | hello world! +Accessing the output without a prefix. +hello world! +Running the command with Start / Wait +foo | hello world! +``` + +### Cmdtmpl + +The `cmdtmpl` command uses the `procs.Builder` to create a command +based on some paramters. It will take a `data.yml` file and +`template.yml` file to create a command. + +```bash +$ cat example/data.json +{ + "source": "https://my.example.org", + "user": "foo", + "model": "widget", + "action": "create", + "args": "-f new -i improved" +} +$ cat example/template.json +[ + "mysvc ${model} ${action} ${args}", + "--endpoint ${source}", + "--username ${user}" +] +$ ./cmdtmpl -data example/data.json -template example/template.json +Command: mysvc foo widget create -f new -i imporoved --endpoint https://my.example.org --username foo +$ ./cmdtmpl -data example/data.json -template example/template.json -field user=bar +Command: mysvc foo widget create -f new -i imporoved --endpoint https://my.example.org --username bar +``` + +### Procmon + +The `procmon` command acts like +[foreman](https://github.com/ddollar/foreman) with the difference +being it uses a JSON file with key value pairs instead of a +Procfile. This example uses the `procs.Manager` to manage a set of +`procs.Processes`. + +```bash +$ cat example/procfile.json +{ + "web": "python -m SimpleHTTPServer" +} +$ ./procmon -procfile example/procfile.json +web | Starting web with python -m SimpleHTTPServer +``` + +You can then access http://localhost:8000 to see the logs. You can +also kill the child process and see `procmon` recognizing it has +exited and exit itself. diff --git a/vendor/github.com/ionrock/procs/builder.go b/vendor/github.com/ionrock/procs/builder.go new file mode 100644 index 000000000..85c68391c --- /dev/null +++ b/vendor/github.com/ionrock/procs/builder.go @@ -0,0 +1,58 @@ +package procs + +import ( + "os" + "strings" +) + +// Builder helps construct commands using templates. +type Builder struct { + Context map[string]string + Templates []string +} + +func (b *Builder) getConfig(ctx map[string]string) func(string) string { + return func(key string) string { + if v, ok := ctx[key]; ok { + return v + } + return "" + } +} + +func (b *Builder) expand(v string, ctx map[string]string) string { + return os.Expand(v, b.getConfig(ctx)) +} + +// Command returns the result of the templates as a single string. +func (b *Builder) Command() string { + parts := []string{} + for _, t := range b.Templates { + parts = append(parts, b.expand(t, b.Context)) + } + + return strings.Join(parts, " ") +} + +// CommandContext returns the result of the templates as a single +// string, but allows providing an environment context as a +// map[string]string for expansions. +func (b *Builder) CommandContext(ctx map[string]string) string { + // Build our environment context by starting with our Builder + // context and overlay the passed in context map. + env := make(map[string]string) + for k, v := range b.Context { + env[k] = b.expand(v, b.Context) + } + + for k, v := range ctx { + env[k] = b.expand(v, env) + } + + parts := []string{} + for _, t := range b.Templates { + parts = append(parts, b.expand(t, env)) + } + + return strings.Join(parts, " ") +} diff --git a/vendor/github.com/ionrock/procs/builder_test.go b/vendor/github.com/ionrock/procs/builder_test.go new file mode 100644 index 000000000..a093eeb09 --- /dev/null +++ b/vendor/github.com/ionrock/procs/builder_test.go @@ -0,0 +1,58 @@ +package procs_test + +import ( + "testing" + + "github.com/ionrock/procs" +) + +func TestCommandContext(t *testing.T) { + b := &procs.Builder{ + Context: map[string]string{ + "options": "-Fj -s https://example.com/chef -k knife.pem", + }, + + Templates: []string{ + "knife", + "${model} ${action}", + "${args}", + "${options}", + }, + } + + cmd := b.CommandContext(map[string]string{ + "model": "data bag", + "action": "from file", + "args": "foo data_bags/foo/bar.json", + }) + + expected := "knife data bag from file foo data_bags/foo/bar.json -Fj -s https://example.com/chef -k knife.pem" + if cmd != expected { + t.Fatalf("failed building command: %q != %q", cmd, expected) + } +} + +func TestCommand(t *testing.T) { + b := &procs.Builder{ + Context: map[string]string{ + "options": "-Fj -s https://example.com/chef -k knife.pem", + "model": "data bag", + "action": "from file", + "args": "foo data_bags/foo/bar.json", + }, + + Templates: []string{ + "knife", + "${model} ${action}", + "${args}", + "${options}", + }, + } + + cmd := b.CommandContext(map[string]string{}) + + expected := "knife data bag from file foo data_bags/foo/bar.json -Fj -s https://example.com/chef -k knife.pem" + if cmd != expected { + t.Fatalf("failed building command: %q != %q", cmd, expected) + } +} diff --git a/vendor/github.com/ionrock/procs/env.go b/vendor/github.com/ionrock/procs/env.go new file mode 100644 index 000000000..3cf93f76d --- /dev/null +++ b/vendor/github.com/ionrock/procs/env.go @@ -0,0 +1,48 @@ +package procs + +import ( + "fmt" + "os" + "strings" +) + +// ParseEnv takes an environment []string and converts it to a map[string]string. +func ParseEnv(environ []string) map[string]string { + env := make(map[string]string) + for _, e := range environ { + pair := strings.SplitN(e, "=", 2) + + // There is a chance we can get an env with empty values + if len(pair) == 2 { + env[pair[0]] = pair[1] + } + } + return env +} + +// Env takes a map[string]string and converts it to a []string that +// can be used with exec.Cmd. The useEnv boolean flag will include the +// current process environment, overlaying the provided env +// map[string]string. +func Env(env map[string]string, useEnv bool) []string { + envlist := []string{} + + // update our env by loading our env and overriding any values in + // the provided env. + if useEnv { + environ := ParseEnv(os.Environ()) + for k, v := range env { + environ[k] = v + } + env = environ + } + + for key, val := range env { + if key == "" { + continue + } + envlist = append(envlist, fmt.Sprintf("%s=%s", key, val)) + } + + return envlist +} diff --git a/vendor/github.com/ionrock/procs/env_test.go b/vendor/github.com/ionrock/procs/env_test.go new file mode 100644 index 000000000..1b2a0498b --- /dev/null +++ b/vendor/github.com/ionrock/procs/env_test.go @@ -0,0 +1,83 @@ +package procs_test + +import ( + "fmt" + "os" + "os/exec" + "strings" + "testing" + + "github.com/ionrock/procs" +) + +func TestParseEnv(t *testing.T) { + env := []string{ + "FOO=bar", + "BAZ=`echo 'hello=world'`", + } + + m := procs.ParseEnv(env) + + v, ok := m["FOO"] + if !ok { + t.Errorf("error missing FOO from env: %#v", m) + } + + if v != "bar" { + t.Errorf("error FOO != bar: %s", v) + } + + v, ok = m["BAZ"] + + if !ok { + t.Errorf("error missing BAZ from env: %#v", m) + } + + expectBaz := "`echo 'hello=world'`" + if v != expectBaz { + t.Errorf("error BAZ != %s: %s", expectBaz, v) + } +} + +func TestEnvBuilder(t *testing.T) { + env := procs.Env(map[string]string{ + "FOO": "bar", + "BAZ": "hello world", + }, false) + + if len(env) != 2 { + t.Errorf("error loading env: %s", env) + } +} + +func helperEnvCommand(env map[string]string) *exec.Cmd { + cmd := exec.Command(os.Args[0], "-test.run=TestEnvBuilderOverrides") + cmd.Env = procs.Env(env, false) + return cmd +} + +func TestEnvBuilderOverrides(t *testing.T) { + if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { + return + } + for _, envvar := range procs.Env(map[string]string{"FOO": "override"}, true) { + fmt.Println(envvar) + } +} + +func TestEnvBuilderWithEnv(t *testing.T) { + cmd := helperEnvCommand(map[string]string{ + "GO_WANT_HELPER_PROCESS": "1", + "FOO": "default", + }) + out, err := cmd.Output() + if err != nil { + t.Fatalf("error running helper: %s", err) + } + + env := procs.ParseEnv(strings.Split(string(out), "\n")) + + if env["FOO"] != "override" { + t.Errorf("error overriding envvar: %s", string(out)) + } +} diff --git a/vendor/github.com/ionrock/procs/example_basic_test.go b/vendor/github.com/ionrock/procs/example_basic_test.go new file mode 100644 index 000000000..8d2adaf06 --- /dev/null +++ b/vendor/github.com/ionrock/procs/example_basic_test.go @@ -0,0 +1,33 @@ +package procs_test + +import ( + "fmt" + + "github.com/ionrock/procs" +) + +func Example() { + + b := procs.Builder{ + Context: map[string]string{ + "NAME": "eric", + }, + Templates: []string{ + "echo $NAME |", + "grep $NAME", + }, + } + + cmd := b.Command() + + fmt.Println(cmd) + + p := procs.NewProcess(cmd) + + p.Run() + out, _ := p.Output() + fmt.Println(string(out)) + // Output: + // echo eric | grep eric + // eric +} diff --git a/vendor/github.com/ionrock/procs/example_predefined_cmds_test.go b/vendor/github.com/ionrock/procs/example_predefined_cmds_test.go new file mode 100644 index 000000000..3dfd64015 --- /dev/null +++ b/vendor/github.com/ionrock/procs/example_predefined_cmds_test.go @@ -0,0 +1,23 @@ +package procs_test + +import ( + "fmt" + "os/exec" + + "github.com/ionrock/procs" +) + +func Example_predefinedCmds() { + p := procs.Process{ + Cmds: []*exec.Cmd{ + exec.Command("echo", "foo"), + exec.Command("grep", "foo"), + }, + } + + p.Run() + out, _ := p.Output() + fmt.Println(string(out)) + // Output: + // foo +} diff --git a/vendor/github.com/ionrock/procs/examples_test.go b/vendor/github.com/ionrock/procs/examples_test.go new file mode 100644 index 000000000..c9b43cc1c --- /dev/null +++ b/vendor/github.com/ionrock/procs/examples_test.go @@ -0,0 +1,43 @@ +package procs_test + +import ( + "fmt" + + "github.com/ionrock/procs" +) + +func ExampleSplitCommand() { + parts := procs.SplitCommand("echo 'hello world'") + for i, p := range parts { + fmt.Printf("%d %s\n", i+1, p) + } + + // Output: + // 1 echo + // 2 hello world +} + +func ExampleSplitCommandEnv() { + env := map[string]string{ + "GREETING": "hello", + "NAME": "world!", + "PASSWORD": "secret", + } + + getenv := func(key string) string { + if v, ok := env[key]; ok && key != "PASSWORD" { + return v + } + return "" + } + + parts := procs.SplitCommandEnv("echo '$GREETING $NAME $PASSWORD'", getenv) + + for i, p := range parts { + fmt.Printf("%d %s\n", i+1, p) + } + + // Output: + // 1 echo + // 2 hello world! +} diff --git a/vendor/github.com/ionrock/procs/manager.go b/vendor/github.com/ionrock/procs/manager.go new file mode 100644 index 000000000..91169e300 --- /dev/null +++ b/vendor/github.com/ionrock/procs/manager.go @@ -0,0 +1,119 @@ +package procs + +import ( + "fmt" + "sync" +) + +// Manager manages a set of Processes. +type Manager struct { + Processes map[string]*Process + + lock sync.Mutex +} + +// NewManager creates a new *Manager. +func NewManager() *Manager { + return &Manager{ + Processes: make(map[string]*Process), + } + +} + +// StdoutHandler returns an OutHandler that will ensure the underlying +// process has an empty stdout buffer and logs to stdout a prefixed value +// of "$name | $line". +func (m *Manager) StdoutHandler(name string) OutHandler { + return func(line string) string { + fmt.Printf("%s | %s\n", name, line) + return "" + } +} + +// StderrHandler returns an OutHandler that will ensure the underlying +// process has an empty stderr buffer and logs to stdout a prefixed value +// of "$name | $line". +func (m *Manager) StderrHandler(name string) OutHandler { + return func(line string) string { + fmt.Printf("%s | %s\n", name, line) + return "" + } +} + +// Start and managed a new process using the default handlers from a +// string. +func (m *Manager) Start(name, cmd string) error { + m.lock.Lock() + defer m.lock.Unlock() + + p := NewProcess(cmd) + p.OutputHandler = m.StdoutHandler(name) + p.ErrHandler = m.StderrHandler(name) + err := p.Start() + if err != nil { + return err + } + + m.Processes[name] = p + return nil +} + +// StartProcess starts and manages a predifined process. +func (m *Manager) StartProcess(name string, p *Process) error { + m.lock.Lock() + defer m.lock.Unlock() + + err := p.Start() + if err != nil { + return err + } + + m.Processes[name] = p + return nil +} + +// Stop will try to stop a managed process. If the process does not +// exist, no error is returned. +func (m *Manager) Stop(name string) error { + p, ok := m.Processes[name] + // We don't mind stopping a process that doesn't exist. + if !ok { + return nil + } + + return p.Stop() +} + +// Remove will try to stop and remove a managed process. +func (m *Manager) Remove(name string) error { + m.lock.Lock() + defer m.lock.Unlock() + + err := m.Stop(name) + if err != nil { + return err + } + + // Note that if the stop fails we don't remove it from the map of + // processes to avoid losing the reference. + delete(m.Processes, name) + + return nil +} + +// Wait will block until all managed processes have finished. +func (m *Manager) Wait() error { + wg := &sync.WaitGroup{} + wg.Add(len(m.Processes)) + + for _, p := range m.Processes { + go func(proc *Process) { + defer wg.Done() + proc.Wait() + }(p) + } + + wg.Wait() + + return nil +} diff --git a/vendor/github.com/ionrock/procs/manager_test.go b/vendor/github.com/ionrock/procs/manager_test.go new file mode 100644 index 000000000..69c999407 --- /dev/null +++ b/vendor/github.com/ionrock/procs/manager_test.go @@ -0,0 +1,55 @@ +package procs_test + +import ( + "flag" + "fmt" + "log" + "net/http" + "os" + "testing" + + "github.com/ionrock/procs" +) + +func TestManagerStartHelper(t *testing.T) { + if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { + return + } + + port := flag.String("p", "12212", "port to serve on") + directory := flag.String("d", ".", "the directory of static file to host") + flag.Parse() + + http.Handle("/", http.FileServer(http.Dir(*directory))) + + log.Printf("Serving %s on HTTP port: %s\n", *directory, *port) + log.Fatal(http.ListenAndServe(":"+*port, nil)) + os.Exit(0) +} + +func TestManagerStart(t *testing.T) { + m := procs.NewManager() + + err := m.Start("test", fmt.Sprintf("%s -test.run=TestManagerStartHelper", os.Args[0])) + if err != nil { + t.Errorf("failed to start test process: %s", err) + } + + if len(m.Processes) != 1 { + t.Error("failed to add process") + } + + err = m.Stop("test") + if err != nil { + t.Errorf("error stopping process: %s", err) + } + + err = m.Remove("test") + if err != nil { + t.Errorf("error removing process: %s", err) + } + + if len(m.Processes) != 0 { + t.Error("failed to remove processes") + } +} diff --git a/vendor/github.com/ionrock/procs/parse.go b/vendor/github.com/ionrock/procs/parse.go new file mode 100644 index 000000000..8753f3878 --- /dev/null +++ b/vendor/github.com/ionrock/procs/parse.go @@ -0,0 +1,36 @@ +package procs + +import ( + "log" + "os" + "strings" + + shlex "github.com/flynn/go-shlex" +) + +// SplitCommand parses a command and splits it into lexical arguments +// like a shell, returning a []string that can be used as arguments to +// exec.Command. +func SplitCommand(cmd string) []string { + return SplitCommandEnv(cmd, nil) +} + +// SplitCommandEnv parses a command and splits it into lexical +// arguments like a shell, returning a []string that can be used as +// arguments to exec.Command. It also allows providing an expansion +// function that will be used when expanding values within the parsed +// arguments. +func SplitCommandEnv(cmd string, getenv func(key string) string) []string { + parts, err := shlex.Split(strings.TrimSpace(cmd)) + if err != nil { + log.Fatal(err) + } + + if getenv != nil { + for i, p := range parts { + parts[i] = os.Expand(p, getenv) + } + } + + return parts +} diff --git a/vendor/github.com/ionrock/procs/parse_test.go b/vendor/github.com/ionrock/procs/parse_test.go new file mode 100644 index 000000000..1e24f6a92 --- /dev/null +++ b/vendor/github.com/ionrock/procs/parse_test.go @@ -0,0 +1,44 @@ +package procs_test + +import ( + "testing" + + "github.com/apoydence/onpar" + . "github.com/apoydence/onpar/expect" + . "github.com/apoydence/onpar/matchers" + "github.com/ionrock/procs" +) + +func matchSplitCommand(t *testing.T, parts, expected []string) { + for i, part := range parts { + Expect(t, part).To(Equal(expected[i])) + } +} + +func TestSplitCommand(t *testing.T) { + o := onpar.New() + defer o.Run(t) + + o.Group("split with pipe", func() { + o.BeforeEach(func(t *testing.T) (*testing.T, []string, []string) { + parts := procs.SplitCommand("echo 'foo' | grep o") + expected := []string{"echo", "foo", "|", "grep", "o"} + return t, parts, expected + }) + + o.Spec("pass with a pipe", matchSplitCommand) + }) + + o.Group("replace with specific context", func() { + o.BeforeEach(func(t *testing.T) (*testing.T, []string, []string) { + parts := procs.SplitCommandEnv("echo ${FOO}", func(k string) string { + return "bar" + }) + + expected := []string{"echo", "bar"} + return t, parts, expected + }) + + o.Spec("expand values found in provided env", matchSplitCommand) + }) +} diff --git a/vendor/github.com/ionrock/procs/process.go b/vendor/github.com/ionrock/procs/process.go new file mode 100644 index 000000000..a990e9ff2 --- /dev/null +++ b/vendor/github.com/ionrock/procs/process.go @@ -0,0 +1,307 @@ +// Procs is a library to make working with command line applications a +// little nicer. +// +// The goal is to expand on the os/exec package by providing some +// features usually accomplished in a shell, without having to resort to +// a shell. Procs also tries to make working with output simpler by +// providing a simple line handler API over working with io pipes. +// +// Finally, while the hope is that procs provides some convenience, it +// is also a goal to help make it easier to write more secure +// code. For example, avoiding a shell and the ability to manage the +// environment as a map[string]string are both measures that intend to +// make it easier to accomplish things like avoiding outputting +// secrets and opening the door for MITM attacks. With that said, it is +// always important to consider the security implications, especially +// when you are working with untrusted input or sensitive data. +package procs + +import ( + "bufio" + "bytes" + "fmt" + "io" + "log" + "os" + "os/exec" + "sync" +) + +// OutHandler defines the interface for writing output handlers for +// Process objects. +type OutHandler func(string) string + +// Process is intended to be used like exec.Cmd where possible. +type Process struct { + // CmdString takes a string and parses it into the relevant cmds + CmdString string + + // Cmds is the list of command delmited by pipes. + Cmds []*exec.Cmd + + // Env provides a map[string]string that can mutated before + // running a command. + Env map[string]string + + // Dir defines the directory the command should run in. The + // Default is the current dir. + Dir string + + // OutputHandler can be defined to perform any sort of processing + // on the output. The simple interface is to accept a string (a + // line of output) and return a string that will be included in the + // buffered output and/or output written to stdout.' + // + // For example defining the Process as: + // + // prefix := "myapp" + // p := &procs.Process{ + // OutputHandler: func(line string) string { + // return fmt.Sprintf("%s | %s", prefix, line) + // }, + // } + // + // This would prefix the stdout lines with a "myapp | ". + // + // By the default, this function is nil and will be skipped, with + // the unchanged line being added to the respective output buffer. + OutputHandler OutHandler + + // ErrHandler is a OutputHandler for stderr. + ErrHandler OutHandler + + // When no output is given, we'll buffer output in these vars. + errBuffer bytes.Buffer + outBuffer bytes.Buffer + + // When a output handler is provided, we ensure we're handling a + // single line at at time. + outputWait *sync.WaitGroup +} + +// NewProcess creates a new *Process from a command string. +// +// It is assumed that the user will mutate the resulting *Process by +// setting the necessary attributes. +func NewProcess(command string) *Process { + return &Process{CmdString: command} +} + +// internal expand method to use the proc env. +func (p *Process) expand(s string) string { + return os.Expand(s, func(key string) string { + v, _ := p.Env[key] + return v + }) +} + +// addCmd adds a new command to the list of commands, ensuring the Dir +// and Env have been added to the underlying *exec.Cmd instances. +func (p *Process) addCmd(cmdparts []string) { + var cmd *exec.Cmd + if len(cmdparts) == 1 { + cmd = exec.Command(cmdparts[0]) + } else { + cmd = exec.Command(cmdparts[0], cmdparts[1:]...) + } + + if p.Dir != "" { + cmd.Dir = p.Dir + } + + if p.Env != nil { + env := []string{} + for k, v := range p.Env { + env = append(env, fmt.Sprintf("%s=%s", k, p.expand(v))) + } + + cmd.Env = env + } + + p.Cmds = append(p.Cmds, cmd) +} + +// findCmds parses the CmdString to find the commands that should be +// run by spliting the lexically parsed command by pipes ("|"). +func (p *Process) findCmds() { + // Skip if the cmd set is already set. This allows manual creation + // of piped commands. + if len(p.Cmds) > 0 { + return + } + + if p.CmdString == "" { + return + } + + parts := SplitCommand(p.CmdString) + for i := range parts { + parts[i] = p.expand(parts[i]) + } + + cmd := []string{} + for _, part := range parts { + if part == "|" { + p.addCmd(cmd) + cmd = []string{} + } else { + cmd = append(cmd, part) + } + } + + p.addCmd(cmd) +} + +// lineReader takes will read a line in the io.Reader and write to the +// Process output buffer and use any OutputHandler that exists. +func (p *Process) lineReader(wg *sync.WaitGroup, r io.Reader, w *bytes.Buffer, handler OutHandler) { + defer wg.Done() + + reader := bufio.NewReader(r) + var buffer bytes.Buffer + + for { + buf := make([]byte, 1024) + + n, err := reader.Read(buf) + if err != nil { + return + } + + buf = buf[:n] + + for { + i := bytes.IndexByte(buf, '\n') + if i < 0 { + break + } + + buffer.Write(buf[0:i]) + outLine := buffer.String() + if handler != nil { + outLine = handler(outLine) + } + w.WriteString(outLine) + buffer.Reset() + buf = buf[i+1:] + } + buffer.Write(buf) + } +} + +// checkErr shortens the creation of the pipes by bailing out with a +// log.Fatal. +func checkErr(msg string, err error) { + if err != nil { + log.Fatalf("%s: %s", msg, err) + } +} + +func (p *Process) setupPipes() error { + last := len(p.Cmds) - 1 + + if last != 0 { + for i, cmd := range p.Cmds[:last] { + var err error + + p.Cmds[i+1].Stdin, err = cmd.StdoutPipe() + if err != nil { + fmt.Printf("error creating stdout pipe: %s\n", err) + return err + } + + cmd.Stderr = &p.errBuffer + } + } + + cmd := p.Cmds[last] + stdout, err := cmd.StdoutPipe() + if err != nil { + fmt.Printf("error creating stdout pipe: %s\n", err) + return err + } + + stderr, err := cmd.StderrPipe() + if err != nil { + fmt.Printf("error creating stderr pipe: %s\n", err) + return err + } + + p.outputWait = new(sync.WaitGroup) + p.outputWait.Add(2) + + // These close the stdout/err channels + go p.lineReader(p.outputWait, stdout, &p.outBuffer, p.OutputHandler) + go p.lineReader(p.outputWait, stderr, &p.errBuffer, p.ErrHandler) + + return nil +} + +// Run executes the cmds and returns the output as a string and any error. +func (p *Process) Run() error { + if err := p.Start(); err != nil { + return err + } + + return p.Wait() +} + +// Start will start the list of cmds. +func (p *Process) Start() error { + p.findCmds() + p.setupPipes() + + for i, cmd := range p.Cmds { + err := cmd.Start() + if err != nil { + defer func() { + for _, precmd := range p.Cmds[0:i] { + precmd.Wait() + } + }() + return err + } + } + + return nil +} + +// Wait will block, waiting for the commands to finish. +func (p *Process) Wait() error { + if p.outputWait != nil { + p.outputWait.Wait() + } + + var err error + for _, cmd := range p.Cmds { + err = cmd.Wait() + } + return err +} + +// Stop tries to stop the process. +func (p *Process) Stop() error { + for _, cmd := range p.Cmds { + // ProcessState means it is already exited. + if cmd.ProcessState != nil { + continue + } + + err := cmd.Process.Kill() + if err != nil { + return err + } + } + + return nil +} + +// Output returns the buffered output as []byte. +func (p *Process) Output() ([]byte, error) { + return p.outBuffer.Bytes(), nil +} + +// ErrOutput returns the buffered stderr as []byte +func (p *Process) ErrOutput() ([]byte, error) { + return p.errBuffer.Bytes(), nil +} diff --git a/vendor/github.com/ionrock/procs/process_test.go b/vendor/github.com/ionrock/procs/process_test.go new file mode 100644 index 000000000..d66c5bd66 --- /dev/null +++ b/vendor/github.com/ionrock/procs/process_test.go @@ -0,0 +1,143 @@ +package procs_test + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "testing" + + "github.com/ionrock/procs" +) + +func newProcess() *procs.Process { + return &procs.Process{ + Cmds: []*exec.Cmd{ + exec.Command("echo", "foo"), + exec.Command("grep", "foo"), + }, + } +} + +func TestProcess(t *testing.T) { + p := newProcess() + + err := p.Run() + if err != nil { + t.Fatalf("error running program: %s", err) + } + + out, _ := p.Output() + if !bytes.Equal(bytes.TrimSpace(out), []byte("foo")) { + t.Errorf("wrong output: expected foo but got %s", out) + } +} + +func TestProcessWithOutput(t *testing.T) { + p := newProcess() + + p.OutputHandler = func(line string) string { + return fmt.Sprintf("x | %s", line) + } + + err := p.Run() + + if err != nil { + t.Fatalf("error running program: %s", err) + } + expected := []byte("x | foo") + out, _ := p.Output() + if !bytes.Equal(bytes.TrimSpace(out), expected) { + t.Errorf("wrong output: expected %q but got %q", expected, out) + } +} + +func TestProcessStartAndWait(t *testing.T) { + p := newProcess() + + p.Start() + p.Wait() + + out, _ := p.Output() + expected := []byte("foo") + if !bytes.Equal(bytes.TrimSpace(out), expected) { + t.Errorf("wrong output: expected %q but got %q", expected, out) + } +} + +func TestProcessStartAndWaitWithOutput(t *testing.T) { + p := newProcess() + p.OutputHandler = func(line string) string { + return fmt.Sprintf("x | %s", line) + } + + p.Start() + p.Wait() + + out, _ := p.Output() + expected := []byte("x | foo") + if !bytes.Equal(bytes.TrimSpace(out), expected) { + t.Errorf("wrong output: expected %q but got %q", expected, out) + } +} + +func TestProcessFromString(t *testing.T) { + p := procs.NewProcess("echo 'foo'") + err := p.Run() + if err != nil { + t.Fatalf("error running program: %s", err) + } + out, _ := p.Output() + if !bytes.Equal(bytes.TrimSpace(out), []byte("foo")) { + t.Errorf("wrong output: expected foo but got %s", out) + } +} + +func TestProcessFromStringWithPipe(t *testing.T) { + p := procs.NewProcess("echo 'foo' | grep foo") + err := p.Run() + if err != nil { + t.Fatalf("error running program: %s", err) + } + + out, _ := p.Output() + if !bytes.Equal(bytes.TrimSpace(out), []byte("foo")) { + t.Errorf("wrong output: expected foo but got %s", out) + } +} + +func TestStderrOutput(t *testing.T) { + if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { + return + } + + fmt.Fprintln(os.Stdout, "stdout output") + fmt.Fprintln(os.Stderr, "stderr output") + os.Exit(1) +} + +func TestProcessPipeWithFailures(t *testing.T) { + + // This will run a piped command with a failure part way + // through. We want to be sure we get output on stderr. + p := procs.NewProcess(fmt.Sprintf("echo 'foo' | %s -test.run=TestStderrOutput | grep foo", os.Args[0])) + p.Env = map[string]string{"GO_WANT_HELPER_PROCESS": "1"} + + err := p.Run() + if err == nil { + t.Fatal("expected error running program") + + } + + out, _ := p.Output() + expected := []byte("") // expecting no output b/c the grep foo won't run + if !bytes.Equal(out, expected) { + t.Errorf("wrong stdout output: expected '%s' but got '%s'", expected, out) + } + + errOut, _ := p.ErrOutput() + expected = []byte("stderr output") + if !bytes.Equal(bytes.TrimSpace(errOut), expected) { + t.Errorf("wrong stderr output: expected '%s' but got '%s'", expected, out) + } +} diff --git a/vendor/github.com/kr/pty/License b/vendor/github.com/kr/pty/License new file mode 100644 index 000000000..6b7558b6b --- /dev/null +++ b/vendor/github.com/kr/pty/License @@ -0,0 +1,23 @@ +Copyright (c) 2011 Keith Rarick + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall +be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/kr/pty/README.md b/vendor/github.com/kr/pty/README.md new file mode 100644 index 000000000..f9bb002e0 --- /dev/null +++ b/vendor/github.com/kr/pty/README.md @@ -0,0 +1,100 @@ +# pty + +Pty is a Go package for using unix pseudo-terminals. + +## Install + + go get github.com/kr/pty + +## Example + +### Command + +```go +package main + +import ( + "github.com/kr/pty" + "io" + "os" + "os/exec" +) + +func main() { + c := exec.Command("grep", "--color=auto", "bar") + f, err := pty.Start(c) + if err != nil { + panic(err) + } + + go func() { + f.Write([]byte("foo\n")) + f.Write([]byte("bar\n")) + f.Write([]byte("baz\n")) + f.Write([]byte{4}) // EOT + }() + io.Copy(os.Stdout, f) +} +``` + +### Shell + +```go +package main + +import ( + "io" + "log" + "os" + "os/exec" + "os/signal" + "syscall" + + "github.com/kr/pty" + "golang.org/x/crypto/ssh/terminal" +) + +func test() error { + // Create arbitrary command. + c := exec.Command("bash") + + // Start the command with a pty. + ptmx, err := pty.Start(c) + if err != nil { + return err + } + // Make sure to close the pty at the end. + defer func() { _ = ptmx.Close() }() // Best effort. + + // Handle pty size. + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGWINCH) + go func() { + for range ch { + if err := pty.InheritSize(os.Stdin, ptmx); err != nil { + log.Printf("error resizing pty: %s", err) + } + } + }() + ch <- syscall.SIGWINCH // Initial resize. + + // Set stdin in raw mode. + oldState, err := terminal.MakeRaw(int(os.Stdin.Fd())) + if err != nil { + panic(err) + } + defer func() { _ = terminal.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort. + + // Copy stdin to the pty and the pty to stdout. + go func() { _, _ = io.Copy(ptmx, os.Stdin) }() + _, _ = io.Copy(os.Stdout, ptmx) + + return nil +} + +func main() { + if err := test(); err != nil { + log.Fatal(err) + } +} +``` diff --git a/vendor/github.com/kr/pty/doc.go b/vendor/github.com/kr/pty/doc.go new file mode 100644 index 000000000..190cfbea9 --- /dev/null +++ b/vendor/github.com/kr/pty/doc.go @@ -0,0 +1,16 @@ +// Package pty provides functions for working with Unix terminals. +package pty + +import ( + "errors" + "os" +) + +// ErrUnsupported is returned if a function is not +// available on the current platform. +var ErrUnsupported = errors.New("unsupported") + +// Opens a pty and its corresponding tty. +func Open() (pty, tty *os.File, err error) { + return open() +} diff --git a/vendor/github.com/kr/pty/ioctl.go b/vendor/github.com/kr/pty/ioctl.go new file mode 100644 index 000000000..c57c19e7e --- /dev/null +++ b/vendor/github.com/kr/pty/ioctl.go @@ -0,0 +1,13 @@ +// +build !windows + +package pty + +import "syscall" + +func ioctl(fd, cmd, ptr uintptr) error { + _, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr) + if e != 0 { + return e + } + return nil +} diff --git a/vendor/github.com/kr/pty/ioctl_bsd.go b/vendor/github.com/kr/pty/ioctl_bsd.go new file mode 100644 index 000000000..73b12c53c --- /dev/null +++ b/vendor/github.com/kr/pty/ioctl_bsd.go @@ -0,0 +1,39 @@ +// +build darwin dragonfly freebsd netbsd openbsd + +package pty + +// from +const ( + _IOC_VOID uintptr = 0x20000000 + _IOC_OUT uintptr = 0x40000000 + _IOC_IN uintptr = 0x80000000 + _IOC_IN_OUT uintptr = _IOC_OUT | _IOC_IN + _IOC_DIRMASK = _IOC_VOID | _IOC_OUT | _IOC_IN + + _IOC_PARAM_SHIFT = 13 + _IOC_PARAM_MASK = (1 << _IOC_PARAM_SHIFT) - 1 +) + +func _IOC_PARM_LEN(ioctl uintptr) uintptr { + return (ioctl >> 16) & _IOC_PARAM_MASK +} + +func _IOC(inout uintptr, group byte, ioctl_num uintptr, param_len uintptr) uintptr { + return inout | (param_len&_IOC_PARAM_MASK)<<16 | uintptr(group)<<8 | ioctl_num +} + +func _IO(group byte, ioctl_num uintptr) uintptr { + return _IOC(_IOC_VOID, group, ioctl_num, 0) +} + +func _IOR(group byte, ioctl_num uintptr, param_len uintptr) uintptr { + return _IOC(_IOC_OUT, group, ioctl_num, param_len) +} + +func _IOW(group byte, ioctl_num uintptr, param_len uintptr) uintptr { + return _IOC(_IOC_IN, group, ioctl_num, param_len) +} + +func _IOWR(group byte, ioctl_num uintptr, param_len uintptr) uintptr { + return _IOC(_IOC_IN_OUT, group, ioctl_num, param_len) +} diff --git a/vendor/github.com/kr/pty/mktypes.bash b/vendor/github.com/kr/pty/mktypes.bash new file mode 100755 index 000000000..82ee16721 --- /dev/null +++ b/vendor/github.com/kr/pty/mktypes.bash @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +GOOSARCH="${GOOS}_${GOARCH}" +case "$GOOSARCH" in +_* | *_ | _) + echo 'undefined $GOOS_$GOARCH:' "$GOOSARCH" 1>&2 + exit 1 + ;; +esac + +GODEFS="go tool cgo -godefs" + +$GODEFS types.go |gofmt > ztypes_$GOARCH.go + +case $GOOS in +freebsd|dragonfly|openbsd) + $GODEFS types_$GOOS.go |gofmt > ztypes_$GOOSARCH.go + ;; +esac diff --git a/vendor/github.com/kr/pty/pty_darwin.go b/vendor/github.com/kr/pty/pty_darwin.go new file mode 100644 index 000000000..6344b6b0e --- /dev/null +++ b/vendor/github.com/kr/pty/pty_darwin.go @@ -0,0 +1,65 @@ +package pty + +import ( + "errors" + "os" + "syscall" + "unsafe" +) + +func open() (pty, tty *os.File, err error) { + pFD, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC, 0) + if err != nil { + return nil, nil, err + } + p := os.NewFile(uintptr(pFD), "/dev/ptmx") + // In case of error after this point, make sure we close the ptmx fd. + defer func() { + if err != nil { + _ = p.Close() // Best effort. + } + }() + + sname, err := ptsname(p) + if err != nil { + return nil, nil, err + } + + if err := grantpt(p); err != nil { + return nil, nil, err + } + + if err := unlockpt(p); err != nil { + return nil, nil, err + } + + t, err := os.OpenFile(sname, os.O_RDWR, 0) + if err != nil { + return nil, nil, err + } + return p, t, nil +} + +func ptsname(f *os.File) (string, error) { + n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME)) + + err := ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0]))) + if err != nil { + return "", err + } + + for i, c := range n { + if c == 0 { + return string(n[:i]), nil + } + } + return "", errors.New("TIOCPTYGNAME string not NUL-terminated") +} + +func grantpt(f *os.File) error { + return ioctl(f.Fd(), syscall.TIOCPTYGRANT, 0) +} + +func unlockpt(f *os.File) error { + return ioctl(f.Fd(), syscall.TIOCPTYUNLK, 0) +} diff --git a/vendor/github.com/kr/pty/pty_dragonfly.go b/vendor/github.com/kr/pty/pty_dragonfly.go new file mode 100644 index 000000000..b7d1f20f2 --- /dev/null +++ b/vendor/github.com/kr/pty/pty_dragonfly.go @@ -0,0 +1,80 @@ +package pty + +import ( + "errors" + "os" + "strings" + "syscall" + "unsafe" +) + +// same code as pty_darwin.go +func open() (pty, tty *os.File, err error) { + p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) + if err != nil { + return nil, nil, err + } + // In case of error after this point, make sure we close the ptmx fd. + defer func() { + if err != nil { + _ = p.Close() // Best effort. + } + }() + + sname, err := ptsname(p) + if err != nil { + return nil, nil, err + } + + if err := grantpt(p); err != nil { + return nil, nil, err + } + + if err := unlockpt(p); err != nil { + return nil, nil, err + } + + t, err := os.OpenFile(sname, os.O_RDWR, 0) + if err != nil { + return nil, nil, err + } + return p, t, nil +} + +func grantpt(f *os.File) error { + _, err := isptmaster(f.Fd()) + return err +} + +func unlockpt(f *os.File) error { + _, err := isptmaster(f.Fd()) + return err +} + +func isptmaster(fd uintptr) (bool, error) { + err := ioctl(fd, syscall.TIOCISPTMASTER, 0) + return err == nil, err +} + +var ( + emptyFiodgnameArg fiodgnameArg + ioctl_FIODNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg)) +) + +func ptsname(f *os.File) (string, error) { + name := make([]byte, _C_SPECNAMELEN) + fa := fiodgnameArg{Name: (*byte)(unsafe.Pointer(&name[0])), Len: _C_SPECNAMELEN, Pad_cgo_0: [4]byte{0, 0, 0, 0}} + + err := ioctl(f.Fd(), ioctl_FIODNAME, uintptr(unsafe.Pointer(&fa))) + if err != nil { + return "", err + } + + for i, c := range name { + if c == 0 { + s := "/dev/" + string(name[:i]) + return strings.Replace(s, "ptm", "pts", -1), nil + } + } + return "", errors.New("TIOCPTYGNAME string not NUL-terminated") +} diff --git a/vendor/github.com/kr/pty/pty_freebsd.go b/vendor/github.com/kr/pty/pty_freebsd.go new file mode 100644 index 000000000..63b6d9133 --- /dev/null +++ b/vendor/github.com/kr/pty/pty_freebsd.go @@ -0,0 +1,78 @@ +package pty + +import ( + "errors" + "os" + "syscall" + "unsafe" +) + +func posixOpenpt(oflag int) (fd int, err error) { + r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0) + fd = int(r0) + if e1 != 0 { + err = e1 + } + return fd, err +} + +func open() (pty, tty *os.File, err error) { + fd, err := posixOpenpt(syscall.O_RDWR | syscall.O_CLOEXEC) + if err != nil { + return nil, nil, err + } + p := os.NewFile(uintptr(fd), "/dev/pts") + // In case of error after this point, make sure we close the pts fd. + defer func() { + if err != nil { + _ = p.Close() // Best effort. + } + }() + + sname, err := ptsname(p) + if err != nil { + return nil, nil, err + } + + t, err := os.OpenFile("/dev/"+sname, os.O_RDWR, 0) + if err != nil { + return nil, nil, err + } + return p, t, nil +} + +func isptmaster(fd uintptr) (bool, error) { + err := ioctl(fd, syscall.TIOCPTMASTER, 0) + return err == nil, err +} + +var ( + emptyFiodgnameArg fiodgnameArg + ioctlFIODGNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg)) +) + +func ptsname(f *os.File) (string, error) { + master, err := isptmaster(f.Fd()) + if err != nil { + return "", err + } + if !master { + return "", syscall.EINVAL + } + + const n = _C_SPECNAMELEN + 1 + var ( + buf = make([]byte, n) + arg = fiodgnameArg{Len: n, Buf: (*byte)(unsafe.Pointer(&buf[0]))} + ) + if err := ioctl(f.Fd(), ioctlFIODGNAME, uintptr(unsafe.Pointer(&arg))); err != nil { + return "", err + } + + for i, c := range buf { + if c == 0 { + return string(buf[:i]), nil + } + } + return "", errors.New("FIODGNAME string not NUL-terminated") +} diff --git a/vendor/github.com/kr/pty/pty_linux.go b/vendor/github.com/kr/pty/pty_linux.go new file mode 100644 index 000000000..296dd2129 --- /dev/null +++ b/vendor/github.com/kr/pty/pty_linux.go @@ -0,0 +1,51 @@ +package pty + +import ( + "os" + "strconv" + "syscall" + "unsafe" +) + +func open() (pty, tty *os.File, err error) { + p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) + if err != nil { + return nil, nil, err + } + // In case of error after this point, make sure we close the ptmx fd. + defer func() { + if err != nil { + _ = p.Close() // Best effort. + } + }() + + sname, err := ptsname(p) + if err != nil { + return nil, nil, err + } + + if err := unlockpt(p); err != nil { + return nil, nil, err + } + + t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0) + if err != nil { + return nil, nil, err + } + return p, t, nil +} + +func ptsname(f *os.File) (string, error) { + var n _C_uint + err := ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))) + if err != nil { + return "", err + } + return "/dev/pts/" + strconv.Itoa(int(n)), nil +} + +func unlockpt(f *os.File) error { + var u _C_int + // use TIOCSPTLCK with a zero valued arg to clear the slave pty lock + return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) +} diff --git a/vendor/github.com/kr/pty/pty_openbsd.go b/vendor/github.com/kr/pty/pty_openbsd.go new file mode 100644 index 000000000..6e7aeae7c --- /dev/null +++ b/vendor/github.com/kr/pty/pty_openbsd.go @@ -0,0 +1,33 @@ +package pty + +import ( + "os" + "syscall" + "unsafe" +) + +func open() (pty, tty *os.File, err error) { + /* + * from ptm(4): + * The PTMGET command allocates a free pseudo terminal, changes its + * ownership to the caller, revokes the access privileges for all previous + * users, opens the file descriptors for the master and slave devices and + * returns them to the caller in struct ptmget. + */ + + p, err := os.OpenFile("/dev/ptm", os.O_RDWR|syscall.O_CLOEXEC, 0) + if err != nil { + return nil, nil, err + } + defer p.Close() + + var ptm ptmget + if err := ioctl(p.Fd(), uintptr(ioctl_PTMGET), uintptr(unsafe.Pointer(&ptm))); err != nil { + return nil, nil, err + } + + pty = os.NewFile(uintptr(ptm.Cfd), "/dev/ptm") + tty = os.NewFile(uintptr(ptm.Sfd), "/dev/ptm") + + return pty, tty, nil +} diff --git a/vendor/github.com/kr/pty/pty_unsupported.go b/vendor/github.com/kr/pty/pty_unsupported.go new file mode 100644 index 000000000..9a3e721bc --- /dev/null +++ b/vendor/github.com/kr/pty/pty_unsupported.go @@ -0,0 +1,11 @@ +// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd + +package pty + +import ( + "os" +) + +func open() (pty, tty *os.File, err error) { + return nil, nil, ErrUnsupported +} diff --git a/vendor/github.com/kr/pty/run.go b/vendor/github.com/kr/pty/run.go new file mode 100644 index 000000000..baecca8af --- /dev/null +++ b/vendor/github.com/kr/pty/run.go @@ -0,0 +1,34 @@ +// +build !windows + +package pty + +import ( + "os" + "os/exec" + "syscall" +) + +// Start assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout, +// and c.Stderr, calls c.Start, and returns the File of the tty's +// corresponding pty. +func Start(c *exec.Cmd) (pty *os.File, err error) { + pty, tty, err := Open() + if err != nil { + return nil, err + } + defer tty.Close() + c.Stdout = tty + c.Stdin = tty + c.Stderr = tty + if c.SysProcAttr == nil { + c.SysProcAttr = &syscall.SysProcAttr{} + } + c.SysProcAttr.Setctty = true + c.SysProcAttr.Setsid = true + err = c.Start() + if err != nil { + pty.Close() + return nil, err + } + return pty, err +} diff --git a/vendor/github.com/kr/pty/types.go b/vendor/github.com/kr/pty/types.go new file mode 100644 index 000000000..5aecb6bcd --- /dev/null +++ b/vendor/github.com/kr/pty/types.go @@ -0,0 +1,10 @@ +// +build ignore + +package pty + +import "C" + +type ( + _C_int C.int + _C_uint C.uint +) diff --git a/vendor/github.com/kr/pty/types_dragonfly.go b/vendor/github.com/kr/pty/types_dragonfly.go new file mode 100644 index 000000000..5c0493b85 --- /dev/null +++ b/vendor/github.com/kr/pty/types_dragonfly.go @@ -0,0 +1,17 @@ +// +build ignore + +package pty + +/* +#define _KERNEL +#include +#include +#include +*/ +import "C" + +const ( + _C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */ +) + +type fiodgnameArg C.struct_fiodname_args diff --git a/vendor/github.com/kr/pty/types_freebsd.go b/vendor/github.com/kr/pty/types_freebsd.go new file mode 100644 index 000000000..ce3eb9518 --- /dev/null +++ b/vendor/github.com/kr/pty/types_freebsd.go @@ -0,0 +1,15 @@ +// +build ignore + +package pty + +/* +#include +#include +*/ +import "C" + +const ( + _C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */ +) + +type fiodgnameArg C.struct_fiodgname_arg diff --git a/vendor/github.com/kr/pty/types_openbsd.go b/vendor/github.com/kr/pty/types_openbsd.go new file mode 100644 index 000000000..47701b5f9 --- /dev/null +++ b/vendor/github.com/kr/pty/types_openbsd.go @@ -0,0 +1,14 @@ +// +build ignore + +package pty + +/* +#include +#include +#include +*/ +import "C" + +type ptmget C.struct_ptmget + +var ioctl_PTMGET = C.PTMGET diff --git a/vendor/github.com/kr/pty/util.go b/vendor/github.com/kr/pty/util.go new file mode 100644 index 000000000..68a8584cf --- /dev/null +++ b/vendor/github.com/kr/pty/util.go @@ -0,0 +1,64 @@ +// +build !windows + +package pty + +import ( + "os" + "syscall" + "unsafe" +) + +// InheritSize applies the terminal size of master to slave. This should be run +// in a signal handler for syscall.SIGWINCH to automatically resize the slave when +// the master receives a window size change notification. +func InheritSize(master, slave *os.File) error { + size, err := GetsizeFull(master) + if err != nil { + return err + } + err = Setsize(slave, size) + if err != nil { + return err + } + return nil +} + +// Setsize resizes t to s. +func Setsize(t *os.File, ws *Winsize) error { + return windowRectCall(ws, t.Fd(), syscall.TIOCSWINSZ) +} + +// GetsizeFull returns the full terminal size description. +func GetsizeFull(t *os.File) (size *Winsize, err error) { + var ws Winsize + err = windowRectCall(&ws, t.Fd(), syscall.TIOCGWINSZ) + return &ws, err +} + +// Getsize returns the number of rows (lines) and cols (positions +// in each line) in terminal t. +func Getsize(t *os.File) (rows, cols int, err error) { + ws, err := GetsizeFull(t) + return int(ws.Rows), int(ws.Cols), err +} + +// Winsize describes the terminal size. +type Winsize struct { + Rows uint16 // ws_row: Number of rows (in cells) + Cols uint16 // ws_col: Number of columns (in cells) + X uint16 // ws_xpixel: Width in pixels + Y uint16 // ws_ypixel: Height in pixels +} + +func windowRectCall(ws *Winsize, fd, a2 uintptr) error { + _, _, errno := syscall.Syscall( + syscall.SYS_IOCTL, + fd, + a2, + uintptr(unsafe.Pointer(ws)), + ) + if errno != 0 { + return syscall.Errno(errno) + } + return nil +} diff --git a/vendor/github.com/kr/pty/ztypes_386.go b/vendor/github.com/kr/pty/ztypes_386.go new file mode 100644 index 000000000..ff0b8fd83 --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_386.go @@ -0,0 +1,9 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/github.com/kr/pty/ztypes_amd64.go b/vendor/github.com/kr/pty/ztypes_amd64.go new file mode 100644 index 000000000..ff0b8fd83 --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_amd64.go @@ -0,0 +1,9 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/github.com/kr/pty/ztypes_arm.go b/vendor/github.com/kr/pty/ztypes_arm.go new file mode 100644 index 000000000..ff0b8fd83 --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_arm.go @@ -0,0 +1,9 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/github.com/kr/pty/ztypes_arm64.go b/vendor/github.com/kr/pty/ztypes_arm64.go new file mode 100644 index 000000000..6c29a4b91 --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_arm64.go @@ -0,0 +1,11 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +// +build arm64 + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/github.com/kr/pty/ztypes_dragonfly_amd64.go b/vendor/github.com/kr/pty/ztypes_dragonfly_amd64.go new file mode 100644 index 000000000..6b0ba037f --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_dragonfly_amd64.go @@ -0,0 +1,14 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_dragonfly.go + +package pty + +const ( + _C_SPECNAMELEN = 0x3f +) + +type fiodgnameArg struct { + Name *byte + Len uint32 + Pad_cgo_0 [4]byte +} diff --git a/vendor/github.com/kr/pty/ztypes_freebsd_386.go b/vendor/github.com/kr/pty/ztypes_freebsd_386.go new file mode 100644 index 000000000..d9975374e --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_freebsd_386.go @@ -0,0 +1,13 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_freebsd.go + +package pty + +const ( + _C_SPECNAMELEN = 0x3f +) + +type fiodgnameArg struct { + Len int32 + Buf *byte +} diff --git a/vendor/github.com/kr/pty/ztypes_freebsd_amd64.go b/vendor/github.com/kr/pty/ztypes_freebsd_amd64.go new file mode 100644 index 000000000..5fa102fcd --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_freebsd_amd64.go @@ -0,0 +1,14 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_freebsd.go + +package pty + +const ( + _C_SPECNAMELEN = 0x3f +) + +type fiodgnameArg struct { + Len int32 + Pad_cgo_0 [4]byte + Buf *byte +} diff --git a/vendor/github.com/kr/pty/ztypes_freebsd_arm.go b/vendor/github.com/kr/pty/ztypes_freebsd_arm.go new file mode 100644 index 000000000..d9975374e --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_freebsd_arm.go @@ -0,0 +1,13 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_freebsd.go + +package pty + +const ( + _C_SPECNAMELEN = 0x3f +) + +type fiodgnameArg struct { + Len int32 + Buf *byte +} diff --git a/vendor/github.com/kr/pty/ztypes_mipsx.go b/vendor/github.com/kr/pty/ztypes_mipsx.go new file mode 100644 index 000000000..f0ce74086 --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_mipsx.go @@ -0,0 +1,12 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +// +build linux +// +build mips mipsle mips64 mips64le + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/github.com/kr/pty/ztypes_openbsd_386.go b/vendor/github.com/kr/pty/ztypes_openbsd_386.go new file mode 100644 index 000000000..ccb3aab9a --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_openbsd_386.go @@ -0,0 +1,13 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_openbsd.go + +package pty + +type ptmget struct { + Cfd int32 + Sfd int32 + Cn [16]int8 + Sn [16]int8 +} + +var ioctl_PTMGET = 0x40287401 diff --git a/vendor/github.com/kr/pty/ztypes_openbsd_amd64.go b/vendor/github.com/kr/pty/ztypes_openbsd_amd64.go new file mode 100644 index 000000000..e67051688 --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_openbsd_amd64.go @@ -0,0 +1,13 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_openbsd.go + +package pty + +type ptmget struct { + Cfd int32 + Sfd int32 + Cn [16]int8 + Sn [16]int8 +} + +var ioctl_PTMGET = 0x40287401 diff --git a/vendor/github.com/kr/pty/ztypes_ppc64.go b/vendor/github.com/kr/pty/ztypes_ppc64.go new file mode 100644 index 000000000..4e1af8431 --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_ppc64.go @@ -0,0 +1,11 @@ +// +build ppc64 + +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/github.com/kr/pty/ztypes_ppc64le.go b/vendor/github.com/kr/pty/ztypes_ppc64le.go new file mode 100644 index 000000000..e6780f4e2 --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_ppc64le.go @@ -0,0 +1,11 @@ +// +build ppc64le + +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/github.com/kr/pty/ztypes_s390x.go b/vendor/github.com/kr/pty/ztypes_s390x.go new file mode 100644 index 000000000..a7452b61c --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_s390x.go @@ -0,0 +1,11 @@ +// +build s390x + +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/vendor.json b/vendor/vendor.json new file mode 100644 index 000000000..2147fdee2 --- /dev/null +++ b/vendor/vendor.json @@ -0,0 +1,19 @@ +{ + "comment": "", + "ignore": "", + "package": [ + { + "checksumSHA1": "AcczqNgc2/sR/+4gklvFe8zs2eE=", + "path": "github.com/ionrock/procs", + "revision": "f53ef5630f1a1fd42c4e528a04cdaa81265cc66d", + "revisionTime": "2018-01-02T00:55:58Z" + }, + { + "checksumSHA1": "KU+GT3javo9S9oVEJfqQUKPPUwo=", + "path": "github.com/kr/pty", + "revision": "fa756f09eeb418bf1cc6268c66ceaad9bb98f598", + "revisionTime": "2018-06-20T15:12:22Z" + } + ], + "rootPath": "github.com/jesseduffield/lazygit" +}