diff options
author | TomSweeneyRedHat <tsweeney@redhat.com> | 2018-06-19 10:03:34 -0400 |
---|---|---|
committer | Atomic Bot <atomic-devel@projectatomic.io> | 2018-06-22 14:55:32 +0000 |
commit | 89af35175d97cf90e7336d3c817612fafc68dbdb (patch) | |
tree | 7468b588dfc47b0e9f173c6b273c732514eb6821 /vendor/github.com/projectatomic/buildah/run.go | |
parent | 82a948c04ec068acb9f0d47dc0f9e3bd05b4c90c (diff) | |
download | podman-89af35175d97cf90e7336d3c817612fafc68dbdb.tar.gz podman-89af35175d97cf90e7336d3c817612fafc68dbdb.tar.bz2 podman-89af35175d97cf90e7336d3c817612fafc68dbdb.zip |
Add cap-add and cap-drop to build man page
Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com>
Closes: #968
Approved by: mheon
Diffstat (limited to 'vendor/github.com/projectatomic/buildah/run.go')
-rw-r--r-- | vendor/github.com/projectatomic/buildah/run.go | 538 |
1 files changed, 221 insertions, 317 deletions
diff --git a/vendor/github.com/projectatomic/buildah/run.go b/vendor/github.com/projectatomic/buildah/run.go index 0d96aed09..97d247fdc 100644 --- a/vendor/github.com/projectatomic/buildah/run.go +++ b/vendor/github.com/projectatomic/buildah/run.go @@ -12,7 +12,6 @@ import ( "os/exec" "path/filepath" "runtime" - "sort" "strconv" "strings" "sync" @@ -20,9 +19,7 @@ import ( "time" "github.com/containernetworking/cni/libcni" - "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/ioutils" - "github.com/containers/storage/pkg/mount" "github.com/containers/storage/pkg/reexec" "github.com/docker/docker/profiles/seccomp" units "github.com/docker/go-units" @@ -31,6 +28,7 @@ import ( "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" + "github.com/projectatomic/buildah/bind" "github.com/projectatomic/buildah/util" "github.com/projectatomic/libpod/pkg/secrets" "github.com/sirupsen/logrus" @@ -148,13 +146,23 @@ type RunOptions struct { // decision can be overridden by specifying either WithTerminal or // WithoutTerminal. Terminal TerminalPolicy + // TerminalSize provides a way to set the number of rows and columns in + // a pseudo-terminal, if we create one, and Stdin/Stdout/Stderr aren't + // connected to a terminal. + TerminalSize *specs.Box // The stdin/stdout/stderr descriptors to use. If set to nil, the // corresponding files in the "os" package are used as defaults. - Stdin io.ReadCloser `json:"-"` - Stdout io.WriteCloser `json:"-"` - Stderr io.WriteCloser `json:"-"` + Stdin io.Reader `json:"-"` + Stdout io.Writer `json:"-"` + Stderr io.Writer `json:"-"` // Quiet tells the run to turn off output to stdout. Quiet bool + // AddCapabilities is a list of capabilities to add to the default set. + AddCapabilities []string + // DropCapabilities is a list of capabilities to remove from the default set, + // after processing the AddCapabilities set. If a capability appears in both + // lists, it will be dropped. + DropCapabilities []string } // DefaultNamespaceOptions returns the default namespace settings from the @@ -229,7 +237,17 @@ func addRlimits(ulimit []string, g *generate.Generator) error { func addHosts(hosts []string, w io.Writer) error { buf := bufio.NewWriter(w) for _, host := range hosts { - fmt.Fprintln(buf, host) + values := strings.SplitN(host, ":", 2) + if len(values) != 2 { + return errors.Errorf("unable to parse host entry %q: incorrect format", host) + } + if values[0] == "" { + return errors.Errorf("hostname in host entry %q is empty", host) + } + if values[1] == "" { + return errors.Errorf("IP address in host entry %q is empty", host) + } + fmt.Fprintf(buf, "%s\t%s\n", values[1], values[0]) } return buf.Flush() } @@ -286,7 +304,7 @@ func addCommonOptsToSpec(commonOpts *CommonBuildOptions, g *generate.Generator) return nil } -func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts []specs.Mount, bindFiles map[string]string, builtinVolumes, volumeMounts []string, shmSize string, namespaceOptions NamespaceOptions) error { +func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath string, optionMounts []specs.Mount, bindFiles map[string]string, builtinVolumes, volumeMounts []string, shmSize string, namespaceOptions NamespaceOptions) error { // Start building a new list of mounts. var mounts []specs.Mount haveMount := func(destination string) bool { @@ -316,7 +334,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts Source: "/dev/shm", Type: "bind", Destination: "/dev/shm", - Options: []string{"nobuildahbind", "rbind", "nosuid", "noexec", "nodev"}, + Options: []string{bind.NoBindOption, "rbind", "nosuid", "noexec", "nodev"}, } } } @@ -331,7 +349,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts Source: "/dev/mqueue", Type: "bind", Destination: "/dev/mqueue", - Options: []string{"nobuildahbind", "rbind", "nosuid", "noexec", "nodev"}, + Options: []string{bind.NoBindOption, "rbind", "nosuid", "noexec", "nodev"}, } } } @@ -347,7 +365,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts Source: "/sys", Type: "bind", Destination: "/sys", - Options: []string{"nobuildahbind", "rbind", "nosuid", "noexec", "nodev", "ro"}, + Options: []string{bind.NoBindOption, "rbind", "nosuid", "noexec", "nodev", "ro"}, } } } @@ -363,12 +381,12 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts Destination: "/sys/fs/cgroup", Type: "cgroup", Source: "cgroup", - Options: []string{"nobuildahbind", "nosuid", "noexec", "nodev", "relatime", "ro"}, + Options: []string{bind.NoBindOption, "nosuid", "noexec", "nodev", "relatime", "ro"}, }} } // Get the list of files we need to bind into the container. - bindFileMounts, err := runSetupBoundFiles(bindFiles) + bindFileMounts, err := runSetupBoundFiles(bundlePath, bindFiles) if err != nil { return err } @@ -381,7 +399,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts // Figure out which UID and GID to tell the secrets package to use // for files that it creates. - rootUID, rootGID, err := getHostRootIDs(spec) + rootUID, rootGID, err := util.GetHostRootIDs(spec) if err != nil { return err } @@ -418,13 +436,17 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts return nil } -func runSetupBoundFiles(bindFiles map[string]string) (mounts []specs.Mount, err error) { +func runSetupBoundFiles(bundlePath string, bindFiles map[string]string) (mounts []specs.Mount, err error) { for dest, src := range bindFiles { + options := []string{"rbind"} + if strings.HasPrefix(src, bundlePath) { + options = append(options, bind.NoBindOption) + } mounts = append(mounts, specs.Mount{ Source: src, Destination: dest, Type: "bind", - Options: []string{"rbind"}, + Options: options, }) } return mounts, nil @@ -574,25 +596,88 @@ func setupReadOnlyPaths(g *generate.Generator) { } } +func setupCapAdd(g *generate.Generator, caps ...string) error { + for _, cap := range caps { + if err := g.AddProcessCapabilityBounding(cap); err != nil { + return errors.Wrapf(err, "error adding %q to the bounding capability set", cap) + } + if err := g.AddProcessCapabilityEffective(cap); err != nil { + return errors.Wrapf(err, "error adding %q to the effective capability set", cap) + } + if err := g.AddProcessCapabilityInheritable(cap); err != nil { + return errors.Wrapf(err, "error adding %q to the inheritable capability set", cap) + } + if err := g.AddProcessCapabilityPermitted(cap); err != nil { + return errors.Wrapf(err, "error adding %q to the permitted capability set", cap) + } + if err := g.AddProcessCapabilityAmbient(cap); err != nil { + return errors.Wrapf(err, "error adding %q to the ambient capability set", cap) + } + } + return nil +} + +func setupCapDrop(g *generate.Generator, caps ...string) error { + for _, cap := range caps { + if err := g.DropProcessCapabilityBounding(cap); err != nil { + return errors.Wrapf(err, "error removing %q from the bounding capability set", cap) + } + if err := g.DropProcessCapabilityEffective(cap); err != nil { + return errors.Wrapf(err, "error removing %q from the effective capability set", cap) + } + if err := g.DropProcessCapabilityInheritable(cap); err != nil { + return errors.Wrapf(err, "error removing %q from the inheritable capability set", cap) + } + if err := g.DropProcessCapabilityPermitted(cap); err != nil { + return errors.Wrapf(err, "error removing %q from the permitted capability set", cap) + } + if err := g.DropProcessCapabilityAmbient(cap); err != nil { + return errors.Wrapf(err, "error removing %q from the ambient capability set", cap) + } + } + return nil +} + +func setupCapabilities(g *generate.Generator, firstAdds, firstDrops, secondAdds, secondDrops []string) error { + g.ClearProcessCapabilities() + if err := setupCapAdd(g, util.DefaultCapabilities...); err != nil { + return err + } + if err := setupCapAdd(g, firstAdds...); err != nil { + return err + } + if err := setupCapDrop(g, firstDrops...); err != nil { + return err + } + if err := setupCapAdd(g, secondAdds...); err != nil { + return err + } + if err := setupCapDrop(g, secondDrops...); err != nil { + return err + } + return nil +} + func setupSeccomp(spec *specs.Spec, seccompProfilePath string) error { - if seccompProfilePath != "unconfined" { - if seccompProfilePath != "" { - seccompProfile, err := ioutil.ReadFile(seccompProfilePath) - if err != nil { - return errors.Wrapf(err, "opening seccomp profile (%s) failed", seccompProfilePath) - } - seccompConfig, err := seccomp.LoadProfile(string(seccompProfile), spec) - if err != nil { - return errors.Wrapf(err, "loading seccomp profile (%s) failed", seccompProfilePath) - } - spec.Linux.Seccomp = seccompConfig - } else { - seccompConfig, err := seccomp.GetDefaultProfile(spec) - if err != nil { - return errors.Wrapf(err, "loading seccomp profile (%s) failed", seccompProfilePath) - } - spec.Linux.Seccomp = seccompConfig + switch seccompProfilePath { + case "unconfined": + spec.Linux.Seccomp = nil + case "": + seccompConfig, err := seccomp.GetDefaultProfile(spec) + if err != nil { + return errors.Wrapf(err, "loading default seccomp profile failed") + } + spec.Linux.Seccomp = seccompConfig + default: + seccompProfile, err := ioutil.ReadFile(seccompProfilePath) + if err != nil { + return errors.Wrapf(err, "opening seccomp profile (%s) failed", seccompProfilePath) + } + seccompConfig, err := seccomp.LoadProfile(string(seccompProfile), spec) + if err != nil { + return errors.Wrapf(err, "loading seccomp profile (%s) failed", seccompProfilePath) } + spec.Linux.Seccomp = seccompConfig } return nil } @@ -602,15 +687,24 @@ func setupApparmor(spec *specs.Spec, apparmorProfile string) error { return nil } -func setupTerminal(g *generate.Generator, terminalPolicy TerminalPolicy) { +func setupTerminal(g *generate.Generator, terminalPolicy TerminalPolicy, terminalSize *specs.Box) { switch terminalPolicy { case DefaultTerminal: - g.SetProcessTerminal(terminal.IsTerminal(int(os.Stdout.Fd()))) + onTerminal := terminal.IsTerminal(unix.Stdin) && terminal.IsTerminal(unix.Stdout) && terminal.IsTerminal(unix.Stderr) + if onTerminal { + logrus.Debugf("stdio is a terminal, defaulting to using a terminal") + } else { + logrus.Debugf("stdio is not a terminal, defaulting to not using a terminal") + } + g.SetProcessTerminal(onTerminal) case WithTerminal: g.SetProcessTerminal(true) case WithoutTerminal: g.SetProcessTerminal(false) } + if terminalSize != nil { + g.SetProcessConsoleSize(terminalSize.Width, terminalSize.Height) + } } func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, idmapOptions IDMappingOptions, policy NetworkConfigurationPolicy) (configureNetwork bool, configureNetworks []string, configureUTS bool, err error) { @@ -662,15 +756,15 @@ func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, i if err := g.AddOrReplaceLinuxNamespace(specs.UserNamespace, ""); err != nil { return false, nil, false, errors.Wrapf(err, "error adding new %q namespace for run", string(specs.UserNamespace)) } + hostUidmap, hostGidmap, err := util.GetHostIDMappings("") + if err != nil { + return false, nil, false, err + } for _, m := range idmapOptions.UIDMap { g.AddLinuxUIDMapping(m.HostID, m.ContainerID, m.Size) } if len(idmapOptions.UIDMap) == 0 { - mappings, err := getProcIDMappings("/proc/self/uid_map") - if err != nil { - return false, nil, false, err - } - for _, m := range mappings { + for _, m := range hostUidmap { g.AddLinuxUIDMapping(m.ContainerID, m.ContainerID, m.Size) } } @@ -678,11 +772,7 @@ func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, i g.AddLinuxGIDMapping(m.HostID, m.ContainerID, m.Size) } if len(idmapOptions.GIDMap) == 0 { - mappings, err := getProcIDMappings("/proc/self/gid_map") - if err != nil { - return false, nil, false, err - } - for _, m := range mappings { + for _, m := range hostGidmap { g.AddLinuxGIDMapping(m.ContainerID, m.ContainerID, m.Size) } } @@ -769,7 +859,7 @@ func (b *Builder) Run(command []string, options RunOptions) error { g.SetRootPath(mountPoint) - setupTerminal(g, options.Terminal) + setupTerminal(g, options.Terminal, options.TerminalSize) namespaceOptions := DefaultNamespaceOptions() namespaceOptions.AddOrReplace(b.NamespaceOptions...) @@ -795,9 +885,13 @@ func (b *Builder) Run(command []string, options RunOptions) error { g.SetHostname("") } + // Set the user UID/GID/supplemental group list/capabilities lists. if user, err = b.user(mountPoint, options.User); err != nil { return err } + if err = setupCapabilities(g, b.AddCapabilities, b.DropCapabilities, options.AddCapabilities, options.DropCapabilities); err != nil { + return err + } g.SetProcessUID(user.UID) g.SetProcessGID(user.GID) for _, gid := range user.AdditionalGids { @@ -807,8 +901,9 @@ func (b *Builder) Run(command []string, options RunOptions) error { // Now grab the spec from the generator. Set the generator to nil so that future contributors // will quickly be able to tell that they're supposed to be modifying the spec directly from here. spec := g.Spec() + g = nil - //Remove capabilities if not running as root + // Remove capabilities if not running as root if user.UID != 0 { var caplist []string spec.Process.Capabilities.Permitted = caplist @@ -816,7 +911,8 @@ func (b *Builder) Run(command []string, options RunOptions) error { spec.Process.Capabilities.Effective = caplist spec.Process.Capabilities.Ambient = caplist } - g = nil + + // Set the working directory, creating it if we must. if spec.Process.Cwd == "" { spec.Process.Cwd = DefaultWorkingDir } @@ -829,7 +925,9 @@ func (b *Builder) Run(command []string, options RunOptions) error { return err } - // Set the seccomp configuration using the specified profile name. + // Set the seccomp configuration using the specified profile name. Some syscalls are + // allowed if certain capabilities are to be granted (example: CAP_SYS_CHROOT and chroot), + // so we sorted out the capabilities lists first. if err = setupSeccomp(spec, b.CommonBuildOpts.SeccompProfilePath); err != nil { return err } @@ -851,7 +949,7 @@ func (b *Builder) Run(command []string, options RunOptions) error { "/etc/hosts": hostFile, "/etc/resolv.conf": resolvFile, } - err = b.setupMounts(mountPoint, spec, options.Mounts, bindFiles, b.Volumes(), b.CommonBuildOpts.Volumes, b.CommonBuildOpts.ShmSize, append(b.NamespaceOptions, options.NamespaceOptions...)) + err = b.setupMounts(mountPoint, spec, path, options.Mounts, bindFiles, b.Volumes(), b.CommonBuildOpts.Volumes, b.CommonBuildOpts.ShmSize, append(b.NamespaceOptions, options.NamespaceOptions...)) if err != nil { return errors.Wrapf(err, "error resolving mountpoints for container") } @@ -977,9 +1075,11 @@ func runUsingRuntimeMain() { } func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetworks []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (wstatus unix.WaitStatus, err error) { - // Set up bind mounts for things that a namespaced user might not be able to get to directly. + // Lock the caller to a single OS-level thread. runtime.LockOSThread() - unmountAll, err := runSetupIntermediateMountNamespace(spec, bundlePath) + + // Set up bind mounts for things that a namespaced user might not be able to get to directly. + unmountAll, err := bind.SetupIntermediateMountNamespace(spec, bundlePath) if unmountAll != nil { defer func() { if err := unmountAll(); err != nil { @@ -1009,7 +1109,7 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetwork } // Default to not specifying a console socket location. - moreCreateArgs := func() []string { return nil } + var moreCreateArgs []string // Default to just passing down our stdio. getCreateStdio := func() (io.ReadCloser, io.WriteCloser, io.WriteCloser) { return os.Stdin, os.Stdout, os.Stderr @@ -1021,7 +1121,7 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetwork var errorFds []int stdioPipe := make([][]int, 3) copyConsole := false - copyStdio := false + copyPipes := 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") @@ -1037,11 +1137,11 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetwork return 1, errors.Wrapf(err, "error creating socket to receive terminal descriptor") } // Add console socket arguments. - moreCreateArgs = func() []string { return []string{"--console-socket", socketPath} } + moreCreateArgs = append(moreCreateArgs, "--console-socket", socketPath) } else { - copyStdio = true + copyPipes = true // Figure out who should own the pipes. - uid, gid, err := getHostRootIDs(spec) + uid, gid, err := util.GetHostRootIDs(spec) if err != nil { return 1, err } @@ -1069,7 +1169,7 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetwork // 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) + 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() @@ -1077,25 +1177,21 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetwork 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() @@ -1144,7 +1240,7 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetwork } } - if copyStdio { + if copyPipes { // We don't need the ends of the pipes that belong to the container. stdin.Close() if stdout != nil { @@ -1155,7 +1251,7 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetwork // Handle stdio for the container in the background. stdio.Add(1) - go runCopyStdio(&stdio, copyStdio, stdioPipe, copyConsole, consoleListener, finishCopy, finishedCopy) + go runCopyStdio(&stdio, copyPipes, stdioPipe, copyConsole, consoleListener, finishCopy, finishedCopy, spec) // Start the container. err = start.Run() @@ -1284,7 +1380,7 @@ func runConfigureNetwork(options RunOptions, configureNetwork bool, configureNet if err != nil { return nil, errors.Wrapf(err, "error loading networking configuration from file %q for %v", file, command) } - if len(configureNetworks) > 0 && nc.Network != nil && (nc.Network.Name == "" || !stringInSlice(nc.Network.Name, configureNetworks)) { + if len(configureNetworks) > 0 && nc.Network != nil && (nc.Network.Name == "" || !util.StringInSlice(nc.Network.Name, configureNetworks)) { if nc.Network.Name == "" { logrus.Debugf("configuration in %q has no name, skipping it", file) } else { @@ -1304,7 +1400,7 @@ func runConfigureNetwork(options RunOptions, configureNetwork bool, configureNet if err != nil { return nil, errors.Wrapf(err, "error loading networking configuration list from file %q for %v", list, command) } - if len(configureNetworks) > 0 && (cl.Name == "" || !stringInSlice(cl.Name, configureNetworks)) { + if len(configureNetworks) > 0 && (cl.Name == "" || !util.StringInSlice(cl.Name, configureNetworks)) { if cl.Name == "" { logrus.Debugf("configuration list in %q has no name, skipping it", list) } else { @@ -1359,10 +1455,10 @@ func runConfigureNetwork(options RunOptions, configureNetwork bool, configureNet return teardown, nil } -func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copyConsole bool, consoleListener *net.UnixListener, finishCopy []int, finishedCopy chan struct{}) { +func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copyConsole bool, consoleListener *net.UnixListener, finishCopy []int, finishedCopy chan struct{}, spec *specs.Spec) { defer func() { unix.Close(finishCopy[0]) - if copyStdio { + if copyPipes { unix.Close(stdioPipe[unix.Stdin][1]) unix.Close(stdioPipe[unix.Stdout][0]) unix.Close(stdioPipe[unix.Stderr][0]) @@ -1370,37 +1466,6 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy 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 and outgoing descriptors. @@ -1408,7 +1473,15 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy writeDesc := make(map[int]string) // Buffers. relayBuffer := make(map[int]*bytes.Buffer) + // Set up the terminal descriptor or pipes for polling. if copyConsole { + // Accept a connection over our listening socket. + fd, err := runAcceptTerminal(consoleListener, spec.Process.ConsoleSize) + if err != nil { + logrus.Errorf("%v", err) + return + } + terminalFD := fd // Input from our stdin, output from the terminal descriptor. relayMap[unix.Stdin] = terminalFD readDesc[unix.Stdin] = "stdin" @@ -1418,9 +1491,21 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy readDesc[terminalFD] = "container terminal output" relayBuffer[unix.Stdout] = new(bytes.Buffer) writeDesc[unix.Stdout] = "output" - reading = 2 + // Set our terminal's mode to raw, to pass handling of special + // terminal input to the terminal in the container. + if terminal.IsTerminal(unix.Stdin) { + if state, err := terminal.MakeRaw(unix.Stdin); 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) + } + }() + } + } } - if copyStdio { + if copyPipes { // Input from our stdin, output from the stdout and stderr pipes. relayMap[unix.Stdin] = stdioPipe[unix.Stdin][1] readDesc[unix.Stdin] = "stdin" @@ -1434,7 +1519,6 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy readDesc[stdioPipe[unix.Stderr][0]] = "container stderr" relayBuffer[unix.Stderr] = new(bytes.Buffer) writeDesc[unix.Stderr] = "stderr" - reading = 3 } // Set our reading descriptors to non-blocking. for fd := range relayMap { @@ -1460,9 +1544,9 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy } // Pass data back and forth. pollTimeout := -1 - for { + for len(relayMap) > 0 { // Start building the list of descriptors to poll. - pollFds := make([]unix.PollFd, 0, reading+1) + pollFds := make([]unix.PollFd, 0, len(relayMap)+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. @@ -1475,18 +1559,23 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy if !logIfNotRetryable(err, fmt.Sprintf("error waiting for stdio/terminal data to relay: %v", err)) { return } - var removes []int + removes := make(map[int]struct{}) 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)) + removes[int(pollFd.Fd)] = struct{}{} + } + // If the descriptor was closed elsewhere, remove it from our list. + if pollFd.Revents&unix.POLLNVAL != 0 { + logrus.Debugf("error polling descriptor %d: closed?", pollFd.Fd) + removes[int(pollFd.Fd)] = struct{}{} } // If the POLLIN 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 and it's closed, close the writing // end of the corresponding pipe. - if copyStdio && int(pollFd.Fd) == unix.Stdin && pollFd.Revents&unix.POLLHUP != 0 { + if copyPipes && int(pollFd.Fd) == unix.Stdin && pollFd.Revents&unix.POLLHUP != 0 { unix.Close(stdioPipe[unix.Stdin][1]) stdioPipe[unix.Stdin][1] = -1 } @@ -1503,7 +1592,7 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy // 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 { + if n == 0 && copyPipes && int(pollFd.Fd) == unix.Stdin { unix.Close(stdioPipe[unix.Stdin][1]) stdioPipe[unix.Stdin][1] = -1 } @@ -1531,13 +1620,8 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy } } // Remove any descriptors which we don't need to poll any more from the poll descriptor list. - for _, remove := range removes { + 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 { @@ -1549,7 +1633,7 @@ func runCopyStdio(stdio *sync.WaitGroup, copyStdio bool, stdioPipe [][]int, copy } } -func runAcceptTerminal(consoleListener *net.UnixListener) (int, error) { +func runAcceptTerminal(consoleListener *net.UnixListener, terminalSize *specs.Box) (int, error) { defer consoleListener.Close() c, err := consoleListener.AcceptUnix() if err != nil { @@ -1592,28 +1676,33 @@ func runAcceptTerminal(consoleListener *net.UnixListener) (int, error) { 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 + // Set the pseudoterminal's size to the configured size, or our own. + winsize := &unix.Winsize{} + if terminalSize != nil { + // Use configured sizes. + winsize.Row = uint16(terminalSize.Height) + winsize.Col = uint16(terminalSize.Width) + } else { + if terminal.IsTerminal(unix.Stdin) { + // Use the size of our terminal. + if winsize, err = unix.IoctlGetWinsize(unix.Stdin, unix.TIOCGWINSZ); err != nil { + logrus.Warnf("error reading size of controlling terminal: %v", err) + winsize.Row = 0 + winsize.Col = 0 + } + } } - err = unix.IoctlSetWinsize(terminalFD, unix.TIOCSWINSZ, winsize) - if err != nil { - logrus.Warnf("error setting size of container pseudoterminal: %v", err) + if winsize.Row != 0 && winsize.Col != 0 { + if err = unix.IoctlSetWinsize(terminalFD, unix.TIOCSWINSZ, winsize); err != nil { + logrus.Warnf("error setting size of container pseudoterminal: %v", err) + } + // FIXME - if we're connected to a terminal, we should + // be passing the updated terminal size down when we + // receive a SIGWINCH. } 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 - } -} - // Create pipes to use for relaying stdio. func runMakeStdioPipe(uid, gid int) ([][]int, error) { stdioPipe := make([][]int, 3) @@ -1634,188 +1723,3 @@ func runMakeStdioPipe(uid, gid int) ([][]int, error) { } return stdioPipe, nil } - -// Create and bind mount all bind-mount sources into a subdirectory of -// bundlePath that can only be reached by the root user of the container's user -// namespace. -func runSetupIntermediateMountNamespace(spec *specs.Spec, bundlePath string) (unmountAll func() error, err error) { - defer func() { - // Strip "nobuildahbind" options out of the spec, at least. */ - for i := range spec.Mounts { - if stringInSlice("nobuildahbind", spec.Mounts[i].Options) { - prunedOptions := make([]string, 0, len(spec.Mounts[i].Options)) - for _, option := range spec.Mounts[i].Options { - if option != "nobuildahbind" { - prunedOptions = append(prunedOptions, option) - } - } - spec.Mounts[i].Options = prunedOptions - } - } - }() - - // Create a new mount namespace in which to do the things we're doing. - if err := unix.Unshare(unix.CLONE_NEWNS); err != nil { - return nil, errors.Wrapf(err, "error creating new mount namespace for %v", spec.Process.Args) - } - - // Make all of our mounts private to our namespace. - if err := mount.MakePrivate("/"); err != nil { - return nil, errors.Wrapf(err, "error making mounts private to mount namespace for %v", spec.Process.Args) - } - - // We expect a root directory to be defined. - if spec.Root == nil { - return nil, errors.Errorf("configuration has no root filesystem?") - } - rootPath := spec.Root.Path - - // Make sure the bundle directory is searchable. We created it with - // TempDir(), so it should have started with permissions set to 0700. - info, err := os.Stat(bundlePath) - if err != nil { - return nil, errors.Wrapf(err, "error checking permissions on %q", bundlePath) - } - if err = os.Chmod(bundlePath, info.Mode()|0111); err != nil { - return nil, errors.Wrapf(err, "error loosening permissions on %q", bundlePath) - } - - // Figure out who needs to be able to reach these bind mounts in order - // for the container to be started. - rootUID, rootGID, err := getHostRootIDs(spec) - if err != nil { - return nil, err - } - - // Hand back a callback that the caller can use to clean up everything - // we're doing here. - unmount := []string{} - unmountAll = func() (err error) { - for _, mountpoint := range unmount { - subdirs := []string{mountpoint} - var infos []*mount.Info - infos, err = mount.GetMounts() - // Gather up mountpoints below this one, since we did - // some recursive mounting. - if err == nil { - for _, info := range infos { - if strings.HasPrefix(info.Mountpoint, mountpoint) { - subdirs = append(subdirs, info.Mountpoint) - } - } - } - // Unmount all of the lower mountpoints, then the - // mountpoint itself. - sort.Strings(subdirs) - for i := range subdirs { - var err2 error - subdir := subdirs[len(subdirs)-i-1] - for err2 == nil { - err2 = unix.Unmount(subdir, 0) - } - if errno, ok := err2.(syscall.Errno); !ok || errno != unix.EINVAL { - logrus.Warnf("error unmounting %q: %v", mountpoint, err2) - if err == nil { - err = err2 - } - } - } - // Remove just the mountpoint. - if err2 := os.Remove(mountpoint); err2 != nil { - logrus.Warnf("error removing %q: %v", mountpoint, err2) - if err == nil { - err = err2 - } - } - } - return err - } - - // Create a top-level directory that the "root" user will be able to - // access, that "root" from containers which use different mappings, or - // other unprivileged users outside of containers, shouldn't be able to - // access. - mnt := filepath.Join(bundlePath, "mnt") - if err = idtools.MkdirAndChown(mnt, 0100, idtools.IDPair{UID: int(rootUID), GID: int(rootGID)}); err != nil { - return unmountAll, errors.Wrapf(err, "error creating %q owned by the container's root user", mnt) - } - - // Make that directory private, and add it to the list of locations we - // unmount at cleanup time. - if err = mount.MakeRPrivate(mnt); err != nil { - return unmountAll, errors.Wrapf(err, "error marking filesystem at %q as private", mnt) - } - unmount = append([]string{mnt}, unmount...) - - // Create a bind mount for the root filesystem and add it to the list. - rootfs := filepath.Join(mnt, "rootfs") - if err = os.Mkdir(rootfs, 0000); err != nil { - return unmountAll, errors.Wrapf(err, "error creating directory %q", rootfs) - } - if err = unix.Mount(rootPath, rootfs, "", unix.MS_BIND|unix.MS_REC|unix.MS_PRIVATE, ""); err != nil { - return unmountAll, errors.Wrapf(err, "error bind mounting root filesystem from %q to %q", rootPath, rootfs) - } - unmount = append([]string{rootfs}, unmount...) - spec.Root.Path = rootfs - - // Do the same for everything we're binding in. - mounts := make([]specs.Mount, 0, len(spec.Mounts)) - for i := range spec.Mounts { - // If we're not using an intermediate, leave it in the list. - if runLeaveBindMountAlone(spec.Mounts[i]) { - mounts = append(mounts, spec.Mounts[i]) - continue - } - // Check if the source is a directory or something else. - info, err := os.Stat(spec.Mounts[i].Source) - if err != nil { - if os.IsNotExist(err) { - logrus.Warnf("couldn't find %q on host to bind mount into container", spec.Mounts[i].Source) - continue - } - return unmountAll, errors.Wrapf(err, "error checking if %q is a directory", spec.Mounts[i].Source) - } - stage := filepath.Join(mnt, fmt.Sprintf("buildah-bind-target-%d", i)) - if info.IsDir() { - // If the source is a directory, make one to use as the - // mount target. - if err = os.Mkdir(stage, 0000); err != nil { - return unmountAll, errors.Wrapf(err, "error creating directory %q", stage) - } - } else { - // If the source is not a directory, create an empty - // file to use as the mount target. - file, err := os.OpenFile(stage, os.O_WRONLY|os.O_CREATE, 0000) - if err != nil { - return unmountAll, errors.Wrapf(err, "error creating file %q", stage) - } - file.Close() - } - // Bind mount the source from wherever it is to a place where - // we know the runtime helper will be able to get to it... - if err = unix.Mount(spec.Mounts[i].Source, stage, "", unix.MS_BIND|unix.MS_REC|unix.MS_PRIVATE, ""); err != nil { - return unmountAll, errors.Wrapf(err, "error bind mounting bind object from %q to %q", spec.Mounts[i].Source, stage) - } - spec.Mounts[i].Source = stage - // ... and update the source location that we'll pass to the - // runtime to our intermediate location. - mounts = append(mounts, spec.Mounts[i]) - unmount = append([]string{stage}, unmount...) - } - spec.Mounts = mounts - - return unmountAll, nil -} - -// Decide if the mount should not be redirected to an intermediate location first. -func runLeaveBindMountAlone(mount specs.Mount) bool { - // If we know we shouldn't do a redirection for this mount, skip it. - if stringInSlice("nobuildahbind", mount.Options) { - return true - } - // If we're not bind mounting it in, we don't need to do anything for it. - if mount.Type != "bind" && !stringInSlice("bind", mount.Options) && !stringInSlice("rbind", mount.Options) { - return true - } - return false -} |