2019-10-08 17:53:36 +00:00
|
|
|
// +build linux
|
|
|
|
|
|
|
|
package libpod
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
2020-08-24 15:35:01 +00:00
|
|
|
"context"
|
2019-10-08 17:53:36 +00:00
|
|
|
"fmt"
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
"io"
|
2019-10-08 17:53:36 +00:00
|
|
|
"io/ioutil"
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
"net"
|
2020-08-24 15:35:01 +00:00
|
|
|
"net/http"
|
2019-10-08 17:53:36 +00:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2020-08-24 15:35:01 +00:00
|
|
|
"sync"
|
2019-10-08 17:53:36 +00:00
|
|
|
"syscall"
|
2020-01-07 12:41:56 +00:00
|
|
|
"text/template"
|
2019-10-08 17:53:36 +00:00
|
|
|
"time"
|
|
|
|
|
2020-03-27 14:13:51 +00:00
|
|
|
"github.com/containers/common/pkg/config"
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
conmonConfig "github.com/containers/conmon/runner/config"
|
2020-07-28 12:23:45 +00:00
|
|
|
"github.com/containers/podman/v2/libpod/define"
|
2020-08-24 15:35:01 +00:00
|
|
|
"github.com/containers/podman/v2/libpod/logs"
|
2020-07-28 12:23:45 +00:00
|
|
|
"github.com/containers/podman/v2/pkg/cgroups"
|
|
|
|
"github.com/containers/podman/v2/pkg/errorhandling"
|
|
|
|
"github.com/containers/podman/v2/pkg/lookup"
|
|
|
|
"github.com/containers/podman/v2/pkg/rootless"
|
|
|
|
"github.com/containers/podman/v2/pkg/util"
|
|
|
|
"github.com/containers/podman/v2/utils"
|
2019-10-08 17:53:36 +00:00
|
|
|
pmount "github.com/containers/storage/pkg/mount"
|
2020-03-10 17:18:58 +00:00
|
|
|
"github.com/coreos/go-systemd/v22/activation"
|
2020-06-19 13:29:34 +00:00
|
|
|
"github.com/coreos/go-systemd/v22/daemon"
|
2019-10-08 17:53:36 +00:00
|
|
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
|
|
|
"github.com/opencontainers/selinux/go-selinux"
|
|
|
|
"github.com/opencontainers/selinux/go-selinux/label"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"golang.org/x/sys/unix"
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
"k8s.io/client-go/tools/remotecommand"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// This is Conmon's STDIO_BUF_SIZE. I don't believe we have access to it
|
|
|
|
// directly from the Go cose, so const it here
|
|
|
|
bufferSize = conmonConfig.BufSize
|
2019-10-08 17:53:36 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// ConmonOCIRuntime is an OCI runtime managed by Conmon.
|
|
|
|
// TODO: Make all calls to OCI runtime have a timeout.
|
|
|
|
type ConmonOCIRuntime struct {
|
|
|
|
name string
|
|
|
|
path string
|
|
|
|
conmonPath string
|
|
|
|
conmonEnv []string
|
|
|
|
cgroupManager string
|
|
|
|
tmpDir string
|
|
|
|
exitsDir string
|
|
|
|
socketsDir string
|
|
|
|
logSizeMax int64
|
|
|
|
noPivot bool
|
|
|
|
reservePorts bool
|
|
|
|
supportsJSON bool
|
2020-04-15 18:48:53 +00:00
|
|
|
supportsKVM bool
|
2019-10-08 17:53:36 +00:00
|
|
|
supportsNoCgroups bool
|
|
|
|
sdNotify bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make a new Conmon-based OCI runtime with the given options.
|
|
|
|
// Conmon will wrap the given OCI runtime, which can be `runc`, `crun`, or
|
|
|
|
// any runtime with a runc-compatible CLI.
|
|
|
|
// The first path that points to a valid executable will be used.
|
|
|
|
// Deliberately private. Someone should not be able to construct this outside of
|
|
|
|
// libpod.
|
2020-04-15 18:48:53 +00:00
|
|
|
func newConmonOCIRuntime(name string, paths []string, conmonPath string, runtimeCfg *config.Config) (OCIRuntime, error) {
|
2019-10-08 17:53:36 +00:00
|
|
|
if name == "" {
|
|
|
|
return nil, errors.Wrapf(define.ErrInvalidArg, "the OCI runtime must be provided a non-empty name")
|
|
|
|
}
|
|
|
|
|
2020-04-15 18:48:53 +00:00
|
|
|
// Make lookup tables for runtime support
|
|
|
|
supportsJSON := make(map[string]bool, len(runtimeCfg.Engine.RuntimeSupportsJSON))
|
|
|
|
supportsNoCgroups := make(map[string]bool, len(runtimeCfg.Engine.RuntimeSupportsNoCgroups))
|
|
|
|
supportsKVM := make(map[string]bool, len(runtimeCfg.Engine.RuntimeSupportsKVM))
|
|
|
|
for _, r := range runtimeCfg.Engine.RuntimeSupportsJSON {
|
|
|
|
supportsJSON[r] = true
|
|
|
|
}
|
|
|
|
for _, r := range runtimeCfg.Engine.RuntimeSupportsNoCgroups {
|
|
|
|
supportsNoCgroups[r] = true
|
|
|
|
}
|
|
|
|
for _, r := range runtimeCfg.Engine.RuntimeSupportsKVM {
|
|
|
|
supportsKVM[r] = true
|
|
|
|
}
|
|
|
|
|
2019-10-08 17:53:36 +00:00
|
|
|
runtime := new(ConmonOCIRuntime)
|
|
|
|
runtime.name = name
|
|
|
|
runtime.conmonPath = conmonPath
|
|
|
|
|
2020-03-27 14:13:51 +00:00
|
|
|
runtime.conmonEnv = runtimeCfg.Engine.ConmonEnvVars
|
|
|
|
runtime.cgroupManager = runtimeCfg.Engine.CgroupManager
|
|
|
|
runtime.tmpDir = runtimeCfg.Engine.TmpDir
|
|
|
|
runtime.logSizeMax = runtimeCfg.Containers.LogSizeMax
|
|
|
|
runtime.noPivot = runtimeCfg.Engine.NoPivotRoot
|
|
|
|
runtime.reservePorts = runtimeCfg.Engine.EnablePortReservation
|
|
|
|
runtime.sdNotify = runtimeCfg.Engine.SDNotify
|
2019-10-08 17:53:36 +00:00
|
|
|
|
|
|
|
// TODO: probe OCI runtime for feature and enable automatically if
|
|
|
|
// available.
|
2020-04-15 18:48:53 +00:00
|
|
|
runtime.supportsJSON = supportsJSON[name]
|
|
|
|
runtime.supportsNoCgroups = supportsNoCgroups[name]
|
|
|
|
runtime.supportsKVM = supportsKVM[name]
|
2019-10-08 17:53:36 +00:00
|
|
|
|
|
|
|
foundPath := false
|
|
|
|
for _, path := range paths {
|
|
|
|
stat, err := os.Stat(path)
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return nil, errors.Wrapf(err, "cannot stat %s", path)
|
|
|
|
}
|
|
|
|
if !stat.Mode().IsRegular() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
foundPath = true
|
|
|
|
runtime.path = path
|
|
|
|
logrus.Debugf("using runtime %q", path)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Search the $PATH as last fallback
|
|
|
|
if !foundPath {
|
|
|
|
if foundRuntime, err := exec.LookPath(name); err == nil {
|
|
|
|
foundPath = true
|
|
|
|
runtime.path = foundRuntime
|
|
|
|
logrus.Debugf("using runtime %q from $PATH: %q", name, foundRuntime)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !foundPath {
|
|
|
|
return nil, errors.Wrapf(define.ErrInvalidArg, "no valid executable found for OCI runtime %s", name)
|
|
|
|
}
|
|
|
|
|
|
|
|
runtime.exitsDir = filepath.Join(runtime.tmpDir, "exits")
|
|
|
|
runtime.socketsDir = filepath.Join(runtime.tmpDir, "socket")
|
|
|
|
|
2020-03-27 14:13:51 +00:00
|
|
|
if runtime.cgroupManager != config.CgroupfsCgroupsManager && runtime.cgroupManager != config.SystemdCgroupsManager {
|
2019-10-08 17:53:36 +00:00
|
|
|
return nil, errors.Wrapf(define.ErrInvalidArg, "invalid cgroup manager specified: %s", runtime.cgroupManager)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the exit files and attach sockets directories
|
|
|
|
if err := os.MkdirAll(runtime.exitsDir, 0750); err != nil {
|
|
|
|
// The directory is allowed to exist
|
|
|
|
if !os.IsExist(err) {
|
|
|
|
return nil, errors.Wrapf(err, "error creating OCI runtime exit files directory %s",
|
|
|
|
runtime.exitsDir)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := os.MkdirAll(runtime.socketsDir, 0750); err != nil {
|
|
|
|
// The directory is allowed to exist
|
|
|
|
if !os.IsExist(err) {
|
|
|
|
return nil, errors.Wrapf(err, "error creating OCI runtime attach sockets directory %s",
|
|
|
|
runtime.socketsDir)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return runtime, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Name returns the name of the runtime being wrapped by Conmon.
|
|
|
|
func (r *ConmonOCIRuntime) Name() string {
|
|
|
|
return r.name
|
|
|
|
}
|
|
|
|
|
|
|
|
// Path returns the path of the OCI runtime being wrapped by Conmon.
|
|
|
|
func (r *ConmonOCIRuntime) Path() string {
|
|
|
|
return r.path
|
|
|
|
}
|
|
|
|
|
2020-01-14 12:23:59 +00:00
|
|
|
// hasCurrentUserMapped checks whether the current user is mapped inside the container user namespace
|
|
|
|
func hasCurrentUserMapped(ctr *Container) bool {
|
|
|
|
if len(ctr.config.IDMappings.UIDMap) == 0 && len(ctr.config.IDMappings.GIDMap) == 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
uid := os.Geteuid()
|
|
|
|
for _, m := range ctr.config.IDMappings.UIDMap {
|
|
|
|
if uid >= m.HostID && uid < m.HostID+m.Size {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-10-08 17:53:36 +00:00
|
|
|
// CreateContainer creates a container.
|
2020-07-09 17:50:01 +00:00
|
|
|
func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error {
|
2020-01-14 12:23:59 +00:00
|
|
|
if !hasCurrentUserMapped(ctr) {
|
2020-03-27 14:13:51 +00:00
|
|
|
for _, i := range []string{ctr.state.RunDir, ctr.runtime.config.Engine.TmpDir, ctr.config.StaticDir, ctr.state.Mountpoint, ctr.runtime.config.Engine.VolumePath} {
|
2019-10-08 17:53:36 +00:00
|
|
|
if err := makeAccessible(i, ctr.RootUID(), ctr.RootGID()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we are running a non privileged container, be sure to umount some kernel paths so they are not
|
|
|
|
// bind mounted inside the container at all.
|
|
|
|
if !ctr.config.Privileged && !rootless.IsRootless() {
|
|
|
|
ch := make(chan error)
|
|
|
|
go func() {
|
|
|
|
runtime.LockOSThread()
|
|
|
|
err := func() error {
|
|
|
|
fd, err := os.Open(fmt.Sprintf("/proc/%d/task/%d/ns/mnt", os.Getpid(), unix.Gettid()))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer errorhandling.CloseQuiet(fd)
|
|
|
|
|
|
|
|
// create a new mountns on the current thread
|
|
|
|
if err = unix.Unshare(unix.CLONE_NEWNS); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err := unix.Setns(int(fd.Fd()), unix.CLONE_NEWNS); err != nil {
|
|
|
|
logrus.Errorf("unable to clone new namespace: %q", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// don't spread our mounts around. We are setting only /sys to be slave
|
|
|
|
// so that the cleanup process is still able to umount the storage and the
|
|
|
|
// changes are propagated to the host.
|
|
|
|
err = unix.Mount("/sys", "/sys", "none", unix.MS_REC|unix.MS_SLAVE, "")
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "cannot make /sys slave")
|
|
|
|
}
|
|
|
|
|
|
|
|
mounts, err := pmount.GetMounts()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, m := range mounts {
|
|
|
|
if !strings.HasPrefix(m.Mountpoint, "/sys/kernel") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
err = unix.Unmount(m.Mountpoint, 0)
|
|
|
|
if err != nil && !os.IsNotExist(err) {
|
|
|
|
return errors.Wrapf(err, "cannot unmount %s", m.Mountpoint)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return r.createOCIContainer(ctr, restoreOptions)
|
|
|
|
}()
|
|
|
|
ch <- err
|
|
|
|
}()
|
|
|
|
err := <-ch
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return r.createOCIContainer(ctr, restoreOptions)
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateContainerStatus retrieves the current status of the container from the
|
|
|
|
// runtime. It updates the container's state but does not save it.
|
|
|
|
// If useRuntime is false, we will not directly hit runc to see the container's
|
|
|
|
// status, but will instead only check for the existence of the conmon exit file
|
|
|
|
// and update state to stopped if it exists.
|
2019-10-15 19:11:26 +00:00
|
|
|
func (r *ConmonOCIRuntime) UpdateContainerStatus(ctr *Container) error {
|
|
|
|
exitFile, err := r.ExitFilePath(ctr)
|
2019-10-08 17:53:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
runtimeDir, err := util.GetRuntimeDir()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store old state so we know if we were already stopped
|
|
|
|
oldState := ctr.state.State
|
|
|
|
|
|
|
|
state := new(spec.State)
|
|
|
|
|
|
|
|
cmd := exec.Command(r.path, "state", ctr.ID())
|
|
|
|
cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir))
|
|
|
|
|
|
|
|
outPipe, err := cmd.StdoutPipe()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "getting stdout pipe")
|
|
|
|
}
|
|
|
|
errPipe, err := cmd.StderrPipe()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "getting stderr pipe")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
out, err2 := ioutil.ReadAll(errPipe)
|
|
|
|
if err2 != nil {
|
|
|
|
return errors.Wrapf(err, "error getting container %s state", ctr.ID())
|
|
|
|
}
|
|
|
|
if strings.Contains(string(out), "does not exist") {
|
|
|
|
if err := ctr.removeConmonFiles(); err != nil {
|
|
|
|
logrus.Debugf("unable to remove conmon files for container %s", ctr.ID())
|
|
|
|
}
|
|
|
|
ctr.state.ExitCode = -1
|
|
|
|
ctr.state.FinishedTime = time.Now()
|
|
|
|
ctr.state.State = define.ContainerStateExited
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return errors.Wrapf(err, "error getting container %s state. stderr/out: %s", ctr.ID(), out)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
_ = cmd.Wait()
|
|
|
|
}()
|
|
|
|
|
|
|
|
if err := errPipe.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
out, err := ioutil.ReadAll(outPipe)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error reading stdout: %s", ctr.ID())
|
|
|
|
}
|
|
|
|
if err := json.NewDecoder(bytes.NewBuffer(out)).Decode(state); err != nil {
|
|
|
|
return errors.Wrapf(err, "error decoding container status for container %s", ctr.ID())
|
|
|
|
}
|
|
|
|
ctr.state.PID = state.Pid
|
|
|
|
|
|
|
|
switch state.Status {
|
|
|
|
case "created":
|
|
|
|
ctr.state.State = define.ContainerStateCreated
|
|
|
|
case "paused":
|
|
|
|
ctr.state.State = define.ContainerStatePaused
|
|
|
|
case "running":
|
|
|
|
ctr.state.State = define.ContainerStateRunning
|
|
|
|
case "stopped":
|
|
|
|
ctr.state.State = define.ContainerStateStopped
|
|
|
|
default:
|
|
|
|
return errors.Wrapf(define.ErrInternal, "unrecognized status returned by runtime for container %s: %s",
|
|
|
|
ctr.ID(), state.Status)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only grab exit status if we were not already stopped
|
|
|
|
// If we were, it should already be in the database
|
|
|
|
if ctr.state.State == define.ContainerStateStopped && oldState != define.ContainerStateStopped {
|
|
|
|
var fi os.FileInfo
|
|
|
|
chWait := make(chan error)
|
|
|
|
defer close(chWait)
|
|
|
|
|
|
|
|
_, err := WaitForFile(exitFile, chWait, time.Second*5)
|
|
|
|
if err == nil {
|
|
|
|
fi, err = os.Stat(exitFile)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
ctr.state.ExitCode = -1
|
|
|
|
ctr.state.FinishedTime = time.Now()
|
|
|
|
logrus.Errorf("No exit file for container %s found: %v", ctr.ID(), err)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return ctr.handleExitFile(exitFile, fi)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// StartContainer starts the given container.
|
|
|
|
// Sets time the container was started, but does not save it.
|
|
|
|
func (r *ConmonOCIRuntime) StartContainer(ctr *Container) error {
|
|
|
|
// TODO: streams should probably *not* be our STDIN/OUT/ERR - redirect to buffers?
|
|
|
|
runtimeDir, err := util.GetRuntimeDir()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)}
|
2020-06-19 13:29:34 +00:00
|
|
|
if ctr.config.SdNotifyMode == define.SdNotifyModeContainer {
|
|
|
|
if notify, ok := os.LookupEnv("NOTIFY_SOCKET"); ok {
|
|
|
|
env = append(env, fmt.Sprintf("NOTIFY_SOCKET=%s", notify))
|
|
|
|
}
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
2020-04-03 15:45:51 +00:00
|
|
|
if path, ok := os.LookupEnv("PATH"); ok {
|
|
|
|
env = append(env, fmt.Sprintf("PATH=%s", path))
|
|
|
|
}
|
2019-10-08 17:53:36 +00:00
|
|
|
if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "start", ctr.ID()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctr.state.StartedTime = time.Now()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// KillContainer sends the given signal to the given container.
|
|
|
|
// If all is set, send to all PIDs in the container.
|
|
|
|
// All is only supported if the container created cgroups.
|
|
|
|
func (r *ConmonOCIRuntime) KillContainer(ctr *Container, signal uint, all bool) error {
|
|
|
|
logrus.Debugf("Sending signal %d to container %s", signal, ctr.ID())
|
|
|
|
runtimeDir, err := util.GetRuntimeDir()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)}
|
|
|
|
var args []string
|
|
|
|
if all {
|
|
|
|
args = []string{"kill", "--all", ctr.ID(), fmt.Sprintf("%d", signal)}
|
|
|
|
} else {
|
|
|
|
args = []string{"kill", ctr.ID(), fmt.Sprintf("%d", signal)}
|
|
|
|
}
|
|
|
|
if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, args...); err != nil {
|
|
|
|
return errors.Wrapf(err, "error sending signal to container %s", ctr.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// StopContainer stops a container, first using its given stop signal (or
|
|
|
|
// SIGTERM if no signal was specified), then using SIGKILL.
|
|
|
|
// Timeout is given in seconds. If timeout is 0, the container will be
|
|
|
|
// immediately kill with SIGKILL.
|
|
|
|
// Does not set finished time for container, assumes you will run updateStatus
|
|
|
|
// after to pull the exit code.
|
|
|
|
func (r *ConmonOCIRuntime) StopContainer(ctr *Container, timeout uint, all bool) error {
|
|
|
|
logrus.Debugf("Stopping container %s (PID %d)", ctr.ID(), ctr.state.PID)
|
|
|
|
|
|
|
|
// Ping the container to see if it's alive
|
|
|
|
// If it's not, it's already stopped, return
|
|
|
|
err := unix.Kill(ctr.state.PID, 0)
|
|
|
|
if err == unix.ESRCH {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
stopSignal := ctr.config.StopSignal
|
|
|
|
if stopSignal == 0 {
|
|
|
|
stopSignal = uint(syscall.SIGTERM)
|
|
|
|
}
|
|
|
|
|
|
|
|
if timeout > 0 {
|
|
|
|
if err := r.KillContainer(ctr, stopSignal, all); err != nil {
|
|
|
|
// Is the container gone?
|
|
|
|
// If so, it probably died between the first check and
|
|
|
|
// our sending the signal
|
|
|
|
// The container is stopped, so exit cleanly
|
|
|
|
err := unix.Kill(ctr.state.PID, 0)
|
|
|
|
if err == unix.ESRCH {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := waitContainerStop(ctr, time.Duration(timeout)*time.Second); err != nil {
|
|
|
|
logrus.Warnf("Timed out stopping container %s, resorting to SIGKILL", ctr.ID())
|
|
|
|
} else {
|
|
|
|
// No error, the container is dead
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := r.KillContainer(ctr, 9, all); err != nil {
|
|
|
|
// Again, check if the container is gone. If it is, exit cleanly.
|
|
|
|
err := unix.Kill(ctr.state.PID, 0)
|
|
|
|
if err == unix.ESRCH {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.Wrapf(err, "error sending SIGKILL to container %s", ctr.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Give runtime a few seconds to make it happen
|
|
|
|
if err := waitContainerStop(ctr, killContainerTimeout); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteContainer deletes a container from the OCI runtime.
|
|
|
|
func (r *ConmonOCIRuntime) DeleteContainer(ctr *Container) error {
|
|
|
|
runtimeDir, err := util.GetRuntimeDir()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)}
|
|
|
|
return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "delete", "--force", ctr.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
// PauseContainer pauses the given container.
|
|
|
|
func (r *ConmonOCIRuntime) PauseContainer(ctr *Container) error {
|
|
|
|
runtimeDir, err := util.GetRuntimeDir()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)}
|
|
|
|
return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "pause", ctr.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnpauseContainer unpauses the given container.
|
|
|
|
func (r *ConmonOCIRuntime) UnpauseContainer(ctr *Container) error {
|
|
|
|
runtimeDir, err := util.GetRuntimeDir()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)}
|
|
|
|
return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "resume", ctr.ID())
|
|
|
|
}
|
|
|
|
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
// HTTPAttach performs an attach for the HTTP API.
|
2020-04-07 20:52:47 +00:00
|
|
|
// The caller must handle closing the HTTP connection after this returns.
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
// The cancel channel is not closed; it is up to the caller to do so after
|
|
|
|
// this function returns.
|
|
|
|
// If this is a container with a terminal, we will stream raw. If it is not, we
|
|
|
|
// will stream with an 8-byte header to multiplex STDOUT and STDERR.
|
2020-08-24 15:35:01 +00:00
|
|
|
// Returns any errors that occurred, and whether the connection was successfully
|
|
|
|
// hijacked before that error occurred.
|
|
|
|
func (r *ConmonOCIRuntime) HTTPAttach(ctr *Container, req *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, detachKeys *string, cancel <-chan bool, hijackDone chan<- bool, streamAttach, streamLogs bool) (deferredErr error) {
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
isTerminal := false
|
|
|
|
if ctr.config.Spec.Process != nil {
|
|
|
|
isTerminal = ctr.config.Spec.Process.Terminal
|
|
|
|
}
|
|
|
|
|
|
|
|
if streams != nil {
|
|
|
|
if !streams.Stdin && !streams.Stdout && !streams.Stderr {
|
|
|
|
return errors.Wrapf(define.ErrInvalidArg, "must specify at least one stream to attach to")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
attachSock, err := r.AttachSocketPath(ctr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
socketPath := buildSocketPath(attachSock)
|
|
|
|
|
2020-08-24 15:35:01 +00:00
|
|
|
var conn *net.UnixConn
|
|
|
|
if streamAttach {
|
|
|
|
newConn, err := net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: socketPath, Net: "unixpacket"})
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "failed to connect to container's attach socket: %v", socketPath)
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
}
|
2020-08-24 15:35:01 +00:00
|
|
|
conn = newConn
|
|
|
|
defer func() {
|
|
|
|
if err := conn.Close(); err != nil {
|
|
|
|
logrus.Errorf("unable to close container %s attach socket: %q", ctr.ID(), err)
|
|
|
|
}
|
|
|
|
}()
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
|
2020-08-24 15:35:01 +00:00
|
|
|
logrus.Debugf("Successfully connected to container %s attach socket %s", ctr.ID(), socketPath)
|
|
|
|
}
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
|
2020-03-27 14:13:51 +00:00
|
|
|
detachString := ctr.runtime.config.Engine.DetachKeys
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
if detachKeys != nil {
|
|
|
|
detachString = *detachKeys
|
|
|
|
}
|
|
|
|
detach, err := processDetachKeys(detachString)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make a channel to pass errors back
|
|
|
|
errChan := make(chan error)
|
|
|
|
|
|
|
|
attachStdout := true
|
|
|
|
attachStderr := true
|
|
|
|
attachStdin := true
|
|
|
|
if streams != nil {
|
|
|
|
attachStdout = streams.Stdout
|
|
|
|
attachStderr = streams.Stderr
|
|
|
|
attachStdin = streams.Stdin
|
|
|
|
}
|
|
|
|
|
2020-08-24 15:35:01 +00:00
|
|
|
logrus.Debugf("Going to hijack container %s attach connection", ctr.ID())
|
|
|
|
|
|
|
|
// Alright, let's hijack.
|
|
|
|
hijacker, ok := w.(http.Hijacker)
|
|
|
|
if !ok {
|
|
|
|
return errors.Errorf("unable to hijack connection")
|
|
|
|
}
|
|
|
|
|
|
|
|
httpCon, httpBuf, err := hijacker.Hijack()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error hijacking connection")
|
|
|
|
}
|
|
|
|
|
|
|
|
hijackDone <- true
|
|
|
|
|
|
|
|
writeHijackHeader(req, httpBuf)
|
|
|
|
|
|
|
|
// Force a flush after the header is written.
|
|
|
|
if err := httpBuf.Flush(); err != nil {
|
|
|
|
return errors.Wrapf(err, "error flushing HTTP hijack header")
|
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
hijackWriteErrorAndClose(deferredErr, ctr.ID(), isTerminal, httpCon, httpBuf)
|
|
|
|
}()
|
|
|
|
|
|
|
|
logrus.Debugf("Hijack for container %s attach session done, ready to stream", ctr.ID())
|
|
|
|
|
|
|
|
// TODO: This is gross. Really, really gross.
|
|
|
|
// I want to say we should read all the logs into an array before
|
|
|
|
// calling this, in container_api.go, but that could take a lot of
|
|
|
|
// memory...
|
|
|
|
// On the whole, we need to figure out a better way of doing this,
|
|
|
|
// though.
|
|
|
|
logSize := 0
|
|
|
|
if streamLogs {
|
|
|
|
logrus.Debugf("Will stream logs for container %s attach session", ctr.ID())
|
|
|
|
|
|
|
|
// Get all logs for the container
|
|
|
|
logChan := make(chan *logs.LogLine)
|
|
|
|
logOpts := new(logs.LogOptions)
|
|
|
|
logOpts.Tail = -1
|
|
|
|
logOpts.WaitGroup = new(sync.WaitGroup)
|
|
|
|
errChan := make(chan error)
|
|
|
|
go func() {
|
|
|
|
var err error
|
|
|
|
// In non-terminal mode we need to prepend with the
|
|
|
|
// stream header.
|
|
|
|
logrus.Debugf("Writing logs for container %s to HTTP attach", ctr.ID())
|
|
|
|
for logLine := range logChan {
|
|
|
|
if !isTerminal {
|
|
|
|
device := logLine.Device
|
|
|
|
var header []byte
|
|
|
|
headerLen := uint32(len(logLine.Msg))
|
|
|
|
logSize += len(logLine.Msg)
|
|
|
|
switch strings.ToLower(device) {
|
|
|
|
case "stdin":
|
|
|
|
header = makeHTTPAttachHeader(0, headerLen)
|
|
|
|
case "stdout":
|
|
|
|
header = makeHTTPAttachHeader(1, headerLen)
|
|
|
|
case "stderr":
|
|
|
|
header = makeHTTPAttachHeader(2, headerLen)
|
|
|
|
default:
|
|
|
|
logrus.Errorf("Unknown device for log line: %s", device)
|
|
|
|
header = makeHTTPAttachHeader(1, headerLen)
|
|
|
|
}
|
|
|
|
_, err = httpBuf.Write(header)
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_, err = httpBuf.Write([]byte(logLine.Msg))
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
_, err = httpBuf.Write([]byte("\n"))
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
err = httpBuf.Flush()
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
errChan <- err
|
|
|
|
}()
|
|
|
|
go func() {
|
|
|
|
logOpts.WaitGroup.Wait()
|
|
|
|
close(logChan)
|
|
|
|
}()
|
|
|
|
if err := ctr.ReadLog(context.Background(), logOpts, logChan); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
logrus.Debugf("Done reading logs for container %s, %d bytes", ctr.ID(), logSize)
|
|
|
|
if err := <-errChan; err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !streamAttach {
|
|
|
|
logrus.Debugf("Done streaming logs for container %s attach, exiting as attach streaming not requested", ctr.ID())
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Debugf("Forwarding attach output for container %s", ctr.ID())
|
|
|
|
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
// Handle STDOUT/STDERR
|
|
|
|
go func() {
|
|
|
|
var err error
|
|
|
|
if isTerminal {
|
2020-04-07 20:52:47 +00:00
|
|
|
// Hack: return immediately if attachStdout not set to
|
|
|
|
// emulate Docker.
|
|
|
|
// Basically, when terminal is set, STDERR goes nowhere.
|
|
|
|
// Everything does over STDOUT.
|
|
|
|
// Therefore, if not attaching STDOUT - we'll never copy
|
|
|
|
// anything from here.
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
logrus.Debugf("Performing terminal HTTP attach for container %s", ctr.ID())
|
2020-04-07 20:52:47 +00:00
|
|
|
if attachStdout {
|
|
|
|
err = httpAttachTerminalCopy(conn, httpBuf, ctr.ID())
|
|
|
|
}
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
} else {
|
|
|
|
logrus.Debugf("Performing non-terminal HTTP attach for container %s", ctr.ID())
|
|
|
|
err = httpAttachNonTerminalCopy(conn, httpBuf, ctr.ID(), attachStdin, attachStdout, attachStderr)
|
|
|
|
}
|
|
|
|
errChan <- err
|
|
|
|
logrus.Debugf("STDOUT/ERR copy completed")
|
|
|
|
}()
|
|
|
|
// Next, STDIN. Avoid entirely if attachStdin unset.
|
|
|
|
if attachStdin {
|
|
|
|
go func() {
|
|
|
|
_, err := utils.CopyDetachable(conn, httpBuf, detach)
|
|
|
|
logrus.Debugf("STDIN copy completed")
|
|
|
|
errChan <- err
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
if cancel != nil {
|
|
|
|
select {
|
|
|
|
case err := <-errChan:
|
|
|
|
return err
|
|
|
|
case <-cancel:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var connErr error = <-errChan
|
|
|
|
return connErr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-31 09:15:13 +00:00
|
|
|
// isRetryable returns whether the error was caused by a blocked syscall or the
|
|
|
|
// specified operation on a non blocking file descriptor wasn't ready for completion.
|
|
|
|
func isRetryable(err error) bool {
|
|
|
|
if errno, isErrno := errors.Cause(err).(syscall.Errno); isErrno {
|
|
|
|
return errno == syscall.EINTR || errno == syscall.EAGAIN
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// openControlFile opens the terminal control file.
|
|
|
|
func openControlFile(ctr *Container, parentDir string) (*os.File, error) {
|
|
|
|
controlPath := filepath.Join(parentDir, "ctl")
|
|
|
|
for i := 0; i < 600; i++ {
|
|
|
|
controlFile, err := os.OpenFile(controlPath, unix.O_WRONLY|unix.O_NONBLOCK, 0)
|
|
|
|
if err == nil {
|
|
|
|
return controlFile, err
|
|
|
|
}
|
|
|
|
if !isRetryable(err) {
|
|
|
|
return nil, errors.Wrapf(err, "could not open ctl file for terminal resize for container %s", ctr.ID())
|
|
|
|
}
|
|
|
|
time.Sleep(time.Second / 10)
|
|
|
|
}
|
|
|
|
return nil, errors.Errorf("timeout waiting for %q", controlPath)
|
|
|
|
}
|
|
|
|
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
// AttachResize resizes the terminal used by the given container.
|
|
|
|
func (r *ConmonOCIRuntime) AttachResize(ctr *Container, newSize remotecommand.TerminalSize) error {
|
2020-03-31 09:15:13 +00:00
|
|
|
controlFile, err := openControlFile(ctr, ctr.bundlePath())
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
if err != nil {
|
2020-03-31 09:15:13 +00:00
|
|
|
return err
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
}
|
|
|
|
defer controlFile.Close()
|
|
|
|
|
|
|
|
logrus.Debugf("Received a resize event for container %s: %+v", ctr.ID(), newSize)
|
|
|
|
if _, err = fmt.Fprintf(controlFile, "%d %d %d\n", 1, newSize.Height, newSize.Width); err != nil {
|
|
|
|
return errors.Wrapf(err, "failed to write to ctl file to resize terminal")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-08 17:53:36 +00:00
|
|
|
// CheckpointContainer checkpoints the given container.
|
|
|
|
func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) error {
|
|
|
|
if err := label.SetSocketLabel(ctr.ProcessLabel()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// imagePath is used by CRIU to store the actual checkpoint files
|
|
|
|
imagePath := ctr.CheckpointPath()
|
|
|
|
// workPath will be used to store dump.log and stats-dump
|
|
|
|
workPath := ctr.bundlePath()
|
|
|
|
logrus.Debugf("Writing checkpoint to %s", imagePath)
|
|
|
|
logrus.Debugf("Writing checkpoint logs to %s", workPath)
|
|
|
|
args := []string{}
|
|
|
|
args = append(args, "checkpoint")
|
|
|
|
args = append(args, "--image-path")
|
|
|
|
args = append(args, imagePath)
|
|
|
|
args = append(args, "--work-path")
|
|
|
|
args = append(args, workPath)
|
|
|
|
if options.KeepRunning {
|
|
|
|
args = append(args, "--leave-running")
|
|
|
|
}
|
|
|
|
if options.TCPEstablished {
|
|
|
|
args = append(args, "--tcp-established")
|
|
|
|
}
|
2020-04-03 07:59:49 +00:00
|
|
|
runtimeDir, err := util.GetRuntimeDir()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err = os.Setenv("XDG_RUNTIME_DIR", runtimeDir); err != nil {
|
|
|
|
return errors.Wrapf(err, "cannot set XDG_RUNTIME_DIR")
|
|
|
|
}
|
2019-10-08 17:53:36 +00:00
|
|
|
args = append(args, ctr.ID())
|
|
|
|
return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, args...)
|
|
|
|
}
|
|
|
|
|
Ensure Conmon is alive before waiting for exit file
This came out of a conversation with Valentin about
systemd-managed Podman. He discovered that unit files did not
properly handle cases where Conmon was dead - the ExecStopPost
`podman rm --force` line was not actually removing the container,
but interestingly, adding a `podman cleanup --rm` line would
remove it. Both of these commands do the same thing (minus the
`podman cleanup --rm` command not force-removing running
containers).
Without a running Conmon instance, the container process is still
running (assuming you killed Conmon with SIGKILL and it had no
chance to kill the container it managed), but you can still kill
the container itself with `podman stop` - Conmon is not involved,
only the OCI Runtime. (`podman rm --force` and `podman stop` use
the same code to kill the container). The problem comes when we
want to get the container's exit code - we expect Conmon to make
us an exit file, which it's obviously not going to do, being
dead. The first `podman rm` would fail because of this, but
importantly, it would (after failing to retrieve the exit code
correctly) set container status to Exited, so that the second
`podman cleanup` process would succeed.
To make sure the first `podman rm --force` succeeds, we need to
catch the case where Conmon is already dead, and instead of
waiting for an exit file that will never come, immediately set
the Stopped state and remove an error that can be caught and
handled.
Signed-off-by: Matthew Heon <mheon@redhat.com>
2020-06-08 17:34:12 +00:00
|
|
|
func (r *ConmonOCIRuntime) CheckConmonRunning(ctr *Container) (bool, error) {
|
|
|
|
if ctr.state.ConmonPID == 0 {
|
|
|
|
// If the container is running or paused, assume Conmon is
|
|
|
|
// running. We didn't record Conmon PID on some old versions, so
|
|
|
|
// that is likely what's going on...
|
|
|
|
// Unusual enough that we should print a warning message though.
|
|
|
|
if ctr.ensureState(define.ContainerStateRunning, define.ContainerStatePaused) {
|
|
|
|
logrus.Warnf("Conmon PID is not set, but container is running!")
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
// Container's not running, so conmon PID being unset is
|
|
|
|
// expected. Conmon is not running.
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// We have a conmon PID. Ping it with signal 0.
|
|
|
|
if err := unix.Kill(ctr.state.ConmonPID, 0); err != nil {
|
|
|
|
if err == unix.ESRCH {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
return false, errors.Wrapf(err, "error pinging container %s conmon with signal 0", ctr.ID())
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2019-10-08 17:53:36 +00:00
|
|
|
// SupportsCheckpoint checks if the OCI runtime supports checkpointing
|
|
|
|
// containers.
|
|
|
|
func (r *ConmonOCIRuntime) SupportsCheckpoint() bool {
|
|
|
|
// Check if the runtime implements checkpointing. Currently only
|
|
|
|
// runc's checkpoint/restore implementation is supported.
|
2020-04-03 07:51:18 +00:00
|
|
|
cmd := exec.Command(r.path, "checkpoint", "--help")
|
2019-10-08 17:53:36 +00:00
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if err := cmd.Wait(); err == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// SupportsJSONErrors checks if the OCI runtime supports JSON-formatted error
|
|
|
|
// messages.
|
|
|
|
func (r *ConmonOCIRuntime) SupportsJSONErrors() bool {
|
|
|
|
return r.supportsJSON
|
|
|
|
}
|
|
|
|
|
|
|
|
// SupportsNoCgroups checks if the OCI runtime supports running containers
|
|
|
|
// without cgroups (the --cgroup-manager=disabled flag).
|
|
|
|
func (r *ConmonOCIRuntime) SupportsNoCgroups() bool {
|
|
|
|
return r.supportsNoCgroups
|
|
|
|
}
|
|
|
|
|
2020-04-15 18:48:53 +00:00
|
|
|
// SupportsKVM checks if the OCI runtime supports running containers
|
|
|
|
// without KVM separation
|
|
|
|
func (r *ConmonOCIRuntime) SupportsKVM() bool {
|
|
|
|
return r.supportsKVM
|
|
|
|
}
|
|
|
|
|
2019-10-08 17:53:36 +00:00
|
|
|
// AttachSocketPath is the path to a single container's attach socket.
|
|
|
|
func (r *ConmonOCIRuntime) AttachSocketPath(ctr *Container) (string, error) {
|
|
|
|
if ctr == nil {
|
|
|
|
return "", errors.Wrapf(define.ErrInvalidArg, "must provide a valid container to get attach socket path")
|
|
|
|
}
|
|
|
|
|
|
|
|
return filepath.Join(r.socketsDir, ctr.ID(), "attach"), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExitFilePath is the path to a container's exit file.
|
|
|
|
func (r *ConmonOCIRuntime) ExitFilePath(ctr *Container) (string, error) {
|
|
|
|
if ctr == nil {
|
|
|
|
return "", errors.Wrapf(define.ErrInvalidArg, "must provide a valid container to get exit file path")
|
|
|
|
}
|
|
|
|
return filepath.Join(r.exitsDir, ctr.ID()), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RuntimeInfo provides information on the runtime.
|
2020-03-15 16:53:59 +00:00
|
|
|
func (r *ConmonOCIRuntime) RuntimeInfo() (*define.ConmonInfo, *define.OCIRuntimeInfo, error) {
|
2019-10-08 17:53:36 +00:00
|
|
|
runtimePackage := packageVersion(r.path)
|
|
|
|
conmonPackage := packageVersion(r.conmonPath)
|
|
|
|
runtimeVersion, err := r.getOCIRuntimeVersion()
|
|
|
|
if err != nil {
|
2020-03-15 16:53:59 +00:00
|
|
|
return nil, nil, errors.Wrapf(err, "error getting version of OCI runtime %s", r.name)
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
|
|
|
conmonVersion, err := r.getConmonVersion()
|
|
|
|
if err != nil {
|
2020-03-15 16:53:59 +00:00
|
|
|
return nil, nil, errors.Wrapf(err, "error getting conmon version")
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
|
|
|
|
2020-03-15 16:53:59 +00:00
|
|
|
conmon := define.ConmonInfo{
|
|
|
|
Package: conmonPackage,
|
|
|
|
Path: r.conmonPath,
|
|
|
|
Version: conmonVersion,
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
2020-03-15 16:53:59 +00:00
|
|
|
ocirt := define.OCIRuntimeInfo{
|
|
|
|
Name: r.name,
|
|
|
|
Path: r.path,
|
|
|
|
Package: runtimePackage,
|
|
|
|
Version: runtimeVersion,
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
2020-03-15 16:53:59 +00:00
|
|
|
return &conmon, &ocirt, nil
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// makeAccessible changes the path permission and each parent directory to have --x--x--x
|
|
|
|
func makeAccessible(path string, uid, gid int) error {
|
|
|
|
for ; path != "/"; path = filepath.Dir(path) {
|
|
|
|
st, err := os.Stat(path)
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if int(st.Sys().(*syscall.Stat_t).Uid) == uid && int(st.Sys().(*syscall.Stat_t).Gid) == gid {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if st.Mode()&0111 != 0111 {
|
|
|
|
if err := os.Chmod(path, st.Mode()|0111); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for a container which has been sent a signal to stop
|
|
|
|
func waitContainerStop(ctr *Container, timeout time.Duration) error {
|
|
|
|
return waitPidStop(ctr.state.PID, timeout)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for a given PID to stop
|
|
|
|
func waitPidStop(pid int, timeout time.Duration) error {
|
|
|
|
done := make(chan struct{})
|
|
|
|
chControl := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-chControl:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
if err := unix.Kill(pid, 0); err != nil {
|
|
|
|
if err == unix.ESRCH {
|
|
|
|
close(done)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
logrus.Errorf("Error pinging PID %d with signal 0: %v", pid, err)
|
|
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
select {
|
|
|
|
case <-done:
|
|
|
|
return nil
|
|
|
|
case <-time.After(timeout):
|
|
|
|
close(chControl)
|
|
|
|
return errors.Errorf("given PIDs did not die within timeout")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-07 12:41:56 +00:00
|
|
|
func (r *ConmonOCIRuntime) getLogTag(ctr *Container) (string, error) {
|
|
|
|
logTag := ctr.LogTag()
|
|
|
|
if logTag == "" {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
data, err := ctr.inspectLocked(false)
|
|
|
|
if err != nil {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
tmpl, err := template.New("container").Parse(logTag)
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrapf(err, "template parsing error %s", logTag)
|
|
|
|
}
|
|
|
|
var b bytes.Buffer
|
|
|
|
err = tmpl.Execute(&b, data)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return b.String(), nil
|
|
|
|
}
|
|
|
|
|
2019-10-08 17:53:36 +00:00
|
|
|
// createOCIContainer generates this container's main conmon instance and prepares it for starting
|
2020-07-09 17:50:01 +00:00
|
|
|
func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error {
|
2019-10-08 17:53:36 +00:00
|
|
|
var stderrBuf bytes.Buffer
|
|
|
|
|
|
|
|
runtimeDir, err := util.GetRuntimeDir()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
parentSyncPipe, childSyncPipe, err := newPipe()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error creating socket pair")
|
|
|
|
}
|
|
|
|
defer errorhandling.CloseQuiet(parentSyncPipe)
|
|
|
|
|
|
|
|
childStartPipe, parentStartPipe, err := newPipe()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error creating socket pair for start pipe")
|
|
|
|
}
|
|
|
|
|
|
|
|
defer errorhandling.CloseQuiet(parentStartPipe)
|
|
|
|
|
|
|
|
var ociLog string
|
|
|
|
if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON {
|
|
|
|
ociLog = filepath.Join(ctr.state.RunDir, "oci-log")
|
|
|
|
}
|
2020-01-07 12:41:56 +00:00
|
|
|
|
|
|
|
logTag, err := r.getLogTag(ctr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-06-18 11:56:30 +00:00
|
|
|
if ctr.config.CgroupsMode == cgroupSplit {
|
|
|
|
if err := utils.MoveUnderCgroupSubtree("supervisor"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-19 13:29:34 +00:00
|
|
|
if ctr.config.SdNotifyMode == define.SdNotifyModeIgnore {
|
|
|
|
if err := os.Unsetenv("NOTIFY_SOCKET"); err != nil {
|
|
|
|
logrus.Warnf("Error unsetting NOTIFY_SOCKET %s", err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-10 18:35:00 +00:00
|
|
|
args := r.sharedConmonArgs(ctr, ctr.ID(), ctr.bundlePath(), filepath.Join(ctr.state.RunDir, "pidfile"), ctr.LogPath(), r.exitsDir, ociLog, ctr.LogDriver(), logTag)
|
2019-10-08 17:53:36 +00:00
|
|
|
|
|
|
|
if ctr.config.Spec.Process.Terminal {
|
|
|
|
args = append(args, "-t")
|
|
|
|
} else if ctr.config.Stdin {
|
|
|
|
args = append(args, "-i")
|
|
|
|
}
|
|
|
|
|
|
|
|
if ctr.config.ConmonPidFile != "" {
|
|
|
|
args = append(args, "--conmon-pidfile", ctr.config.ConmonPidFile)
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.noPivot {
|
|
|
|
args = append(args, "--no-pivot")
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(ctr.config.ExitCommand) > 0 {
|
|
|
|
args = append(args, "--exit-command", ctr.config.ExitCommand[0])
|
|
|
|
for _, arg := range ctr.config.ExitCommand[1:] {
|
|
|
|
args = append(args, []string{"--exit-command-arg", arg}...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-16 18:22:05 +00:00
|
|
|
if ctr.config.PreserveFDs > 0 {
|
|
|
|
args = append(args, formatRuntimeOpts("--preserve-fds", fmt.Sprintf("%d", ctr.config.PreserveFDs))...)
|
|
|
|
}
|
|
|
|
|
2019-10-08 17:53:36 +00:00
|
|
|
if restoreOptions != nil {
|
|
|
|
args = append(args, "--restore", ctr.CheckpointPath())
|
|
|
|
if restoreOptions.TCPEstablished {
|
|
|
|
args = append(args, "--runtime-opt", "--tcp-established")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.WithFields(logrus.Fields{
|
|
|
|
"args": args,
|
|
|
|
}).Debugf("running conmon: %s", r.conmonPath)
|
|
|
|
|
|
|
|
cmd := exec.Command(r.conmonPath, args...)
|
|
|
|
cmd.Dir = ctr.bundlePath()
|
|
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
|
|
Setpgid: true,
|
|
|
|
}
|
|
|
|
// TODO this is probably a really bad idea for some uses
|
|
|
|
// Make this configurable
|
|
|
|
cmd.Stdin = os.Stdin
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if ctr.config.Spec.Process.Terminal {
|
|
|
|
cmd.Stderr = &stderrBuf
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0, 1 and 2 are stdin, stdout and stderr
|
2020-06-19 13:29:34 +00:00
|
|
|
conmonEnv, envFiles, err := r.configureConmonEnv(ctr, runtimeDir)
|
2019-10-08 17:53:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-07-29 14:27:12 +00:00
|
|
|
var filesToClose []*os.File
|
2020-06-16 18:22:05 +00:00
|
|
|
if ctr.config.PreserveFDs > 0 {
|
|
|
|
for fd := 3; fd < int(3+ctr.config.PreserveFDs); fd++ {
|
2020-07-29 14:27:12 +00:00
|
|
|
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))
|
|
|
|
filesToClose = append(filesToClose, f)
|
|
|
|
cmd.ExtraFiles = append(cmd.ExtraFiles, f)
|
2020-06-16 18:22:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-13 12:01:45 +00:00
|
|
|
cmd.Env = r.conmonEnv
|
2020-06-16 18:22:05 +00:00
|
|
|
// we don't want to step on users fds they asked to preserve
|
|
|
|
// Since 0-2 are used for stdio, start the fds we pass in at preserveFDs+3
|
|
|
|
cmd.Env = append(cmd.Env, fmt.Sprintf("_OCI_SYNCPIPE=%d", ctr.config.PreserveFDs+3), fmt.Sprintf("_OCI_STARTPIPE=%d", ctr.config.PreserveFDs+4))
|
2019-10-08 17:53:36 +00:00
|
|
|
cmd.Env = append(cmd.Env, conmonEnv...)
|
|
|
|
cmd.ExtraFiles = append(cmd.ExtraFiles, childSyncPipe, childStartPipe)
|
|
|
|
cmd.ExtraFiles = append(cmd.ExtraFiles, envFiles...)
|
|
|
|
|
|
|
|
if r.reservePorts && !ctr.config.NetMode.IsSlirp4netns() {
|
|
|
|
ports, err := bindPorts(ctr.config.PortMappings)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Leak the port we bound in the conmon process. These fd's won't be used
|
|
|
|
// by the container and conmon will keep the ports busy so that another
|
|
|
|
// process cannot use them.
|
|
|
|
cmd.ExtraFiles = append(cmd.ExtraFiles, ports...)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ctr.config.NetMode.IsSlirp4netns() {
|
|
|
|
if ctr.config.PostConfigureNetNS {
|
2020-02-18 10:46:27 +00:00
|
|
|
havePortMapping := len(ctr.Config().PortMappings) > 0
|
|
|
|
if havePortMapping {
|
|
|
|
ctr.rootlessPortSyncR, ctr.rootlessPortSyncW, err = os.Pipe()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "failed to create rootless port sync pipe")
|
|
|
|
}
|
|
|
|
}
|
2019-10-08 17:53:36 +00:00
|
|
|
ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "failed to create rootless network sync pipe")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if ctr.rootlessSlirpSyncR != nil {
|
|
|
|
defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncR)
|
|
|
|
}
|
|
|
|
if ctr.rootlessSlirpSyncW != nil {
|
|
|
|
defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncW)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Leak one end in conmon, the other one will be leaked into slirp4netns
|
|
|
|
cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncW)
|
2019-11-28 14:33:42 +00:00
|
|
|
|
|
|
|
if ctr.rootlessPortSyncW != nil {
|
|
|
|
defer errorhandling.CloseQuiet(ctr.rootlessPortSyncW)
|
|
|
|
// Leak one end in conmon, the other one will be leaked into rootlessport
|
|
|
|
cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessPortSyncW)
|
|
|
|
}
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
err = startCommandGivenSelinux(cmd)
|
|
|
|
// regardless of whether we errored or not, we no longer need the children pipes
|
|
|
|
childSyncPipe.Close()
|
|
|
|
childStartPipe.Close()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-10-21 21:04:27 +00:00
|
|
|
if err := r.moveConmonToCgroupAndSignal(ctr, cmd, parentStartPipe); err != nil {
|
2019-10-08 17:53:36 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
/* Wait for initial setup and fork, and reap child */
|
|
|
|
err = cmd.Wait()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-03-09 13:49:27 +00:00
|
|
|
pid, err := readConmonPipeData(parentSyncPipe, ociLog)
|
|
|
|
if err != nil {
|
2019-10-08 17:53:36 +00:00
|
|
|
if err2 := r.DeleteContainer(ctr); err2 != nil {
|
|
|
|
logrus.Errorf("Error removing container %s from runtime after creation failed", ctr.ID())
|
|
|
|
}
|
2020-03-09 13:49:27 +00:00
|
|
|
return err
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
2020-03-09 13:49:27 +00:00
|
|
|
ctr.state.PID = pid
|
2019-10-08 17:53:36 +00:00
|
|
|
|
|
|
|
conmonPID, err := readConmonPidFile(ctr.config.ConmonPidFile)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warnf("error reading conmon pid file for container %s: %s", ctr.ID(), err.Error())
|
|
|
|
} else if conmonPID > 0 {
|
|
|
|
// conmon not having a pid file is a valid state, so don't set it if we don't have it
|
|
|
|
logrus.Infof("Got Conmon PID as %d", conmonPID)
|
|
|
|
ctr.state.ConmonPID = conmonPID
|
2020-06-19 13:29:34 +00:00
|
|
|
if ctr.config.SdNotifyMode != define.SdNotifyModeIgnore {
|
|
|
|
if sent, err := daemon.SdNotify(false, fmt.Sprintf("MAINPID=%d", conmonPID)); err != nil {
|
|
|
|
logrus.Errorf("Error notifying systemd of Conmon PID: %s", err.Error())
|
|
|
|
} else if sent {
|
|
|
|
logrus.Debugf("Notify MAINPID sent successfully")
|
|
|
|
}
|
|
|
|
}
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
|
|
|
|
2020-07-29 14:27:12 +00:00
|
|
|
// These fds were passed down to the runtime. Close them
|
|
|
|
// and not interfere
|
|
|
|
for _, f := range filesToClose {
|
|
|
|
errorhandling.CloseQuiet(f)
|
2020-06-16 18:22:05 +00:00
|
|
|
}
|
|
|
|
|
2019-10-08 17:53:36 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// prepareProcessExec returns the path of the process.json used in runc exec -p
|
|
|
|
// caller is responsible to close the returned *os.File if needed.
|
|
|
|
func prepareProcessExec(c *Container, cmd, env []string, tty bool, cwd, user, sessionID string) (*os.File, error) {
|
|
|
|
f, err := ioutil.TempFile(c.execBundlePath(sessionID), "exec-process-")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
pspec := c.config.Spec.Process
|
2019-10-29 20:04:21 +00:00
|
|
|
pspec.SelinuxLabel = c.config.ProcessLabel
|
2019-10-08 17:53:36 +00:00
|
|
|
pspec.Args = cmd
|
|
|
|
// We need to default this to false else it will inherit terminal as true
|
|
|
|
// from the container.
|
|
|
|
pspec.Terminal = false
|
|
|
|
if tty {
|
|
|
|
pspec.Terminal = true
|
|
|
|
}
|
|
|
|
if len(env) > 0 {
|
|
|
|
pspec.Env = append(pspec.Env, env...)
|
|
|
|
}
|
|
|
|
|
|
|
|
if cwd != "" {
|
|
|
|
pspec.Cwd = cwd
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-02-27 21:08:29 +00:00
|
|
|
var addGroups []string
|
|
|
|
var sgids []uint32
|
|
|
|
|
|
|
|
// if the user is empty, we should inherit the user that the container is currently running with
|
|
|
|
if user == "" {
|
|
|
|
user = c.config.User
|
|
|
|
addGroups = c.config.Groups
|
|
|
|
}
|
|
|
|
|
2019-10-08 17:53:36 +00:00
|
|
|
overrides := c.getUserOverrides()
|
|
|
|
execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, overrides)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-02-27 21:08:29 +00:00
|
|
|
if len(addGroups) > 0 {
|
|
|
|
sgids, err = lookup.GetContainerGroups(addGroups, c.state.Mountpoint, overrides)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "error looking up supplemental groups for container %s exec session %s", c.ID(), sessionID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-08 17:53:36 +00:00
|
|
|
// If user was set, look it up in the container to get a UID to use on
|
|
|
|
// the host
|
2020-02-27 21:08:29 +00:00
|
|
|
if user != "" || len(sgids) > 0 {
|
|
|
|
if user != "" {
|
|
|
|
for _, sgid := range execUser.Sgids {
|
|
|
|
sgids = append(sgids, uint32(sgid))
|
|
|
|
}
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
|
|
|
processUser := spec.User{
|
|
|
|
UID: uint32(execUser.Uid),
|
|
|
|
GID: uint32(execUser.Gid),
|
|
|
|
AdditionalGids: sgids,
|
|
|
|
}
|
|
|
|
|
|
|
|
pspec.User = processUser
|
|
|
|
}
|
|
|
|
|
|
|
|
hasHomeSet := false
|
|
|
|
for _, s := range pspec.Env {
|
|
|
|
if strings.HasPrefix(s, "HOME=") {
|
|
|
|
hasHomeSet = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !hasHomeSet {
|
|
|
|
pspec.Env = append(pspec.Env, fmt.Sprintf("HOME=%s", execUser.Home))
|
|
|
|
}
|
|
|
|
|
|
|
|
processJSON, err := json.Marshal(pspec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := ioutil.WriteFile(f.Name(), processJSON, 0644); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return f, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// configureConmonEnv gets the environment values to add to conmon's exec struct
|
|
|
|
// TODO this may want to be less hardcoded/more configurable in the future
|
2020-06-19 13:29:34 +00:00
|
|
|
func (r *ConmonOCIRuntime) configureConmonEnv(ctr *Container, runtimeDir string) ([]string, []*os.File, error) {
|
2019-10-08 17:53:36 +00:00
|
|
|
env := make([]string, 0, 6)
|
|
|
|
env = append(env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir))
|
|
|
|
env = append(env, fmt.Sprintf("_CONTAINERS_USERNS_CONFIGURED=%s", os.Getenv("_CONTAINERS_USERNS_CONFIGURED")))
|
|
|
|
env = append(env, fmt.Sprintf("_CONTAINERS_ROOTLESS_UID=%s", os.Getenv("_CONTAINERS_ROOTLESS_UID")))
|
2019-10-21 17:48:23 +00:00
|
|
|
home, err := util.HomeDir()
|
2019-10-08 17:53:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
env = append(env, fmt.Sprintf("HOME=%s", home))
|
|
|
|
|
|
|
|
extraFiles := make([]*os.File, 0)
|
2020-06-19 13:29:34 +00:00
|
|
|
if ctr.config.SdNotifyMode == define.SdNotifyModeContainer {
|
|
|
|
if notify, ok := os.LookupEnv("NOTIFY_SOCKET"); ok {
|
|
|
|
env = append(env, fmt.Sprintf("NOTIFY_SOCKET=%s", notify))
|
|
|
|
}
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
|
|
|
if !r.sdNotify {
|
|
|
|
if listenfds, ok := os.LookupEnv("LISTEN_FDS"); ok {
|
|
|
|
env = append(env, fmt.Sprintf("LISTEN_FDS=%s", listenfds), "LISTEN_PID=1")
|
|
|
|
fds := activation.Files(false)
|
|
|
|
extraFiles = append(extraFiles, fds...)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
logrus.Debug("disabling SD notify")
|
|
|
|
}
|
|
|
|
return env, extraFiles, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// sharedConmonArgs takes common arguments for exec and create/restore and formats them for the conmon CLI
|
2020-06-10 18:35:00 +00:00
|
|
|
func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, pidPath, logPath, exitDir, ociLogPath, logDriver, logTag string) []string {
|
2019-10-08 17:53:36 +00:00
|
|
|
// set the conmon API version to be able to use the correct sync struct keys
|
2020-05-20 13:48:16 +00:00
|
|
|
args := []string{
|
|
|
|
"--api-version", "1",
|
|
|
|
"-c", ctr.ID(),
|
|
|
|
"-u", cuuid,
|
|
|
|
"-r", r.path,
|
|
|
|
"-b", bundlePath,
|
|
|
|
"-p", pidPath,
|
|
|
|
"-n", ctr.Name(),
|
|
|
|
"--exit-dir", exitDir,
|
|
|
|
"--socket-dir-path", r.socketsDir,
|
|
|
|
}
|
|
|
|
|
2020-06-18 11:56:30 +00:00
|
|
|
if r.cgroupManager == config.SystemdCgroupsManager && !ctr.config.NoCgroups && ctr.config.CgroupsMode != cgroupSplit {
|
2019-10-08 17:53:36 +00:00
|
|
|
args = append(args, "-s")
|
|
|
|
}
|
|
|
|
|
2020-06-10 18:35:00 +00:00
|
|
|
var logDriverArg string
|
|
|
|
switch logDriver {
|
2020-04-16 13:39:34 +00:00
|
|
|
case define.JournaldLogging:
|
2020-06-10 18:35:00 +00:00
|
|
|
logDriverArg = define.JournaldLogging
|
2020-04-16 13:39:34 +00:00
|
|
|
case define.JSONLogging:
|
2019-10-08 17:53:36 +00:00
|
|
|
fallthrough
|
2020-06-10 18:35:00 +00:00
|
|
|
case define.NoLogging:
|
|
|
|
logDriverArg = define.NoLogging
|
2019-10-08 17:53:36 +00:00
|
|
|
default: //nolint-stylecheck
|
|
|
|
// No case here should happen except JSONLogging, but keep this here in case the options are extended
|
|
|
|
logrus.Errorf("%s logging specified but not supported. Choosing k8s-file logging instead", ctr.LogDriver())
|
|
|
|
fallthrough
|
|
|
|
case "":
|
|
|
|
// to get here, either a user would specify `--log-driver ""`, or this came from another place in libpod
|
|
|
|
// since the former case is obscure, and the latter case isn't an error, let's silently fallthrough
|
|
|
|
fallthrough
|
2020-04-16 13:39:34 +00:00
|
|
|
case define.KubernetesLogging:
|
2020-06-10 18:35:00 +00:00
|
|
|
logDriverArg = fmt.Sprintf("%s:%s", define.KubernetesLogging, logPath)
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
|
|
|
|
2020-06-10 18:35:00 +00:00
|
|
|
args = append(args, "-l", logDriverArg)
|
2019-10-08 17:53:36 +00:00
|
|
|
if r.logSizeMax >= 0 {
|
|
|
|
args = append(args, "--log-size-max", fmt.Sprintf("%v", r.logSizeMax))
|
|
|
|
}
|
|
|
|
|
|
|
|
logLevel := logrus.GetLevel()
|
|
|
|
args = append(args, "--log-level", logLevel.String())
|
|
|
|
|
|
|
|
if logLevel == logrus.DebugLevel {
|
|
|
|
logrus.Debugf("%s messages will be logged to syslog", r.conmonPath)
|
|
|
|
args = append(args, "--syslog")
|
|
|
|
}
|
|
|
|
if ociLogPath != "" {
|
|
|
|
args = append(args, "--runtime-arg", "--log-format=json", "--runtime-arg", "--log", fmt.Sprintf("--runtime-arg=%s", ociLogPath))
|
|
|
|
}
|
2020-01-07 12:41:56 +00:00
|
|
|
if logTag != "" {
|
|
|
|
args = append(args, "--log-tag", logTag)
|
|
|
|
}
|
2019-10-08 17:53:36 +00:00
|
|
|
if ctr.config.NoCgroups {
|
|
|
|
logrus.Debugf("Running with no CGroups")
|
|
|
|
args = append(args, "--runtime-arg", "--cgroup-manager", "--runtime-arg", "disabled")
|
|
|
|
}
|
|
|
|
return args
|
|
|
|
}
|
|
|
|
|
|
|
|
// startCommandGivenSelinux starts a container ensuring to set the labels of
|
|
|
|
// the process to make sure SELinux doesn't block conmon communication, if SELinux is enabled
|
|
|
|
func startCommandGivenSelinux(cmd *exec.Cmd) error {
|
|
|
|
if !selinux.GetEnabled() {
|
|
|
|
return cmd.Start()
|
|
|
|
}
|
|
|
|
// Set the label of the conmon process to be level :s0
|
|
|
|
// This will allow the container processes to talk to fifo-files
|
|
|
|
// passed into the container by conmon
|
|
|
|
var (
|
|
|
|
plabel string
|
|
|
|
con selinux.Context
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
plabel, err = selinux.CurrentLabel()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "Failed to get current SELinux label")
|
|
|
|
}
|
|
|
|
|
|
|
|
con, err = selinux.NewContext(plabel)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "Failed to get new context from SELinux label")
|
|
|
|
}
|
|
|
|
|
|
|
|
runtime.LockOSThread()
|
|
|
|
if con["level"] != "s0" && con["level"] != "" {
|
|
|
|
con["level"] = "s0"
|
|
|
|
if err = label.SetProcessLabel(con.Get()); err != nil {
|
|
|
|
runtime.UnlockOSThread()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
err = cmd.Start()
|
|
|
|
// Ignore error returned from SetProcessLabel("") call,
|
|
|
|
// can't recover.
|
|
|
|
if labelErr := label.SetProcessLabel(""); labelErr != nil {
|
|
|
|
logrus.Errorf("unable to set process label: %q", err)
|
|
|
|
}
|
|
|
|
runtime.UnlockOSThread()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// moveConmonToCgroupAndSignal gets a container's cgroupParent and moves the conmon process to that cgroup
|
|
|
|
// it then signals for conmon to start by sending nonse data down the start fd
|
2019-10-21 21:04:27 +00:00
|
|
|
func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec.Cmd, startFd *os.File) error {
|
2019-10-08 17:53:36 +00:00
|
|
|
mustCreateCgroup := true
|
2020-01-14 14:05:12 +00:00
|
|
|
|
2019-10-08 17:53:36 +00:00
|
|
|
if ctr.config.NoCgroups {
|
|
|
|
mustCreateCgroup = false
|
|
|
|
}
|
|
|
|
|
2020-01-14 14:05:12 +00:00
|
|
|
// If cgroup creation is disabled - just signal.
|
|
|
|
switch ctr.config.CgroupsMode {
|
2020-06-18 11:56:30 +00:00
|
|
|
case "disabled", "no-conmon", cgroupSplit:
|
2020-01-14 14:05:12 +00:00
|
|
|
mustCreateCgroup = false
|
|
|
|
}
|
|
|
|
|
2020-01-14 14:34:02 +00:00
|
|
|
// $INVOCATION_ID is set by systemd when running as a service.
|
|
|
|
if os.Getenv("INVOCATION_ID") != "" {
|
|
|
|
mustCreateCgroup = false
|
|
|
|
}
|
|
|
|
|
2019-10-08 17:53:36 +00:00
|
|
|
if mustCreateCgroup {
|
|
|
|
cgroupParent := ctr.CgroupParent()
|
2020-03-27 14:13:51 +00:00
|
|
|
if r.cgroupManager == config.SystemdCgroupsManager {
|
2019-10-08 17:53:36 +00:00
|
|
|
unitName := createUnitName("libpod-conmon", ctr.ID())
|
|
|
|
|
|
|
|
realCgroupParent := cgroupParent
|
|
|
|
splitParent := strings.Split(cgroupParent, "/")
|
|
|
|
if strings.HasSuffix(cgroupParent, ".slice") && len(splitParent) > 1 {
|
|
|
|
realCgroupParent = splitParent[len(splitParent)-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Infof("Running conmon under slice %s and unitName %s", realCgroupParent, unitName)
|
|
|
|
if err := utils.RunUnderSystemdScope(cmd.Process.Pid, realCgroupParent, unitName); err != nil {
|
|
|
|
logrus.Warnf("Failed to add conmon to systemd sandbox cgroup: %v", err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
cgroupPath := filepath.Join(ctr.config.CgroupParent, "conmon")
|
|
|
|
control, err := cgroups.New(cgroupPath, &spec.LinuxResources{})
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err)
|
2020-01-13 12:01:45 +00:00
|
|
|
} else if err := control.AddPid(cmd.Process.Pid); err != nil {
|
2019-10-08 17:53:36 +00:00
|
|
|
// we need to remove this defer and delete the cgroup once conmon exits
|
|
|
|
// maybe need a conmon monitor?
|
2020-01-13 12:01:45 +00:00
|
|
|
logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err)
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We set the cgroup, now the child can start creating children */
|
|
|
|
if err := writeConmonPipeData(startFd); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-07-09 17:50:01 +00:00
|
|
|
// newPipe creates a unix socket pair for communication.
|
|
|
|
// Returns two files - first is parent, second is child.
|
|
|
|
func newPipe() (*os.File, *os.File, error) {
|
2019-10-08 17:53:36 +00:00
|
|
|
fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_SEQPACKET|unix.SOCK_CLOEXEC, 0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// readConmonPidFile attempts to read conmon's pid from its pid file
|
|
|
|
func readConmonPidFile(pidFile string) (int, error) {
|
|
|
|
// Let's try reading the Conmon pid at the same time.
|
|
|
|
if pidFile != "" {
|
|
|
|
contents, err := ioutil.ReadFile(pidFile)
|
|
|
|
if err != nil {
|
|
|
|
return -1, err
|
|
|
|
}
|
|
|
|
// Convert it to an int
|
|
|
|
conmonPID, err := strconv.Atoi(string(contents))
|
|
|
|
if err != nil {
|
|
|
|
return -1, err
|
|
|
|
}
|
|
|
|
return conmonPID, nil
|
|
|
|
}
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// readConmonPipeData attempts to read a syncInfo struct from the pipe
|
2020-03-09 13:49:27 +00:00
|
|
|
func readConmonPipeData(pipe *os.File, ociLog string) (int, error) {
|
2019-10-08 17:53:36 +00:00
|
|
|
// syncInfo is used to return data from monitor process to daemon
|
|
|
|
type syncInfo struct {
|
|
|
|
Data int `json:"data"`
|
|
|
|
Message string `json:"message,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait to get container pid from conmon
|
|
|
|
type syncStruct struct {
|
|
|
|
si *syncInfo
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
ch := make(chan syncStruct)
|
|
|
|
go func() {
|
|
|
|
var si *syncInfo
|
|
|
|
rdr := bufio.NewReader(pipe)
|
|
|
|
b, err := rdr.ReadBytes('\n')
|
|
|
|
if err != nil {
|
|
|
|
ch <- syncStruct{err: err}
|
|
|
|
}
|
|
|
|
if err := json.Unmarshal(b, &si); err != nil {
|
|
|
|
ch <- syncStruct{err: err}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ch <- syncStruct{si: si}
|
|
|
|
}()
|
|
|
|
|
2020-03-09 13:48:47 +00:00
|
|
|
data := -1
|
2019-10-08 17:53:36 +00:00
|
|
|
select {
|
|
|
|
case ss := <-ch:
|
|
|
|
if ss.err != nil {
|
|
|
|
if ociLog != "" {
|
|
|
|
ociLogData, err := ioutil.ReadFile(ociLog)
|
|
|
|
if err == nil {
|
|
|
|
var ociErr ociError
|
|
|
|
if err := json.Unmarshal(ociLogData, &ociErr); err == nil {
|
2020-03-09 13:49:27 +00:00
|
|
|
return -1, getOCIRuntimeError(ociErr.Msg)
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-03-09 13:49:27 +00:00
|
|
|
return -1, errors.Wrapf(ss.err, "container create failed (no logs from conmon)")
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
|
|
|
logrus.Debugf("Received: %d", ss.si.Data)
|
|
|
|
if ss.si.Data < 0 {
|
|
|
|
if ociLog != "" {
|
|
|
|
ociLogData, err := ioutil.ReadFile(ociLog)
|
|
|
|
if err == nil {
|
|
|
|
var ociErr ociError
|
|
|
|
if err := json.Unmarshal(ociLogData, &ociErr); err == nil {
|
2020-03-09 13:49:27 +00:00
|
|
|
return ss.si.Data, getOCIRuntimeError(ociErr.Msg)
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If we failed to parse the JSON errors, then print the output as it is
|
|
|
|
if ss.si.Message != "" {
|
2020-03-09 13:49:27 +00:00
|
|
|
return ss.si.Data, getOCIRuntimeError(ss.si.Message)
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
2020-03-09 13:49:27 +00:00
|
|
|
return ss.si.Data, errors.Wrapf(define.ErrInternal, "container create failed")
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
|
|
|
data = ss.si.Data
|
2019-10-21 17:48:23 +00:00
|
|
|
case <-time.After(define.ContainerCreateTimeout):
|
2020-03-09 13:49:27 +00:00
|
|
|
return -1, errors.Wrapf(define.ErrInternal, "container creation timeout")
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
2020-03-09 13:49:27 +00:00
|
|
|
return data, nil
|
2019-10-08 17:53:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// writeConmonPipeData writes nonse data to a pipe
|
|
|
|
func writeConmonPipeData(pipe *os.File) error {
|
|
|
|
someData := []byte{0}
|
|
|
|
_, err := pipe.Write(someData)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// formatRuntimeOpts prepends opts passed to it with --runtime-opt for passing to conmon
|
|
|
|
func formatRuntimeOpts(opts ...string) []string {
|
|
|
|
args := make([]string, 0, len(opts)*2)
|
|
|
|
for _, o := range opts {
|
|
|
|
args = append(args, "--runtime-opt", o)
|
|
|
|
}
|
|
|
|
return args
|
|
|
|
}
|
|
|
|
|
|
|
|
// getConmonVersion returns a string representation of the conmon version.
|
|
|
|
func (r *ConmonOCIRuntime) getConmonVersion() (string, error) {
|
|
|
|
output, err := utils.ExecCmd(r.conmonPath, "--version")
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return strings.TrimSuffix(strings.Replace(output, "\n", ", ", 1), "\n"), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getOCIRuntimeVersion returns a string representation of the OCI runtime's
|
|
|
|
// version.
|
|
|
|
func (r *ConmonOCIRuntime) getOCIRuntimeVersion() (string, error) {
|
|
|
|
output, err := utils.ExecCmd(r.path, "--version")
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return strings.TrimSuffix(output, "\n"), nil
|
|
|
|
}
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
|
|
|
|
// Copy data from container to HTTP connection, for terminal attach.
|
|
|
|
// Container is the container's attach socket connection, http is a buffer for
|
|
|
|
// the HTTP connection. cid is the ID of the container the attach session is
|
|
|
|
// running for (used solely for error messages).
|
|
|
|
func httpAttachTerminalCopy(container *net.UnixConn, http *bufio.ReadWriter, cid string) error {
|
|
|
|
buf := make([]byte, bufferSize)
|
|
|
|
for {
|
|
|
|
numR, err := container.Read(buf)
|
2020-05-08 15:33:44 +00:00
|
|
|
logrus.Debugf("Read fd(%d) %d/%d bytes for container %s", int(buf[0]), numR, len(buf), cid)
|
|
|
|
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
if numR > 0 {
|
|
|
|
switch buf[0] {
|
|
|
|
case AttachPipeStdout:
|
|
|
|
// Do nothing
|
|
|
|
default:
|
|
|
|
logrus.Errorf("Received unexpected attach type %+d, discarding %d bytes", buf[0], numR)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
numW, err2 := http.Write(buf[1:numR])
|
|
|
|
if err2 != nil {
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error reading container %s STDOUT: %v", cid, err)
|
|
|
|
}
|
|
|
|
return err2
|
|
|
|
} else if numW+1 != numR {
|
|
|
|
return io.ErrShortWrite
|
|
|
|
}
|
|
|
|
// We need to force the buffer to write immediately, so
|
|
|
|
// there isn't a delay on the terminal side.
|
|
|
|
if err2 := http.Flush(); err2 != nil {
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error reading container %s STDOUT: %v", cid, err)
|
|
|
|
}
|
|
|
|
return err2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy data from a container to an HTTP connection, for non-terminal attach.
|
|
|
|
// Appends a header to multiplex input.
|
|
|
|
func httpAttachNonTerminalCopy(container *net.UnixConn, http *bufio.ReadWriter, cid string, stdin, stdout, stderr bool) error {
|
|
|
|
buf := make([]byte, bufferSize)
|
|
|
|
for {
|
|
|
|
numR, err := container.Read(buf)
|
|
|
|
if numR > 0 {
|
2020-04-07 20:52:47 +00:00
|
|
|
var headerBuf []byte
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
|
2020-04-07 20:52:47 +00:00
|
|
|
// Subtract 1 because we strip the first byte (used for
|
|
|
|
// multiplexing by Conmon).
|
|
|
|
headerLen := uint32(numR - 1)
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
// Practically speaking, we could make this buf[0] - 1,
|
|
|
|
// but we need to validate it anyways...
|
|
|
|
switch buf[0] {
|
|
|
|
case AttachPipeStdin:
|
2020-04-07 20:52:47 +00:00
|
|
|
headerBuf = makeHTTPAttachHeader(0, headerLen)
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
if !stdin {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
case AttachPipeStdout:
|
|
|
|
if !stdout {
|
|
|
|
continue
|
|
|
|
}
|
2020-04-07 20:52:47 +00:00
|
|
|
headerBuf = makeHTTPAttachHeader(1, headerLen)
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
case AttachPipeStderr:
|
|
|
|
if !stderr {
|
|
|
|
continue
|
|
|
|
}
|
2020-04-07 20:52:47 +00:00
|
|
|
headerBuf = makeHTTPAttachHeader(2, headerLen)
|
Add an API for Attach over HTTP API
The new APIv2 branch provides an HTTP-based remote API to Podman.
The requirements of this are, unfortunately, incompatible with
the existing Attach API. For non-terminal attach, we need append
a header to what was copied from the container, to multiplex
STDOUT and STDERR; to do this with the old API, we'd need to copy
into an intermediate buffer first, to handle the headers.
To avoid this, provide a new API to handle all aspects of
terminal and non-terminal attach, including closing the hijacked
HTTP connection. This might be a bit too specific, but for now,
it seems to be the simplest approach.
At the same time, add a Resize endpoint. This needs to be a
separate endpoint, so our existing channel approach does not work
here.
I wanted to rework the rest of attach at the same time (some
parts of it, particularly how we start the Attach session and how
we do resizing, are (in my opinion) handled much better here.
That may still be on the table, but I wanted to avoid breaking
existing APIs in this already massive change.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-01-10 18:37:10 +00:00
|
|
|
default:
|
|
|
|
logrus.Errorf("Received unexpected attach type %+d, discarding %d bytes", buf[0], numR)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
numH, err2 := http.Write(headerBuf)
|
|
|
|
if err2 != nil {
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error reading container %s standard streams: %v", cid, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return err2
|
|
|
|
}
|
|
|
|
// Hardcoding header length is pretty gross, but
|
|
|
|
// fast. Should be safe, as this is a fixed part
|
|
|
|
// of the protocol.
|
|
|
|
if numH != 8 {
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error reading container %s standard streams: %v", cid, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return io.ErrShortWrite
|
|
|
|
}
|
|
|
|
|
|
|
|
numW, err2 := http.Write(buf[1:numR])
|
|
|
|
if err2 != nil {
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error reading container %s standard streams: %v", cid, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return err2
|
|
|
|
} else if numW+1 != numR {
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error reading container %s standard streams: %v", cid, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return io.ErrShortWrite
|
|
|
|
}
|
|
|
|
// We need to force the buffer to write immediately, so
|
|
|
|
// there isn't a delay on the terminal side.
|
|
|
|
if err2 := http.Flush(); err2 != nil {
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error reading container %s STDOUT: %v", cid, err)
|
|
|
|
}
|
|
|
|
return err2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|