From b464f1e3151997fc12a36a9f38697016a96e5775 Mon Sep 17 00:00:00 2001 From: Cory Bennett Date: Mon, 19 Oct 2020 23:47:00 +0000 Subject: [PATCH] update go-runc to use runc.ExitError for container exec status Signed-off-by: Cory Bennett --- client/build_test.go | 34 +++++------ executor/runcexecutor/executor.go | 40 +++++++------ executor/runcexecutor/executor_common.go | 26 +++----- executor/runcexecutor/executor_linux.go | 38 ++++++------ go.mod | 2 +- go.sum | 2 + .../github.com/containerd/go-runc/.travis.yml | 3 +- vendor/github.com/containerd/go-runc/go.mod | 1 + vendor/github.com/containerd/go-runc/go.sum | 7 +++ .../github.com/containerd/go-runc/io_unix.go | 26 +++++++- vendor/github.com/containerd/go-runc/runc.go | 59 +++++++++++-------- .../containerd/go-runc/runc_unix.go | 38 ++++++++++++ .../containerd/go-runc/runc_windows.go | 31 ++++++++++ vendor/modules.txt | 2 +- 14 files changed, 205 insertions(+), 104 deletions(-) create mode 100644 vendor/github.com/containerd/go-runc/runc_unix.go create mode 100644 vendor/github.com/containerd/go-runc/runc_windows.go diff --git a/client/build_test.go b/client/build_test.go index 93edb22f..649e95b8 100644 --- a/client/build_test.go +++ b/client/build_test.go @@ -271,9 +271,8 @@ func testClientGatewayContainerCancelOnRelease(t *testing.T, sb integration.Sand // We are mimicing: `echo testing | cat | cat > /tmp/foo && cat /tmp/foo` func testClientGatewayContainerExecPipe(t *testing.T, sb integration.Sandbox) { if sb.Rootless() { - // TODO fix this - // We get `panic: cannot statfs cgroup root` from runc when when running - // this test with runc-rootless, no idea why. + // TODO remove when https://github.com/opencontainers/runc/pull/2634 + // is merged and released t.Skip("Skipping oci-rootless for cgroup error") } requiresLinux(t) @@ -467,9 +466,8 @@ func testClientGatewayContainerPID1Fail(t *testing.T, sb integration.Sandbox) { // via `Exec` are shutdown when the primary pid1 process exits func testClientGatewayContainerPID1Exit(t *testing.T, sb integration.Sandbox) { if sb.Rootless() { - // TODO fix this - // We get `panic: cannot statfs cgroup root` when running this test - // with runc-rootless + // TODO remove when https://github.com/opencontainers/runc/pull/2634 + // is merged and released t.Skip("Skipping runc-rootless for cgroup error") } requiresLinux(t) @@ -535,10 +533,9 @@ func testClientGatewayContainerPID1Exit(t *testing.T, sb integration.Sandbox) { } _, err = c.Build(ctx, SolveOpt{}, product, b, nil) - // pid2 should error with `buildkit-runc did not terminate successfully` on runc or - // `exit code: 137` (ie sigkill) on containerd require.Error(t, err) - require.Regexp(t, "exit code: 137|buildkit-runc did not terminate successfully", err.Error()) + // `exit code: 137` (ie sigkill) + require.Regexp(t, "exit code: 137", err.Error()) checkAllReleasable(t, c, sb, true) } @@ -547,9 +544,8 @@ func testClientGatewayContainerPID1Exit(t *testing.T, sb integration.Sandbox) { // llb.States func testClientGatewayContainerMounts(t *testing.T, sb integration.Sandbox) { if sb.Rootless() { - // TODO fix this - // We get `panic: cannot statfs cgroup root` when running this test - // with runc-rootless + // TODO remove when https://github.com/opencontainers/runc/pull/2634 + // is merged and released t.Skip("Skipping runc-rootless for cgroup error") } requiresLinux(t) @@ -864,9 +860,8 @@ func (p *testPrompt) wait(msg string) string { // executor.Exec (secondary process) func testClientGatewayContainerExecTty(t *testing.T, sb integration.Sandbox) { if sb.Rootless() { - // TODO fix this - // We get `panic: cannot statfs cgroup root` when running this test - // with runc-rootless + // TODO remove when https://github.com/opencontainers/runc/pull/2634 + // is merged and released t.Skip("Skipping runc-rootless for cgroup error") } requiresLinux(t) @@ -937,12 +932,17 @@ func testClientGatewayContainerExecTty(t *testing.T, sb integration.Sandbox) { prompt.SendExpect("ttysize", "100 60") prompt.SendExit(99) - return &client.Result{}, pid2.Wait() + err = pid2.Wait() + var exitError *errdefs.ExitError + require.True(t, errors.As(err, &exitError)) + require.Equal(t, uint32(99), exitError.ExitCode) + + return &client.Result{}, err } _, err = c.Build(ctx, SolveOpt{}, product, b, nil) require.Error(t, err) - require.Regexp(t, "exit code: 99|runc did not terminate successfully", err.Error()) + require.Regexp(t, "exit code: 99", err.Error()) inputW.Close() inputR.Close() diff --git a/executor/runcexecutor/executor.go b/executor/runcexecutor/executor.go index 60c07f54..62f4ba41 100644 --- a/executor/runcexecutor/executor.go +++ b/executor/runcexecutor/executor.go @@ -12,6 +12,7 @@ import ( "syscall" "time" + "github.com/containerd/containerd" "github.com/containerd/containerd/mount" containerdoci "github.com/containerd/containerd/oci" "github.com/containerd/continuity/fs" @@ -103,15 +104,16 @@ func New(opt Opt, networkProviders map[pb.NetMode]network.Provider) (executor.Ex os.RemoveAll(filepath.Join(root, "resolv.conf")) runtime := &runc.Runc{ - Command: cmd, - Log: filepath.Join(root, "runc-log.json"), - LogFormat: runc.JSON, - PdeathSignal: syscall.SIGKILL, // this can still leak the process - Setpgid: true, + Command: cmd, + Log: filepath.Join(root, "runc-log.json"), + LogFormat: runc.JSON, + Setpgid: true, // we don't execute runc with --rootless=(true|false) explicitly, // so as to support non-runc runtimes } + updateRuncFieldsForHostOS(runtime) + w := &runcExecutor{ runc: runtime, root: root, @@ -324,21 +326,29 @@ func (w *runcExecutor) Run(ctx context.Context, id string, root cache.Mountable, }) } - status, err := w.run(runCtx, id, bundle, process) + err = w.run(runCtx, id, bundle, process) close(ended) + return exitError(ctx, err) +} - if status != 0 || err != nil { +func exitError(ctx context.Context, err error) error { + if err != nil { exitErr := &errdefs.ExitError{ - ExitCode: uint32(status), + ExitCode: containerd.UnknownExitStatus, Err: err, } - err = exitErr + var runcExitError *runc.ExitError + if errors.As(err, &runcExitError) { + exitErr = &errdefs.ExitError{ + ExitCode: uint32(runcExitError.Status), + } + } select { case <-ctx.Done(): exitErr.Err = errors.Wrapf(ctx.Err(), exitErr.Error()) return exitErr default: - return stack.Enable(err) + return stack.Enable(exitErr) } } @@ -408,14 +418,8 @@ func (w *runcExecutor) Exec(ctx context.Context, id string, process executor.Pro spec.Process.Env = process.Meta.Env } - status, err := w.exec(ctx, id, state.Bundle, spec.Process, process) - if status == 0 && err == nil { - return nil - } - return &errdefs.ExitError{ - ExitCode: uint32(status), - Err: err, - } + err = w.exec(ctx, id, state.Bundle, spec.Process, process) + return exitError(ctx, err) } type forwardIO struct { diff --git a/executor/runcexecutor/executor_common.go b/executor/runcexecutor/executor_common.go index 729db008..3751b900 100644 --- a/executor/runcexecutor/executor_common.go +++ b/executor/runcexecutor/executor_common.go @@ -4,9 +4,7 @@ package runcexecutor import ( "context" - "os/exec" - "github.com/containerd/containerd" runc "github.com/containerd/go-runc" "github.com/moby/buildkit/executor" "github.com/opencontainers/runtime-spec/specs-go" @@ -15,30 +13,24 @@ import ( var unsupportedConsoleError = errors.New("tty for runc is only supported on linux") -func (w *runcExecutor) run(ctx context.Context, id, bundle string, process executor.ProcessInfo) (int, error) { +func updateRuncFieldsForHostOS(runtime *runc.Runc) {} + +func (w *runcExecutor) run(ctx context.Context, id, bundle string, process executor.ProcessInfo) error { if process.Meta.Tty { - return 0, unsupportedConsoleError + return unsupportedConsoleError } - return w.runc.Run(ctx, id, bundle, &runc.CreateOpts{ + _, err := w.runc.Run(ctx, id, bundle, &runc.CreateOpts{ IO: &forwardIO{stdin: process.Stdin, stdout: process.Stdout, stderr: process.Stderr}, NoPivot: w.noPivot, }) + return err } -func (w *runcExecutor) exec(ctx context.Context, id, bundle string, specsProcess *specs.Process, process executor.ProcessInfo) (int, error) { +func (w *runcExecutor) exec(ctx context.Context, id, bundle string, specsProcess *specs.Process, process executor.ProcessInfo) error { if process.Meta.Tty { - return 0, unsupportedConsoleError + return unsupportedConsoleError } - err := w.runc.Exec(ctx, id, *specsProcess, &runc.ExecOpts{ + return w.runc.Exec(ctx, id, *specsProcess, &runc.ExecOpts{ IO: &forwardIO{stdin: process.Stdin, stdout: process.Stdout, stderr: process.Stderr}, }) - - var exitError *exec.ExitError - if errors.As(err, &exitError) { - return exitError.ExitCode(), err - } - if err != nil { - return containerd.UnknownExitStatus, err - } - return 0, nil } diff --git a/executor/runcexecutor/executor_linux.go b/executor/runcexecutor/executor_linux.go index 0daa8d23..91d6cd78 100644 --- a/executor/runcexecutor/executor_linux.go +++ b/executor/runcexecutor/executor_linux.go @@ -7,14 +7,12 @@ import ( "io" "io/ioutil" "os" - "os/exec" "strconv" "strings" "syscall" "time" "github.com/containerd/console" - "github.com/containerd/containerd" runc "github.com/containerd/go-runc" "github.com/docker/docker/pkg/signal" "github.com/moby/buildkit/executor" @@ -24,42 +22,40 @@ import ( "golang.org/x/sync/errgroup" ) -func (w *runcExecutor) run(ctx context.Context, id, bundle string, process executor.ProcessInfo) (int, error) { - return w.callWithIO(ctx, id, bundle, process, func(ctx context.Context, pidfile string, io runc.IO) (int, error) { - return w.runc.Run(ctx, id, bundle, &runc.CreateOpts{ +func updateRuncFieldsForHostOS(runtime *runc.Runc) { + // PdeathSignal only supported on unix platforms + runtime.PdeathSignal = syscall.SIGKILL // this can still leak the process +} + +func (w *runcExecutor) run(ctx context.Context, id, bundle string, process executor.ProcessInfo) error { + return w.callWithIO(ctx, id, bundle, process, func(ctx context.Context, pidfile string, io runc.IO) error { + _, err := w.runc.Run(ctx, id, bundle, &runc.CreateOpts{ NoPivot: w.noPivot, PidFile: pidfile, IO: io, }) + return err }) } -func (w *runcExecutor) exec(ctx context.Context, id, bundle string, specsProcess *specs.Process, process executor.ProcessInfo) (int, error) { - return w.callWithIO(ctx, id, bundle, process, func(ctx context.Context, pidfile string, io runc.IO) (int, error) { - err := w.runc.Exec(ctx, id, *specsProcess, &runc.ExecOpts{ +func (w *runcExecutor) exec(ctx context.Context, id, bundle string, specsProcess *specs.Process, process executor.ProcessInfo) error { + return w.callWithIO(ctx, id, bundle, process, func(ctx context.Context, pidfile string, io runc.IO) error { + return w.runc.Exec(ctx, id, *specsProcess, &runc.ExecOpts{ PidFile: pidfile, IO: io, }) - var exitError *exec.ExitError - if errors.As(err, &exitError) { - return exitError.ExitCode(), err - } - if err != nil { - return containerd.UnknownExitStatus, err - } - return 0, nil }) } -type runcCall func(ctx context.Context, pidfile string, io runc.IO) (int, error) +type runcCall func(ctx context.Context, pidfile string, io runc.IO) error -func (w *runcExecutor) callWithIO(ctx context.Context, id, bundle string, process executor.ProcessInfo, call runcCall) (int, error) { +func (w *runcExecutor) callWithIO(ctx context.Context, id, bundle string, process executor.ProcessInfo, call runcCall) error { ctx, cancel := context.WithCancel(ctx) defer cancel() pidfile, err := ioutil.TempFile(bundle, "*.pid") if err != nil { - return containerd.UnknownExitStatus, errors.Wrap(err, "failed to create pidfile") + return errors.Wrap(err, "failed to create pidfile") } defer os.Remove(pidfile.Name()) pidfile.Close() @@ -70,13 +66,13 @@ func (w *runcExecutor) callWithIO(ctx context.Context, id, bundle string, proces ptm, ptsName, err := console.NewPty() if err != nil { - return containerd.UnknownExitStatus, err + return err } pts, err := os.OpenFile(ptsName, os.O_RDWR|syscall.O_NOCTTY, 0) if err != nil { ptm.Close() - return containerd.UnknownExitStatus, err + return err } eg, ctx := errgroup.WithContext(ctx) diff --git a/go.mod b/go.mod index 0bba1576..ee7bfa15 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/containerd/containerd v1.4.1-0.20200903181227-d4e78200d6da github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe github.com/containerd/go-cni v1.0.1 - github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328 + github.com/containerd/go-runc v0.0.0-20201017060547-8c4be61c2e34 github.com/containerd/stargz-snapshotter v0.0.0-20200903042824-2ee75e91f8f9 github.com/containerd/typeurl v1.0.1 github.com/coreos/go-systemd/v22 v22.1.0 diff --git a/go.sum b/go.sum index f5b88b34..4b05c7c5 100644 --- a/go.sum +++ b/go.sum @@ -127,6 +127,8 @@ github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZH github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328 h1:PRTagVMbJcCezLcHXe8UJvR1oBzp2lG3CEumeFOLOds= github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= +github.com/containerd/go-runc v0.0.0-20201017060547-8c4be61c2e34 h1:jFRg/hwx0DPpcg23MNusAppCDJa5nndN3/RAxSblN58= +github.com/containerd/go-runc v0.0.0-20201017060547-8c4be61c2e34/go.mod h1:1CDPys/h0SMZoki7dv3bPQ1wJWnmaeZIO026WPts2xM= github.com/containerd/stargz-snapshotter v0.0.0-20200903042824-2ee75e91f8f9 h1:JEcj3rNCg0Ho5t9kiOa4LV/vaNXbRQ9l2PIRzz2LWCQ= github.com/containerd/stargz-snapshotter v0.0.0-20200903042824-2ee75e91f8f9/go.mod h1:f+gZLtYcuNQWxucWyfVEQXSBoGbXNpQ76+XWVMgp+34= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= diff --git a/vendor/github.com/containerd/go-runc/.travis.yml b/vendor/github.com/containerd/go-runc/.travis.yml index dd60e9ba..724ee09d 100644 --- a/vendor/github.com/containerd/go-runc/.travis.yml +++ b/vendor/github.com/containerd/go-runc/.travis.yml @@ -1,7 +1,8 @@ language: go go: - - 1.12.x - 1.13.x + - 1.14.x + - 1.15.x install: - go get -t ./... diff --git a/vendor/github.com/containerd/go-runc/go.mod b/vendor/github.com/containerd/go-runc/go.mod index d833ee16..6329c768 100644 --- a/vendor/github.com/containerd/go-runc/go.mod +++ b/vendor/github.com/containerd/go-runc/go.mod @@ -6,5 +6,6 @@ require ( github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e github.com/opencontainers/runtime-spec v1.0.1 github.com/pkg/errors v0.8.1 + github.com/sirupsen/logrus v1.6.0 golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 ) diff --git a/vendor/github.com/containerd/go-runc/go.sum b/vendor/github.com/containerd/go-runc/go.sum index f7d00e37..47ecf5ad 100644 --- a/vendor/github.com/containerd/go-runc/go.sum +++ b/vendor/github.com/containerd/go-runc/go.sum @@ -1,9 +1,16 @@ github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e h1:GdiIYd8ZDOrT++e1NjhSD4rGt9zaJukHm4rt5F4mRQc= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/opencontainers/runtime-spec v1.0.1 h1:wY4pOY8fBdSIvs9+IDHC55thBuEulhzfSgKeC1yFvzQ= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/containerd/go-runc/io_unix.go b/vendor/github.com/containerd/go-runc/io_unix.go index 567cd072..ccf1dd49 100644 --- a/vendor/github.com/containerd/go-runc/io_unix.go +++ b/vendor/github.com/containerd/go-runc/io_unix.go @@ -20,7 +20,9 @@ package runc import ( "github.com/pkg/errors" + "github.com/sirupsen/logrus" "golang.org/x/sys/unix" + "runtime" ) // NewPipeIO creates pipe pairs to be used with runc @@ -47,7 +49,13 @@ func NewPipeIO(uid, gid int, opts ...IOOpt) (i IO, err error) { } pipes = append(pipes, stdin) if err = unix.Fchown(int(stdin.r.Fd()), uid, gid); err != nil { - return nil, errors.Wrap(err, "failed to chown stdin") + // TODO: revert with proper darwin solution, skipping for now + // as darwin chown is returning EINVAL on anonymous pipe + if runtime.GOOS == "darwin" { + logrus.WithError(err).Debug("failed to chown stdin, ignored") + } else { + return nil, errors.Wrap(err, "failed to chown stdin") + } } } if option.OpenStdout { @@ -56,7 +64,13 @@ func NewPipeIO(uid, gid int, opts ...IOOpt) (i IO, err error) { } pipes = append(pipes, stdout) if err = unix.Fchown(int(stdout.w.Fd()), uid, gid); err != nil { - return nil, errors.Wrap(err, "failed to chown stdout") + // TODO: revert with proper darwin solution, skipping for now + // as darwin chown is returning EINVAL on anonymous pipe + if runtime.GOOS == "darwin" { + logrus.WithError(err).Debug("failed to chown stdout, ignored") + } else { + return nil, errors.Wrap(err, "failed to chown stdout") + } } } if option.OpenStderr { @@ -65,7 +79,13 @@ func NewPipeIO(uid, gid int, opts ...IOOpt) (i IO, err error) { } pipes = append(pipes, stderr) if err = unix.Fchown(int(stderr.w.Fd()), uid, gid); err != nil { - return nil, errors.Wrap(err, "failed to chown stderr") + // TODO: revert with proper darwin solution, skipping for now + // as darwin chown is returning EINVAL on anonymous pipe + if runtime.GOOS == "darwin" { + logrus.WithError(err).Debug("failed to chown stderr, ignored") + } else { + return nil, errors.Wrap(err, "failed to chown stderr") + } } } return &pipeIO{ diff --git a/vendor/github.com/containerd/go-runc/runc.go b/vendor/github.com/containerd/go-runc/runc.go index c3a95af2..2a515d2a 100644 --- a/vendor/github.com/containerd/go-runc/runc.go +++ b/vendor/github.com/containerd/go-runc/runc.go @@ -29,7 +29,6 @@ import ( "path/filepath" "strconv" "strings" - "syscall" "time" specs "github.com/opencontainers/runtime-spec/specs-go" @@ -55,21 +54,6 @@ const ( DefaultCommand = "runc" ) -// Runc is the client to the runc cli -type Runc struct { - //If command is empty, DefaultCommand is used - Command string - Root string - Debug bool - Log string - LogFormat Format - PdeathSignal syscall.Signal - Setpgid bool - Criu string - SystemdCgroup bool - Rootless *bool // nil stands for "auto" -} - // List returns all containers created inside the provided runc root directory func (r *Runc) List(context context.Context) ([]*Container, error) { data, err := cmdOutput(r.command(context, "list", "--format=json"), false) @@ -176,7 +160,7 @@ func (r *Runc) Create(context context.Context, id, bundle string, opts *CreateOp } status, err := Monitor.Wait(cmd, ec) if err == nil && status != 0 { - err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0]) + err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status}) } return err } @@ -210,7 +194,7 @@ func (o *ExecOpts) args() (out []string, err error) { return out, nil } -// Exec executres and additional process inside the container based on a full +// Exec executes an additional process inside the container based on a full // OCI Process specification func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts *ExecOpts) error { f, err := ioutil.TempFile(os.Getenv("XDG_RUNTIME_DIR"), "runc-process") @@ -239,7 +223,7 @@ func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts data, err := cmdOutput(cmd, true) defer putBuf(data) if err != nil { - return fmt.Errorf("%s: %s", err, data.String()) + return fmt.Errorf("%w: %s", err, data.String()) } return nil } @@ -256,7 +240,7 @@ func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts } status, err := Monitor.Wait(cmd, ec) if err == nil && status != 0 { - err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0]) + err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status}) } return err } @@ -282,7 +266,7 @@ func (r *Runc) Run(context context.Context, id, bundle string, opts *CreateOpts) } status, err := Monitor.Wait(cmd, ec) if err == nil && status != 0 { - err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0]) + err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status}) } return status, err } @@ -452,6 +436,10 @@ type CheckpointOpts struct { // EmptyNamespaces creates a namespace for the container but does not save its properties // Provide the namespaces you wish to be checkpointed without their settings on restore EmptyNamespaces []string + // LazyPages uses userfaultfd to lazily restore memory pages + LazyPages bool + // StatusFile is the file criu writes \0 to once lazy-pages is ready + StatusFile *os.File } type CgroupMode string @@ -493,6 +481,9 @@ func (o *CheckpointOpts) args() (out []string) { for _, ns := range o.EmptyNamespaces { out = append(out, "--empty-ns", ns) } + if o.LazyPages { + out = append(out, "--lazy-pages") + } return out } @@ -511,13 +502,23 @@ func PreDump(args []string) []string { // Checkpoint allows you to checkpoint a container using criu func (r *Runc) Checkpoint(context context.Context, id string, opts *CheckpointOpts, actions ...CheckpointAction) error { args := []string{"checkpoint"} + extraFiles := []*os.File{} if opts != nil { args = append(args, opts.args()...) + if opts.StatusFile != nil { + // pass the status file to the child process + extraFiles = []*os.File{opts.StatusFile} + // set status-fd to 3 as this will be the file descriptor + // of the first file passed with cmd.ExtraFiles + args = append(args, "--status-fd", "3") + } } for _, a := range actions { args = a(args) } - return r.runOrError(r.command(context, append(args, id)...)) + cmd := r.command(context, append(args, id)...) + cmd.ExtraFiles = extraFiles + return r.runOrError(cmd) } type RestoreOpts struct { @@ -583,7 +584,7 @@ func (r *Runc) Restore(context context.Context, id, bundle string, opts *Restore } status, err := Monitor.Wait(cmd, ec) if err == nil && status != 0 { - err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0]) + err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status}) } return status, err } @@ -680,7 +681,7 @@ func (r *Runc) runOrError(cmd *exec.Cmd) error { } status, err := Monitor.Wait(cmd, ec) if err == nil && status != 0 { - err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0]) + err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status}) } return err } @@ -708,8 +709,16 @@ func cmdOutput(cmd *exec.Cmd, combined bool) (*bytes.Buffer, error) { status, err := Monitor.Wait(cmd, ec) if err == nil && status != 0 { - err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0]) + err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status}) } return b, err } + +type ExitError struct { + Status int +} + +func (e *ExitError) Error() string { + return fmt.Sprintf("exit status %d", e.Status) +} diff --git a/vendor/github.com/containerd/go-runc/runc_unix.go b/vendor/github.com/containerd/go-runc/runc_unix.go new file mode 100644 index 00000000..548ffd6b --- /dev/null +++ b/vendor/github.com/containerd/go-runc/runc_unix.go @@ -0,0 +1,38 @@ +//+build !windows + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package runc + +import ( + "golang.org/x/sys/unix" +) + +// Runc is the client to the runc cli +type Runc struct { + //If command is empty, DefaultCommand is used + Command string + Root string + Debug bool + Log string + LogFormat Format + PdeathSignal unix.Signal + Setpgid bool + Criu string + SystemdCgroup bool + Rootless *bool // nil stands for "auto" +} diff --git a/vendor/github.com/containerd/go-runc/runc_windows.go b/vendor/github.com/containerd/go-runc/runc_windows.go new file mode 100644 index 00000000..c5873de8 --- /dev/null +++ b/vendor/github.com/containerd/go-runc/runc_windows.go @@ -0,0 +1,31 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package runc + +// Runc is the client to the runc cli +type Runc struct { + //If command is empty, DefaultCommand is used + Command string + Root string + Debug bool + Log string + LogFormat Format + Setpgid bool + Criu string + SystemdCgroup bool + Rootless *bool // nil stands for "auto" +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 74eb4535..f12b736c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -117,7 +117,7 @@ github.com/containerd/continuity/sysx github.com/containerd/fifo # github.com/containerd/go-cni v1.0.1 github.com/containerd/go-cni -# github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328 +# github.com/containerd/go-runc v0.0.0-20201017060547-8c4be61c2e34 github.com/containerd/go-runc # github.com/containerd/stargz-snapshotter v0.0.0-20200903042824-2ee75e91f8f9 github.com/containerd/stargz-snapshotter/cache