From 915364034f1ddf036d277830d45c54b8eb39f940 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Wed, 23 May 2018 14:15:54 -0400 Subject: Update podman build to match buildah bud functionality Add --label, --annotations, --idfile, --squash Signed-off-by: Daniel J Walsh Closes: #824 Approved by: TomSweeneyRedHat --- vendor/github.com/projectatomic/buildah/run.go | 652 +++++++++++++++++++++++-- 1 file changed, 621 insertions(+), 31 deletions(-) (limited to 'vendor/github.com/projectatomic/buildah/run.go') 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 } -- cgit v1.2.3-54-g00ecf