summaryrefslogtreecommitdiff
path: root/vendor/github.com/projectatomic/buildah/run.go
diff options
context:
space:
mode:
authorDaniel J Walsh <dwalsh@redhat.com>2018-05-23 14:15:54 -0400
committerAtomic Bot <atomic-devel@projectatomic.io>2018-05-23 22:38:17 +0000
commit915364034f1ddf036d277830d45c54b8eb39f940 (patch)
tree968ec9dfe5a69626a69aef545a5dd429ae695cb1 /vendor/github.com/projectatomic/buildah/run.go
parentd252fa710e55fde35824dfe1f01e03e783f04a18 (diff)
downloadpodman-915364034f1ddf036d277830d45c54b8eb39f940.tar.gz
podman-915364034f1ddf036d277830d45c54b8eb39f940.tar.bz2
podman-915364034f1ddf036d277830d45c54b8eb39f940.zip
Update podman build to match buildah bud functionality
Add --label, --annotations, --idfile, --squash Signed-off-by: Daniel J Walsh <dwalsh@redhat.com> Closes: #824 Approved by: TomSweeneyRedHat
Diffstat (limited to 'vendor/github.com/projectatomic/buildah/run.go')
-rw-r--r--vendor/github.com/projectatomic/buildah/run.go652
1 files changed, 621 insertions, 31 deletions
diff --git a/vendor/github.com/projectatomic/buildah/run.go b/vendor/github.com/projectatomic/buildah/run.go
index 50edc9434..0af21b7f0 100644
--- a/vendor/github.com/projectatomic/buildah/run.go
+++ b/vendor/github.com/projectatomic/buildah/run.go
@@ -2,16 +2,23 @@ package buildah
import (
"bufio"
+ "bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
+ "net"
"os"
"os/exec"
"path/filepath"
+ "strconv"
"strings"
+ "sync"
+ "syscall"
+ "time"
"github.com/containers/storage/pkg/ioutils"
+ "github.com/containers/storage/pkg/reexec"
"github.com/docker/docker/profiles/seccomp"
units "github.com/docker/go-units"
digest "github.com/opencontainers/go-digest"
@@ -22,6 +29,7 @@ import (
"github.com/projectatomic/libpod/pkg/secrets"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh/terminal"
+ "golang.org/x/sys/unix"
)
const (
@@ -29,12 +37,17 @@ const (
DefaultWorkingDir = "/"
// DefaultRuntime is the default command to use to run the container.
DefaultRuntime = "runc"
+ // runUsingRuntimeCommand is a command we use as a key for reexec
+ runUsingRuntimeCommand = Package + "-runtime"
)
+// TerminalPolicy takes the value DefaultTerminal, WithoutTerminal, or WithTerminal.
+type TerminalPolicy int
+
const (
// DefaultTerminal indicates that this Run invocation should be
// connected to a pseudoterminal if we're connected to a terminal.
- DefaultTerminal = iota
+ DefaultTerminal TerminalPolicy = iota
// WithoutTerminal indicates that this Run invocation should NOT be
// connected to a pseudoterminal.
WithoutTerminal
@@ -43,6 +56,19 @@ const (
WithTerminal
)
+// String converts a TerminalPoliicy into a string.
+func (t TerminalPolicy) String() string {
+ switch t {
+ case DefaultTerminal:
+ return "DefaultTerminal"
+ case WithoutTerminal:
+ return "WithoutTerminal"
+ case WithTerminal:
+ return "WithTerminal"
+ }
+ return fmt.Sprintf("unrecognized terminal setting %d", t)
+}
+
// RunOptions can be used to alter how a command is run in the container.
type RunOptions struct {
// Hostname is the hostname we set for the running container.
@@ -72,7 +98,7 @@ type RunOptions struct {
// terminal is used if os.Stdout is connected to a terminal, but that
// decision can be overridden by specifying either WithTerminal or
// WithoutTerminal.
- Terminal int
+ Terminal TerminalPolicy
// Quiet tells the run to turn off output to stdout.
Quiet bool
}
@@ -114,7 +140,7 @@ func addHostsToFile(hosts []string, filename string) error {
}
func addCommonOptsToSpec(commonOpts *CommonBuildOptions, g *generate.Generator) error {
- // RESOURCES - CPU
+ // Resources - CPU
if commonOpts.CPUPeriod != 0 {
g.SetLinuxResourcesCPUPeriod(commonOpts.CPUPeriod)
}
@@ -131,7 +157,7 @@ func addCommonOptsToSpec(commonOpts *CommonBuildOptions, g *generate.Generator)
g.SetLinuxResourcesCPUMems(commonOpts.CPUSetMems)
}
- // RESOURCES - MEMORY
+ // Resources - Memory
if commonOpts.Memory != 0 {
g.SetLinuxResourcesMemoryLimit(commonOpts.Memory)
}
@@ -139,22 +165,21 @@ func addCommonOptsToSpec(commonOpts *CommonBuildOptions, g *generate.Generator)
g.SetLinuxResourcesMemorySwap(commonOpts.MemorySwap)
}
+ // cgroup membership
if commonOpts.CgroupParent != "" {
g.SetLinuxCgroupsPath(commonOpts.CgroupParent)
}
+ // Other process resource limits
if err := addRlimits(commonOpts.Ulimit, g); err != nil {
return err
}
- if err := addHostsToFile(commonOpts.AddHost, "/etc/hosts"); err != nil {
- return err
- }
- logrus.Debugln("Resources:", commonOpts)
+ logrus.Debugf("Resources: %#v", commonOpts)
return nil
}
-func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts []specs.Mount, bindFiles, builtinVolumes, volumeMounts []string, shmSize string) error {
+func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts []specs.Mount, bindFiles map[string]string, builtinVolumes, volumeMounts []string, shmSize string) error {
// The passed-in mounts matter the most to us.
mounts := make([]specs.Mount, len(optionMounts))
copy(mounts, optionMounts)
@@ -179,14 +204,14 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts
mounts = append(mounts, specMount)
}
// Add bind mounts for important files, unless they conflict.
- for _, boundFile := range bindFiles {
- if haveMount(boundFile) {
+ for dest, src := range bindFiles {
+ if haveMount(dest) {
// Already have something to mount there, so skip this one.
continue
}
mounts = append(mounts, specs.Mount{
- Source: boundFile,
- Destination: boundFile,
+ Source: src,
+ Destination: dest,
Type: "bind",
Options: []string{"rbind", "ro"},
})
@@ -293,6 +318,28 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts
return nil
}
+// addNetworkConfig copies files from host and sets them up to bind mount into container
+func (b *Builder) addNetworkConfig(rdir, hostPath string) (string, error) {
+ stat, err := os.Stat(hostPath)
+ if err != nil {
+ return "", errors.Wrapf(err, "stat %q failed", hostPath)
+ }
+
+ buf, err := ioutil.ReadFile(hostPath)
+ if err != nil {
+ return "", errors.Wrapf(err, "opening %q failed", hostPath)
+ }
+ cfile := filepath.Join(rdir, filepath.Base(hostPath))
+ if err := ioutil.WriteFile(cfile, buf, stat.Mode()); err != nil {
+ return "", errors.Wrapf(err, "opening %q failed", cfile)
+ }
+ if err = label.Relabel(cfile, b.MountLabel, false); err != nil {
+ return "", errors.Wrapf(err, "error relabeling %q in container %q", cfile, b.ContainerID)
+ }
+
+ return cfile, nil
+}
+
// Run runs the specified command in the container's root filesystem.
func (b *Builder) Run(command []string, options RunOptions) error {
var user specs.User
@@ -399,10 +446,10 @@ func (b *Builder) Run(command []string, options RunOptions) error {
return errors.Wrapf(err, "error ensuring working directory %q exists", spec.Process.Cwd)
}
- //Security Opts
+ // Set the apparmor profile name.
g.SetProcessApparmorProfile(b.CommonBuildOpts.ApparmorProfile)
- // HANDLE SECCOMP
+ // Set the seccomp configuration using the specified profile name.
if b.CommonBuildOpts.SeccompProfilePath != "unconfined" {
if b.CommonBuildOpts.SeccompProfilePath != "" {
seccompProfile, err := ioutil.ReadFile(b.CommonBuildOpts.SeccompProfilePath)
@@ -430,37 +477,580 @@ func (b *Builder) Run(command []string, options RunOptions) error {
Options: []string{"nosuid", "noexec", "nodev", "relatime", "ro"},
}
g.AddMount(cgroupMnt)
+ hostFile, err := b.addNetworkConfig(path, "/etc/hosts")
+ if err != nil {
+ return err
+ }
+ resolvFile, err := b.addNetworkConfig(path, "/etc/resolv.conf")
+ if err != nil {
+ return err
+ }
- bindFiles := []string{"/etc/hosts", "/etc/resolv.conf"}
+ if err := addHostsToFile(b.CommonBuildOpts.AddHost, hostFile); err != nil {
+ return err
+ }
+
+ bindFiles := map[string]string{
+ "/etc/hosts": hostFile,
+ "/etc/resolv.conf": resolvFile,
+ }
err = b.setupMounts(mountPoint, spec, options.Mounts, bindFiles, b.Volumes(), b.CommonBuildOpts.Volumes, b.CommonBuildOpts.ShmSize)
if err != nil {
return errors.Wrapf(err, "error resolving mountpoints for container")
}
- specbytes, err := json.Marshal(spec)
+ return b.runUsingRuntimeSubproc(options, spec, mountPoint, path, Package+"-"+filepath.Base(path))
+}
+
+type runUsingRuntimeSubprocOptions struct {
+ Options RunOptions
+ Spec *specs.Spec
+ RootPath string
+ BundlePath string
+ ContainerName string
+}
+
+func (b *Builder) runUsingRuntimeSubproc(options RunOptions, spec *specs.Spec, rootPath, bundlePath, containerName string) (err error) {
+ var confwg sync.WaitGroup
+ config, conferr := json.Marshal(runUsingRuntimeSubprocOptions{
+ Options: options,
+ Spec: spec,
+ RootPath: rootPath,
+ BundlePath: bundlePath,
+ ContainerName: containerName,
+ })
+ if conferr != nil {
+ return errors.Wrapf(conferr, "error encoding configuration for %q", runUsingRuntimeCommand)
+ }
+ cmd := reexec.Command(runUsingRuntimeCommand)
+ cmd.Dir = bundlePath
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.Env = append(os.Environ(), fmt.Sprintf("LOGLEVEL=%d", logrus.GetLevel()))
+ preader, pwriter, err := os.Pipe()
if err != nil {
- return err
+ return errors.Wrapf(err, "error creating configuration pipe")
+ }
+ confwg.Add(1)
+ go func() {
+ _, conferr = io.Copy(pwriter, bytes.NewReader(config))
+ confwg.Done()
+ }()
+ cmd.ExtraFiles = append([]*os.File{preader}, cmd.ExtraFiles...)
+ defer preader.Close()
+ defer pwriter.Close()
+ err = cmd.Run()
+ confwg.Wait()
+ if err == nil {
+ return conferr
}
- err = ioutils.AtomicWriteFile(filepath.Join(path, "config.json"), specbytes, 0600)
+ return err
+}
+
+func init() {
+ reexec.Register(runUsingRuntimeCommand, runUsingRuntimeMain)
+}
+
+func runUsingRuntimeMain() {
+ var options runUsingRuntimeSubprocOptions
+ // Set logging.
+ if level := os.Getenv("LOGLEVEL"); level != "" {
+ if ll, err := strconv.Atoi(level); err == nil {
+ logrus.SetLevel(logrus.Level(ll))
+ }
+ }
+ // Unpack our configuration.
+ confPipe := os.NewFile(3, "confpipe")
+ if confPipe == nil {
+ fmt.Fprintf(os.Stderr, "error reading options pipe\n")
+ os.Exit(1)
+ }
+ defer confPipe.Close()
+ if err := json.NewDecoder(confPipe).Decode(&options); err != nil {
+ fmt.Fprintf(os.Stderr, "error decoding options: %v\n", err)
+ os.Exit(1)
+ }
+ // Set ourselves up to read the container's exit status. We're doing this in a child process
+ // so that we won't mess with the setting in a caller of the library.
+ if err := unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, uintptr(1), 0, 0, 0); err != nil {
+ fmt.Fprintf(os.Stderr, "prctl(PR_SET_CHILD_SUBREAPER, 1): %v\n", err)
+ os.Exit(1)
+ }
+ // Run the container, start to finish.
+ status, err := runUsingRuntime(options.Options, options.Spec, options.RootPath, options.BundlePath, options.ContainerName)
if err != nil {
- return errors.Wrapf(err, "error storing runtime configuration")
+ fmt.Fprintf(os.Stderr, "error running container: %v\n", err)
+ os.Exit(1)
+ }
+ // Pass the container's exit status back to the caller by exiting with the same status.
+ if status.Exited() {
+ os.Exit(status.ExitStatus())
+ } else if status.Signaled() {
+ fmt.Fprintf(os.Stderr, "container exited on %s\n", status.Signal())
+ os.Exit(1)
}
+ os.Exit(1)
+}
+
+func runUsingRuntime(options RunOptions, spec *specs.Spec, rootPath, bundlePath, containerName string) (wstatus unix.WaitStatus, err error) {
+ // Write the runtime configuration.
+ specbytes, err := json.Marshal(spec)
+ if err != nil {
+ return 1, err
+ }
+ if err = ioutils.AtomicWriteFile(filepath.Join(bundlePath, "config.json"), specbytes, 0600); err != nil {
+ return 1, errors.Wrapf(err, "error storing runtime configuration")
+ }
+
logrus.Debugf("config = %v", string(specbytes))
+
+ // Decide which runtime to use.
runtime := options.Runtime
if runtime == "" {
runtime = DefaultRuntime
}
- args := append(options.Args, "run", "-b", path, Package+"-"+b.ContainerID)
- cmd := exec.Command(runtime, args...)
- cmd.Dir = mountPoint
- cmd.Stdin = os.Stdin
- cmd.Stdout = os.Stdout
- if options.Quiet {
- cmd.Stdout = nil
+
+ // Default to not specifying a console socket location.
+ moreCreateArgs := func() []string { return nil }
+ // Default to just passing down our stdio.
+ getCreateStdio := func() (*os.File, *os.File, *os.File) { return os.Stdin, os.Stdout, os.Stderr }
+
+ // Figure out how we're doing stdio handling, and create pipes and sockets.
+ var stdio sync.WaitGroup
+ var consoleListener *net.UnixListener
+ stdioPipe := make([][]int, 3)
+ copyConsole := false
+ copyStdio := false
+ finishCopy := make([]int, 2)
+ if err = unix.Pipe(finishCopy); err != nil {
+ return 1, errors.Wrapf(err, "error creating pipe for notifying to stop stdio")
}
- cmd.Stderr = os.Stderr
- err = cmd.Run()
+ finishedCopy := make(chan struct{})
+ if spec.Process != nil {
+ if spec.Process.Terminal {
+ copyConsole = true
+ // Create a listening socket for accepting the container's terminal's PTY master.
+ socketPath := filepath.Join(bundlePath, "console.sock")
+ consoleListener, err = net.ListenUnix("unix", &net.UnixAddr{Name: socketPath, Net: "unix"})
+ if err != nil {
+ return 1, errors.Wrapf(err, "error creating socket to receive terminal descriptor")
+ }
+ // Add console socket arguments.
+ moreCreateArgs = func() []string { return []string{"--console-socket", socketPath} }
+ } else {
+ copyStdio = true
+ // Create pipes to use for relaying stdio.
+ for i := range stdioPipe {
+ stdioPipe[i] = make([]int, 2)
+ if err = unix.Pipe(stdioPipe[i]); err != nil {
+ return 1, errors.Wrapf(err, "error creating pipe for container FD %d", i)
+ }
+ }
+ // Set stdio to our pipes.
+ getCreateStdio = func() (*os.File, *os.File, *os.File) {
+ stdin := os.NewFile(uintptr(stdioPipe[unix.Stdin][0]), "/dev/stdin")
+ stdout := os.NewFile(uintptr(stdioPipe[unix.Stdout][1]), "/dev/stdout")
+ stderr := os.NewFile(uintptr(stdioPipe[unix.Stderr][1]), "/dev/stderr")
+ return stdin, stdout, stderr
+ }
+ }
+ } else {
+ if options.Quiet {
+ // Discard stdout.
+ getCreateStdio = func() (*os.File, *os.File, *os.File) {
+ return os.Stdin, nil, os.Stderr
+ }
+ }
+ }
+
+ // Build the commands that we'll execute.
+ pidFile := filepath.Join(bundlePath, "pid")
+ args := append(append(append(options.Args, "create", "--bundle", bundlePath, "--pid-file", pidFile), moreCreateArgs()...), containerName)
+ create := exec.Command(runtime, args...)
+ create.Dir = bundlePath
+ stdin, stdout, stderr := getCreateStdio()
+ create.Stdin, create.Stdout, create.Stderr = stdin, stdout, stderr
+ if create.SysProcAttr == nil {
+ create.SysProcAttr = &syscall.SysProcAttr{}
+ }
+ runSetDeathSig(create)
+
+ args = append(options.Args, "start", containerName)
+ start := exec.Command(runtime, args...)
+ start.Dir = bundlePath
+ start.Stderr = os.Stderr
+ runSetDeathSig(start)
+
+ args = append(options.Args, "kill", containerName)
+ kill := exec.Command(runtime, args...)
+ kill.Dir = bundlePath
+ kill.Stderr = os.Stderr
+ runSetDeathSig(kill)
+
+ args = append(options.Args, "delete", containerName)
+ del := exec.Command(runtime, args...)
+ del.Dir = bundlePath
+ del.Stderr = os.Stderr
+ runSetDeathSig(del)
+
+ // Actually create the container.
+ err = create.Run()
if err != nil {
- logrus.Debugf("error running runc %v: %v", spec.Process.Args, err)
+ return 1, errors.Wrapf(err, "error creating container for %v", spec.Process.Args)
+ }
+ defer func() {
+ err2 := del.Run()
+ if err2 != nil {
+ if err == nil {
+ err = errors.Wrapf(err2, "error deleting container")
+ } else {
+ logrus.Infof("error deleting container: %v", err2)
+ }
+ }
+ }()
+
+ // Make sure we read the container's exit status when it exits.
+ pidValue, err := ioutil.ReadFile(pidFile)
+ if err != nil {
+ return 1, errors.Wrapf(err, "error reading pid from %q", pidFile)
+ }
+ pid, err := strconv.Atoi(strings.TrimSpace(string(pidValue)))
+ if err != nil {
+ return 1, errors.Wrapf(err, "error parsing pid %s as a number", string(pidValue))
+ }
+ var reaping sync.WaitGroup
+ reaping.Add(1)
+ go func() {
+ defer reaping.Done()
+ var err error
+ _, err = unix.Wait4(pid, &wstatus, 0, nil)
+ if err != nil {
+ wstatus = 0
+ logrus.Errorf("error waiting for container child process: %v\n", err)
+ }
+ }()
+
+ if copyStdio {
+ // We don't need the ends of the pipes that belong to the container.
+ stdin.Close()
+ if stdout != nil {
+ stdout.Close()
+ }
+ stderr.Close()
+ }
+
+ // Handle stdio for the container in the background.
+ stdio.Add(1)
+ go runCopyStdio(&stdio, copyStdio, stdioPipe, copyConsole, consoleListener, finishCopy, finishedCopy)
+
+ // Start the container.
+ err = start.Run()
+ if err != nil {
+ return 1, errors.Wrapf(err, "error starting container")
+ }
+ stopped := false
+ defer func() {
+ if !stopped {
+ err2 := kill.Run()
+ if err2 != nil {
+ if err == nil {
+ err = errors.Wrapf(err2, "error stopping container")
+ } else {
+ logrus.Infof("error stopping container: %v", err2)
+ }
+ }
+ }
+ }()
+
+ // Wait for the container to exit.
+ for {
+ now := time.Now()
+ var state specs.State
+ args = append(options.Args, "state", containerName)
+ stat := exec.Command(runtime, args...)
+ stat.Dir = bundlePath
+ stat.Stderr = os.Stderr
+ stateOutput, stateErr := stat.Output()
+ if stateErr != nil {
+ return 1, errors.Wrapf(stateErr, "error reading container state")
+ }
+ if err = json.Unmarshal(stateOutput, &state); err != nil {
+ return 1, errors.Wrapf(stateErr, "error parsing container state %q", string(stateOutput))
+ }
+ switch state.Status {
+ case "running":
+ case "stopped":
+ stopped = true
+ default:
+ return 1, errors.Errorf("container status unexpectedly changed to %q", state.Status)
+ }
+ if stopped {
+ break
+ }
+ select {
+ case <-finishedCopy:
+ stopped = true
+ case <-time.After(time.Until(now.Add(100 * time.Millisecond))):
+ continue
+ }
+ if stopped {
+ break
+ }
+ }
+
+ // Close the writing end of the stop-handling-stdio notification pipe.
+ unix.Close(finishCopy[1])
+ // Wait for the stdio copy goroutine to flush.
+ stdio.Wait()
+ // Wait until we finish reading the exit status.
+ reaping.Wait()
+
+ return wstatus, nil
+}
+
+func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copyConsole bool, consoleListener *net.UnixListener, finishCopy []int, finishedCopy chan struct{}) {
+ defer func() {
+ unix.Close(finishCopy[0])
+ if copyStdio {
+ unix.Close(stdioPipe[unix.Stdin][1])
+ unix.Close(stdioPipe[unix.Stdout][0])
+ unix.Close(stdioPipe[unix.Stderr][0])
+ }
+ stdio.Done()
+ finishedCopy <- struct{}{}
+ }()
+ // If we're not doing I/O handling, we're done.
+ if !copyConsole && !copyStdio {
+ return
+ }
+ terminalFD := -1
+ if copyConsole {
+ // Accept a connection over our listening socket.
+ fd, err := runAcceptTerminal(consoleListener)
+ if err != nil {
+ logrus.Errorf("%v", err)
+ return
+ }
+ terminalFD = fd
+ // Set our terminal's mode to raw, to pass handling of special
+ // terminal input to the terminal in the container.
+ state, err := terminal.MakeRaw(unix.Stdin)
+ if err != nil {
+ logrus.Warnf("error setting terminal state: %v", err)
+ } else {
+ defer func() {
+ if err = terminal.Restore(unix.Stdin, state); err != nil {
+ logrus.Errorf("unable to restore terminal state: %v", err)
+ }
+ }()
+ // FIXME - if we're connected to a terminal, we should be
+ // passing the updated terminal size down when we receive a
+ // SIGWINCH.
+ }
+ }
+ // Track how many descriptors we're expecting data from.
+ reading := 0
+ // Map describing where data on an incoming descriptor should go.
+ relayMap := make(map[int]int)
+ // Map describing incoming descriptors.
+ relayDesc := make(map[int]string)
+ // Buffers.
+ relayBuffer := make(map[int]*bytes.Buffer)
+ if copyConsole {
+ // Input from our stdin, output from the terminal descriptor.
+ relayMap[unix.Stdin] = terminalFD
+ relayDesc[unix.Stdin] = "stdin"
+ relayBuffer[unix.Stdin] = new(bytes.Buffer)
+ relayMap[terminalFD] = unix.Stdout
+ relayDesc[terminalFD] = "container terminal output"
+ relayBuffer[terminalFD] = new(bytes.Buffer)
+ reading = 2
+ }
+ if copyStdio {
+ // Input from our stdin, output from the stdout and stderr pipes.
+ relayMap[unix.Stdin] = stdioPipe[unix.Stdin][1]
+ relayDesc[unix.Stdin] = "stdin"
+ relayBuffer[unix.Stdin] = new(bytes.Buffer)
+ relayMap[stdioPipe[unix.Stdout][0]] = unix.Stdout
+ relayDesc[stdioPipe[unix.Stdout][0]] = "container stdout"
+ relayBuffer[stdioPipe[unix.Stdout][0]] = new(bytes.Buffer)
+ relayMap[stdioPipe[unix.Stderr][0]] = unix.Stderr
+ relayDesc[stdioPipe[unix.Stderr][0]] = "container stderr"
+ relayBuffer[stdioPipe[unix.Stderr][0]] = new(bytes.Buffer)
+ reading = 3
+ }
+ // Set our reading descriptors to non-blocking.
+ for fd := range relayMap {
+ if err := unix.SetNonblock(fd, true); err != nil {
+ logrus.Errorf("error setting %s to nonblocking: %v", relayDesc[fd], err)
+ return
+ }
+ }
+ // Pass data back and forth.
+ for {
+ // Start building the list of descriptors to poll.
+ pollFds := make([]unix.PollFd, 0, reading+1)
+ // Poll for a notification that we should stop handling stdio.
+ pollFds = append(pollFds, unix.PollFd{Fd: int32(finishCopy[0]), Events: unix.POLLIN | unix.POLLHUP})
+ // Poll on our reading descriptors.
+ for rfd := range relayMap {
+ pollFds = append(pollFds, unix.PollFd{Fd: int32(rfd), Events: unix.POLLIN | unix.POLLHUP})
+ }
+ buf := make([]byte, 8192)
+ // Wait for new data from any input descriptor, or a notification that we're done.
+ nevents, err := unix.Poll(pollFds, -1)
+ if err != nil {
+ if errno, isErrno := err.(syscall.Errno); isErrno {
+ switch errno {
+ case syscall.EINTR:
+ continue
+ default:
+ logrus.Errorf("unable to wait for stdio/terminal data to relay: %v", err)
+ return
+ }
+ } else {
+ logrus.Errorf("unable to wait for stdio/terminal data to relay: %v", err)
+ return
+ }
+ }
+ if nevents == 0 {
+ logrus.Errorf("unexpected no data, no error waiting for terminal data to relay")
+ return
+ }
+ var removes []int
+ for _, pollFd := range pollFds {
+ // If this descriptor's just been closed from the other end, mark it for
+ // removal from the set that we're checking for.
+ if pollFd.Revents&unix.POLLHUP == unix.POLLHUP {
+ removes = append(removes, int(pollFd.Fd))
+ }
+ // If the EPOLLIN flag isn't set, then there's no data to be read from this descriptor.
+ if pollFd.Revents&unix.POLLIN == 0 {
+ // If we're using pipes and it's our stdin, close the writing end
+ // of the corresponding pipe.
+ if copyStdio && int(pollFd.Fd) == unix.Stdin {
+ unix.Close(stdioPipe[unix.Stdin][1])
+ stdioPipe[unix.Stdin][1] = -1
+ }
+ continue
+ }
+ // Copy whatever we read to wherever it needs to be sent.
+ readFD := int(pollFd.Fd)
+ writeFD, needToRelay := relayMap[readFD]
+ if needToRelay {
+ n, err := unix.Read(readFD, buf)
+ if err != nil {
+ if errno, isErrno := err.(syscall.Errno); isErrno {
+ switch errno {
+ default:
+ logrus.Errorf("unable to read %s: %v", relayDesc[readFD], err)
+ case syscall.EINTR, syscall.EAGAIN:
+ }
+ } else {
+ logrus.Errorf("unable to wait for %s data to relay: %v", relayDesc[readFD], err)
+ }
+ continue
+ }
+ // If it's zero-length on our stdin and we're
+ // using pipes, it's an EOF, so close the stdin
+ // pipe's writing end.
+ if n == 0 && copyStdio && int(pollFd.Fd) == unix.Stdin {
+ unix.Close(stdioPipe[unix.Stdin][1])
+ stdioPipe[unix.Stdin][1] = -1
+ }
+ if n > 0 {
+ // Buffer the data in case we're blocked on where they need to go.
+ relayBuffer[readFD].Write(buf[:n])
+ // Try to drain the buffer.
+ n, err = unix.Write(writeFD, relayBuffer[readFD].Bytes())
+ if err != nil {
+ logrus.Errorf("unable to write %s: %v", relayDesc[readFD], err)
+ return
+ }
+ relayBuffer[readFD].Next(n)
+ }
+ }
+ }
+ // Remove any descriptors which we don't need to poll any more from the poll descriptor list.
+ for _, remove := range removes {
+ delete(relayMap, remove)
+ reading--
+ }
+ if reading == 0 {
+ // We have no more open descriptors to read, so we can stop now.
+ return
+ }
+ // If the we-can-return pipe had anything for us, we're done.
+ for _, pollFd := range pollFds {
+ if int(pollFd.Fd) == finishCopy[0] && pollFd.Revents != 0 {
+ // The pipe is closed, indicating that we can stop now.
+ return
+ }
+ }
+ }
+}
+
+func runAcceptTerminal(consoleListener *net.UnixListener) (int, error) {
+ defer consoleListener.Close()
+ c, err := consoleListener.AcceptUnix()
+ if err != nil {
+ return -1, errors.Wrapf(err, "error accepting socket descriptor connection")
+ }
+ defer c.Close()
+ // Expect a control message over our new connection.
+ b := make([]byte, 8192)
+ oob := make([]byte, 8192)
+ n, oobn, _, _, err := c.ReadMsgUnix(b, oob)
+ if err != nil {
+ return -1, errors.Wrapf(err, "error reading socket descriptor: %v")
+ }
+ if n > 0 {
+ logrus.Debugf("socket descriptor is for %q", string(b[:n]))
+ }
+ if oobn > len(oob) {
+ return -1, errors.Errorf("too much out-of-bounds data (%d bytes)", oobn)
+ }
+ // Parse the control message.
+ scm, err := unix.ParseSocketControlMessage(oob[:oobn])
+ if err != nil {
+ return -1, errors.Wrapf(err, "error parsing out-of-bound data as a socket control message")
+ }
+ logrus.Debugf("control messages: %v", scm)
+ // Expect to get a descriptor.
+ terminalFD := -1
+ for i := range scm {
+ fds, err := unix.ParseUnixRights(&scm[i])
+ if err != nil {
+ return -1, errors.Wrapf(err, "error parsing unix rights control message: %v")
+ }
+ logrus.Debugf("fds: %v", fds)
+ if len(fds) == 0 {
+ continue
+ }
+ terminalFD = fds[0]
+ break
+ }
+ if terminalFD == -1 {
+ return -1, errors.Errorf("unable to read terminal descriptor")
+ }
+ // Set the pseudoterminal's size to match our own.
+ winsize, err := unix.IoctlGetWinsize(unix.Stdin, unix.TIOCGWINSZ)
+ if err != nil {
+ logrus.Warnf("error reading size of controlling terminal: %v", err)
+ return terminalFD, nil
+ }
+ err = unix.IoctlSetWinsize(terminalFD, unix.TIOCSWINSZ, winsize)
+ if err != nil {
+ logrus.Warnf("error setting size of container pseudoterminal: %v", err)
+ }
+ return terminalFD, nil
+}
+
+func runSetDeathSig(cmd *exec.Cmd) {
+ if cmd.SysProcAttr == nil {
+ cmd.SysProcAttr = &syscall.SysProcAttr{}
+ }
+ if cmd.SysProcAttr.Pdeathsig == 0 {
+ cmd.SysProcAttr.Pdeathsig = syscall.SIGTERM
}
- return err
}