diff --git a/common/xfer/pipes.go b/common/xfer/pipes.go index 9510a9d7f..895b2bb33 100644 --- a/common/xfer/pipes.go +++ b/common/xfer/pipes.go @@ -27,7 +27,16 @@ type pipe struct { onClose func() } -// NewPipe makes a new... pipe. +// NewPipeFromEnds makes a new pipe specifying its ends +func NewPipeFromEnds(local io.ReadWriter, remote io.ReadWriter) Pipe { + return &pipe{ + port: local, + starboard: remote, + quit: make(chan struct{}), + } +} + +// NewPipe makes a new pipe func NewPipe() Pipe { r1, w1 := io.Pipe() r2, w2 := io.Pipe() diff --git a/integration/410_container_control_test.sh b/integration/410_container_control_test.sh index 483c18622..b85a57a8b 100755 --- a/integration/410_container_control_test.sh +++ b/integration/410_container_control_test.sh @@ -13,8 +13,6 @@ wait_for_containers $HOST1 60 alpine assert "docker_on $HOST1 inspect --format='{{.State.Running}}' alpine" "true" PROBEID=$(docker_on $HOST1 logs weavescope 2>&1 | grep "probe starting" | sed -n 's/^.*ID \([0-9a-f]*\)$/\1/p') -HOSTID=$(echo $HOST1 | cut -d"." -f1) - # Execute 'echo foo' in a container tty and check its output PIPEID=$(curl -s -f -X POST "http://$HOST1:4040/api/control/$PROBEID/$CID;/docker_exec_container" | jq -r '.pipe' ) diff --git a/integration/420_host_control_test.sh b/integration/420_host_control_test.sh new file mode 100755 index 000000000..54d23314f --- /dev/null +++ b/integration/420_host_control_test.sh @@ -0,0 +1,19 @@ +#! /bin/bash + +. ./config.sh + +start_suite "Test host controls" + +weave_on $HOST1 launch +scope_on $HOST1 launch + +sleep 10 + +PROBEID=$(docker_on $HOST1 logs weavescope 2>&1 | grep "probe starting" | sed -n 's/^.*ID \([0-9a-f]*\)$/\1/p') +HOSTID=$($SSH $HOST1 hostname) + +# Execute 'echo foo' in the host tty and check its output +PIPEID=$(curl -s -f -X POST "http://$HOST1:4040/api/control/$PROBEID/$HOSTID;/host_exec" | jq -r '.pipe' ) +assert "(sleep 1 && echo \"PS1=''; echo foo\" && sleep 1) | wscat -b 'ws://$HOST1:4040/api/pipe/$PIPEID' | col -pb | tail -n 1" "foo\n" + +scope_end_suite diff --git a/probe/controls/pipes.go b/probe/controls/pipes.go index cf540419f..ec4198eed 100644 --- a/probe/controls/pipes.go +++ b/probe/controls/pipes.go @@ -2,6 +2,7 @@ package controls import ( "fmt" + "io" "math/rand" "github.com/weaveworks/scope/common/xfer" @@ -21,11 +22,10 @@ type pipe struct { client PipeClient } -// NewPipe creats a new pipe and connects it to the app. -var NewPipe = func(c PipeClient, appID string) (string, xfer.Pipe, error) { +func newPipe(p xfer.Pipe, c PipeClient, appID string) (string, xfer.Pipe, error) { pipeID := fmt.Sprintf("pipe-%d", rand.Int63()) pipe := &pipe{ - Pipe: xfer.NewPipe(), + Pipe: p, appID: appID, id: pipeID, client: c, @@ -36,6 +36,16 @@ var NewPipe = func(c PipeClient, appID string) (string, xfer.Pipe, error) { return pipeID, pipe, nil } +// NewPipe creates a new pipe and connects it to the app. +var NewPipe = func(c PipeClient, appID string) (string, xfer.Pipe, error) { + return newPipe(xfer.NewPipe(), c, appID) +} + +// NewPipeFromEnds creates a new pipe from its ends and connects it to the app. +func NewPipeFromEnds(local, remote io.ReadWriter, c PipeClient, appID string) (string, xfer.Pipe, error) { + return newPipe(xfer.NewPipeFromEnds(local, remote), c, appID) +} + func (p *pipe) Close() error { err1 := p.Pipe.Close() err2 := p.client.PipeClose(p.appID, p.id) diff --git a/probe/docker/controls.go b/probe/docker/controls.go index 8977523e9..23b8d23ba 100644 --- a/probe/docker/controls.go +++ b/probe/docker/controls.go @@ -10,7 +10,7 @@ import ( "github.com/weaveworks/scope/report" ) -// Control IDs used by the docker intergation. +// Control IDs used by the docker integration. const ( StopContainer = "docker_stop_container" StartContainer = "docker_start_container" diff --git a/probe/docker/reporter.go b/probe/docker/reporter.go index d56a2677e..87b69a606 100644 --- a/probe/docker/reporter.go +++ b/probe/docker/reporter.go @@ -102,7 +102,7 @@ func (r *Reporter) containerTopology(localAddrs []net.IP) report.Topology { }) result.Controls.AddControl(report.Control{ ID: ExecContainer, - Human: "Exec /bin/sh", + Human: "Exec shell", Icon: "fa-terminal", }) diff --git a/probe/host/controls.go b/probe/host/controls.go new file mode 100644 index 000000000..4e67e7a55 --- /dev/null +++ b/probe/host/controls.go @@ -0,0 +1,58 @@ +package host + +import ( + "os/exec" + + log "github.com/Sirupsen/logrus" + "github.com/kr/pty" + + "github.com/weaveworks/scope/common/xfer" + "github.com/weaveworks/scope/probe/controls" +) + +// Control IDs used by the host integration. +const ( + ExecHost = "host_exec" +) + +func (r *Reporter) registerControls() { + controls.Register(ExecHost, r.execHost) +} + +func (*Reporter) deregisterControls() { + controls.Rm(ExecHost) +} + +func (r *Reporter) execHost(req xfer.Request) xfer.Response { + cmd := exec.Command(r.hostShellCmd[0], r.hostShellCmd[1:]...) + cmd.Env = []string{"TERM=xterm"} + ptyPipe, err := pty.Start(cmd) + if err != nil { + return xfer.ResponseError(err) + } + + id, pipe, err := controls.NewPipeFromEnds(nil, ptyPipe, r.pipes, req.AppID) + if err != nil { + return xfer.ResponseError(err) + } + pipe.OnClose(func() { + if err := cmd.Process.Kill(); err != nil { + log.Errorf("Error stopping host shell: %v", err) + } + if err := ptyPipe.Close(); err != nil { + log.Errorf("Error closing host shell's pty: %v", err) + } + log.Info("Host shell closed.") + }) + go func() { + if err := cmd.Wait(); err != nil { + log.Errorf("Error waiting on host shell: %v", err) + } + pipe.Close() + }() + + return xfer.Response{ + Pipe: id, + RawTTY: true, + } +} diff --git a/probe/host/controls_darwin.go b/probe/host/controls_darwin.go new file mode 100644 index 000000000..cae9c363e --- /dev/null +++ b/probe/host/controls_darwin.go @@ -0,0 +1,5 @@ +package host + +func getHostShellCmd() []string { + return []string{"/bin/bash"} +} diff --git a/probe/host/controls_linux.go b/probe/host/controls_linux.go new file mode 100644 index 000000000..f4870c33d --- /dev/null +++ b/probe/host/controls_linux.go @@ -0,0 +1,88 @@ +package host + +import ( + "bytes" + "os/exec" + "strings" + "syscall" + + log "github.com/Sirupsen/logrus" + "github.com/willdonnelly/passwd" +) + +func getHostShellCmd() []string { + if isProbeContainerized() { + // Escape the container namespaces and jump into the ones from + // the host's init process. + // Note: There should be no need to enter into the host network + // and PID namespace because we should already already be there + // but it doesn't hurt. + readPasswdCmd := []string{"/usr/bin/nsenter", "-t1", "-m", "--no-fork", "cat", "/etc/passwd"} + uid, gid, shell := getRootUserDetails(readPasswdCmd) + return []string{ + "/usr/bin/nsenter", "-t1", "-m", "-i", "-n", "-p", "--no-fork", + "--setuid", uid, + "--setgid", gid, + shell, + } + } + + _, _, shell := getRootUserDetails([]string{"cat", "/etc/passwd"}) + return []string{shell} +} + +func getRootUserDetails(readPasswdCmd []string) (uid, gid, shell string) { + uid = "0" + gid = "0" + shell = "/bin/sh" + + cmd := exec.Command(readPasswdCmd[0], readPasswdCmd[1:]...) + cmdBuffer := &bytes.Buffer{} + cmd.Stdout = cmdBuffer + if err := cmd.Run(); err != nil { + log.Warnf( + "getRootUserDetails(): error running read passwd command %q: %s", + strings.Join(readPasswdCmd, " "), + err, + ) + return + } + + entries, err := passwd.ParseReader(cmdBuffer) + if err != nil { + log.Warnf("getRootUserDetails(): error parsing passwd: %s", err) + return + } + + entry, ok := entries["root"] + if !ok { + log.Warnf("getRootUserDetails(): no root entry in passwd") + return + } + + return entry.Uid, entry.Gid, entry.Shell +} + +func isProbeContainerized() bool { + // Figure out whether we are running in a container by checking if our + // mount namespace matches the one from init process. This works + // because, when containerized, the Scope probes run in the host's PID + // namespace (and if they weren't due to a configuration problem, we + // wouldn't have a way to escape the container anyhow). + var statT syscall.Stat_t + + path := "/proc/self/ns/mnt" + if err := syscall.Stat(path, &statT); err != nil { + log.Warnf("isProbeContainerized(): stat() error on %q: %s", path, err) + return false + } + selfMountNamespaceID := statT.Ino + + path = "/proc/1/ns/mnt" + if err := syscall.Stat(path, &statT); err != nil { + log.Warnf("isProbeContainerized(): stat() error on %q: %s", path, err) + return false + } + + return selfMountNamespaceID != statT.Ino +} diff --git a/probe/host/reporter.go b/probe/host/reporter.go index 5d20e3fcf..04019d376 100644 --- a/probe/host/reporter.go +++ b/probe/host/reporter.go @@ -6,6 +6,7 @@ import ( "time" "github.com/weaveworks/scope/common/mtime" + "github.com/weaveworks/scope/probe/controls" "github.com/weaveworks/scope/report" ) @@ -34,17 +35,25 @@ const ( // Reporter generates Reports containing the host topology. type Reporter struct { - hostID string - hostName string + hostID string + hostName string + probeID string + pipes controls.PipeClient + hostShellCmd []string } // NewReporter returns a Reporter which produces a report containing host // topology for this host. -func NewReporter(hostID, hostName string) *Reporter { - return &Reporter{ - hostID: hostID, - hostName: hostName, +func NewReporter(hostID, hostName, probeID string, pipes controls.PipeClient) *Reporter { + r := &Reporter{ + hostID: hostID, + hostName: hostName, + probeID: probeID, + pipes: pipes, + hostShellCmd: getHostShellCmd(), } + r.registerControls() + return r } // Name of this reporter, for metrics gathering @@ -98,6 +107,7 @@ func (r *Reporter) Report() (report.Report, error) { memoryUsage, max := GetMemoryUsageBytes() metrics[MemoryUsage] = report.MakeMetric().Add(now, memoryUsage).WithMax(max) + metadata := map[string]string{report.ControlProbeID: r.probeID} rep.Host.AddNode(report.MakeHostNodeID(r.hostID), report.MakeNodeWith(map[string]string{ Timestamp: mtime.Now().UTC().Format(time.RFC3339Nano), HostName: r.hostName, @@ -106,7 +116,18 @@ func (r *Reporter) Report() (report.Report, error) { Uptime: uptime.String(), }).WithSets(report.EmptySets. Add(LocalNetworks, report.MakeStringSet(localCIDRs...)), - ).WithMetrics(metrics)) + ).WithMetrics(metrics).WithControls(ExecHost).WithLatests(metadata)) + + rep.Host.Controls.AddControl(report.Control{ + ID: ExecHost, + Human: "Exec shell", + Icon: "fa-terminal", + }) return rep, nil } + +// Stop stops the reporter. +func (r *Reporter) Stop() { + r.deregisterControls() +} diff --git a/probe/host/reporter_test.go b/probe/host/reporter_test.go index 4bf7ba2e1..3933c1e27 100644 --- a/probe/host/reporter_test.go +++ b/probe/host/reporter_test.go @@ -18,6 +18,7 @@ func TestReporter(t *testing.T) { network = "192.168.0.0/16" hostID = "hostid" hostname = "hostname" + probeID = "abcdeadbeef" timestamp = time.Now() metrics = report.Metrics{ host.Load1: report.MakeMetric().Add(timestamp, 1.0), @@ -57,7 +58,7 @@ func TestReporter(t *testing.T) { host.GetMemoryUsageBytes = func() (float64, float64) { return 60.0, 100.0 } host.GetLocalNetworks = func() ([]*net.IPNet, error) { return []*net.IPNet{ipnet}, nil } - rpt, err := host.NewReporter(hostID, hostname).Report() + rpt, err := host.NewReporter(hostID, hostname, probeID, nil).Report() if err != nil { t.Fatal(err) } diff --git a/prog/probe.go b/prog/probe.go index ccb7f10e6..7ae166747 100644 --- a/prog/probe.go +++ b/prog/probe.go @@ -140,9 +140,11 @@ func probeMain() { p := probe.New(*spyInterval, *publishInterval, clients) p.AddTicker(processCache) + hostReporter := host.NewReporter(hostID, hostName, probeID, clients) + defer hostReporter.Stop() p.AddReporter( endpointReporter, - host.NewReporter(hostID, hostName), + hostReporter, process.NewReporter(processCache, hostID, process.GetDeltaTotalJiffies), ) p.AddTagger(probe.NewTopologyTagger(), host.NewTagger(hostID)) 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..7b7900c3a --- /dev/null +++ b/vendor/github.com/kr/pty/README.md @@ -0,0 +1,36 @@ +# pty + +Pty is a Go package for using unix pseudo-terminals. + +## Install + + go get github.com/kr/pty + +## Example + +```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) +} +``` 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..5b856e871 --- /dev/null +++ b/vendor/github.com/kr/pty/ioctl.go @@ -0,0 +1,11 @@ +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 100644 index 000000000..9952c8883 --- /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) + $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..4f4d5ca26 --- /dev/null +++ b/vendor/github.com/kr/pty/pty_darwin.go @@ -0,0 +1,60 @@ +package pty + +import ( + "errors" + "os" + "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 + } + + sname, err := ptsname(p) + if err != nil { + return nil, nil, err + } + + err = grantpt(p) + if err != nil { + return nil, nil, err + } + + err = unlockpt(p) + if 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_freebsd.go b/vendor/github.com/kr/pty/pty_freebsd.go new file mode 100644 index 000000000..b341babd0 --- /dev/null +++ b/vendor/github.com/kr/pty/pty_freebsd.go @@ -0,0 +1,73 @@ +package pty + +import ( + "errors" + "os" + "syscall" + "unsafe" +) + +func posix_openpt(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 +} + +func open() (pty, tty *os.File, err error) { + fd, err := posix_openpt(syscall.O_RDWR | syscall.O_CLOEXEC) + if err != nil { + return nil, nil, err + } + + p := os.NewFile(uintptr(fd), "/dev/pts") + 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 + ioctl_FIODGNAME = _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]))} + ) + err = ioctl(f.Fd(), ioctl_FIODGNAME, uintptr(unsafe.Pointer(&arg))) + if 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..cb901a21e --- /dev/null +++ b/vendor/github.com/kr/pty/pty_linux.go @@ -0,0 +1,46 @@ +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 + } + + sname, err := ptsname(p) + if err != nil { + return nil, nil, err + } + + err = unlockpt(p) + if 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_unsupported.go b/vendor/github.com/kr/pty/pty_unsupported.go new file mode 100644 index 000000000..898c7303c --- /dev/null +++ b/vendor/github.com/kr/pty/pty_unsupported.go @@ -0,0 +1,11 @@ +// +build !linux,!darwin,!freebsd + +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..c2bc48878 --- /dev/null +++ b/vendor/github.com/kr/pty/run.go @@ -0,0 +1,32 @@ +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_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/util.go b/vendor/github.com/kr/pty/util.go new file mode 100644 index 000000000..67c52d06c --- /dev/null +++ b/vendor/github.com/kr/pty/util.go @@ -0,0 +1,35 @@ +package pty + +import ( + "os" + "syscall" + "unsafe" +) + +// 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) { + var ws winsize + err = windowrect(&ws, t.Fd()) + return int(ws.ws_row), int(ws.ws_col), err +} + +type winsize struct { + ws_row uint16 + ws_col uint16 + ws_xpixel uint16 + ws_ypixel uint16 +} + +func windowrect(ws *winsize, fd uintptr) error { + _, _, errno := syscall.Syscall( + syscall.SYS_IOCTL, + fd, + syscall.TIOCGWINSZ, + 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_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_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/github.com/willdonnelly/passwd/README b/vendor/github.com/willdonnelly/passwd/README new file mode 100644 index 000000000..0024c0a4f --- /dev/null +++ b/vendor/github.com/willdonnelly/passwd/README @@ -0,0 +1,26 @@ +> godoc github.com/willdonnelly/passwd + +PACKAGE + +package passwd + import "github.com/willdonnelly/passwd" + + +FUNCTIONS + +func Parse() (map[string]Entry, error) + Parse opens the '/etc/passwd' file and parses it into a map from + usernames to Entries + + +TYPES + +type Entry struct { + Pass string + Uid string + Gid string + Gecos string + Home string + Shell string +} + An Entry contains all the fields for a specific user diff --git a/vendor/github.com/willdonnelly/passwd/passwd.go b/vendor/github.com/willdonnelly/passwd/passwd.go new file mode 100644 index 000000000..de9f6b462 --- /dev/null +++ b/vendor/github.com/willdonnelly/passwd/passwd.go @@ -0,0 +1,71 @@ +// Package passwd parsed content and files in form of /etc/passwd +package passwd + +import ( + "bufio" + "errors" + "io" + "os" + "strings" +) + +// An Entry contains all the fields for a specific user +type Entry struct { + Pass string + Uid string + Gid string + Gecos string + Home string + Shell string +} + +// Parse opens the '/etc/passwd' file and parses it into a map from usernames +// to Entries +func Parse() (map[string]Entry, error) { + return ParseFile("/etc/passwd") +} + +// ParseFile opens the file and parses it into a map from usernames to Entries +func ParseFile(path string) (map[string]Entry, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + + defer file.Close() + + return ParseReader(file) +} + +// ParseReader consumes the contents of r and parses it into a map from +// usernames to Entries +func ParseReader(r io.Reader) (map[string]Entry, error) { + lines := bufio.NewReader(r) + entries := make(map[string]Entry) + for { + line, _, err := lines.ReadLine() + if err != nil { + break + } + name, entry, err := parseLine(string(copyBytes(line))) + if err != nil { + return nil, err + } + entries[name] = entry + } + return entries, nil +} + +func parseLine(line string) (string, Entry, error) { + fs := strings.Split(line, ":") + if len(fs) != 7 { + return "", Entry{}, errors.New("Unexpected number of fields in /etc/passwd") + } + return fs[0], Entry{fs[1], fs[2], fs[3], fs[4], fs[5], fs[6]}, nil +} + +func copyBytes(x []byte) []byte { + y := make([]byte, len(x)) + copy(y, x) + return y +} diff --git a/vendor/manifest b/vendor/manifest index 3f70106c0..99077b18e 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -681,6 +681,12 @@ "revision": "77ed1c8a01217656d2080ad51981f6e99adaa177", "branch": "master" }, + { + "importpath": "github.com/kr/pty", + "repository": "https://github.com/kr/pty", + "revision": "f7ee69f31298ecbe5d2b349c711e2547a617d398", + "branch": "master" + }, { "importpath": "github.com/lsegal/gucumber", "repository": "https://github.com/lsegal/gucumber", @@ -871,6 +877,12 @@ "branch": "master", "path": "/common" }, + { + "importpath": "github.com/willdonnelly/passwd", + "repository": "https://github.com/willdonnelly/passwd", + "revision": "7935dab3074ca1d47c8805e0230f8685116b6019", + "branch": "master" + }, { "importpath": "golang.org/x/crypto/curve25519", "repository": "https://go.googlesource.com/crypto", @@ -1199,4 +1211,4 @@ "branch": "master" } ] -} +} \ No newline at end of file