diff --git a/linux/shim/exec.go b/linux/shim/exec.go index d54593da0..2b790b233 100644 --- a/linux/shim/exec.go +++ b/linux/shim/exec.go @@ -86,7 +86,7 @@ func newExecProcess(context context.Context, path string, r *shimapi.ExecRequest spec.Terminal = r.Terminal if err := parent.runc.Exec(context, parent.id, spec, opts); err != nil { - return nil, err + return nil, parent.runcError(err, "runc exec failed") } if r.Stdin != "" { sc, err := fifo.OpenFifo(context, r.Stdin, syscall.O_WRONLY|syscall.O_NONBLOCK, 0) diff --git a/linux/shim/init.go b/linux/shim/init.go index 16c8f510d..11249c071 100644 --- a/linux/shim/init.go +++ b/linux/shim/init.go @@ -4,6 +4,7 @@ package shim import ( "context" + "encoding/json" "fmt" "io" "os" @@ -109,7 +110,7 @@ func newInitProcess(context context.Context, path, namespace string, r *shimapi. NoSubreaper: true, } if _, err := p.runc.Restore(context, r.ID, r.Bundle, opts); err != nil { - return nil, err + return nil, p.runcError(err, "runc restore failed") } } else { opts := &runc.CreateOpts{ @@ -121,7 +122,7 @@ func newInitProcess(context context.Context, path, namespace string, r *shimapi. opts.ConsoleSocket = socket } if err := p.runc.Create(context, r.ID, r.Bundle, opts); err != nil { - return nil, err + return nil, p.runcError(err, "runc create failed") } } if r.Stdin != "" { @@ -173,7 +174,7 @@ func (p *initProcess) ExitedAt() time.Time { func (p *initProcess) ContainerStatus(ctx context.Context) (string, error) { c, err := p.runc.State(ctx, p.id) if err != nil { - return "", err + return "", p.runcError(err, "runc state failed") } return c.Status, nil } @@ -181,7 +182,8 @@ func (p *initProcess) ContainerStatus(ctx context.Context) (string, error) { func (p *initProcess) Start(context context.Context) error { p.mu.Lock() defer p.mu.Unlock() - return p.runc.Start(context, p.id) + err := p.runc.Start(context, p.id) + return p.runcError(err, "runc start failed") } func (p *initProcess) Exited(status int) { @@ -208,7 +210,7 @@ func (p *initProcess) Delete(context context.Context) error { } p.io.Close() } - return err + return p.runcError(err, "runc delete failed") } func (p *initProcess) Resize(ws console.WinSize) error { @@ -219,23 +221,27 @@ func (p *initProcess) Resize(ws console.WinSize) error { } func (p *initProcess) Pause(context context.Context) error { - return p.runc.Pause(context, p.id) + err := p.runc.Pause(context, p.id) + return p.runcError(err, "runc pause failed") } func (p *initProcess) Resume(context context.Context) error { - return p.runc.Resume(context, p.id) + err := p.runc.Resume(context, p.id) + return p.runcError(err, "runc resume failed") } func (p *initProcess) Kill(context context.Context, signal uint32, all bool) error { - return p.runc.Kill(context, p.id, int(signal), &runc.KillOpts{ + err := p.runc.Kill(context, p.id, int(signal), &runc.KillOpts{ All: all, }) + return p.runcError(err, "runc kill failed") } func (p *initProcess) killAll(context context.Context) error { - return p.runc.Kill(context, p.id, int(syscall.SIGKILL), &runc.KillOpts{ + err := p.runc.Kill(context, p.id, int(syscall.SIGKILL), &runc.KillOpts{ All: true, }) + return p.runcError(err, "runc killall failed") } func (p *initProcess) Signal(sig int) error { @@ -271,6 +277,55 @@ func (p *initProcess) Checkpoint(context context.Context, r *shimapi.CheckpointR return nil } +// TODO(mlaventure): move to runc package? +func getLastRuncError(r *runc.Runc) (string, error) { + if r.Log == "" { + return "", nil + } + + f, err := os.OpenFile(r.Log, os.O_RDONLY, 0400) + if err != nil { + return "", err + } + + var ( + errMsg string + log struct { + Level string + Msg string + Time time.Time + } + ) + + dec := json.NewDecoder(f) + for err = nil; err == nil; { + if err = dec.Decode(&log); err != nil && err != io.EOF { + return "", err + } + if log.Level == "error" { + errMsg = strings.TrimSpace(log.Msg) + } + } + + return errMsg, nil +} + +func (p *initProcess) runcError(rErr error, msg string) error { + if rErr == nil { + return nil + } + + rMsg, err := getLastRuncError(p.runc) + switch { + case err != nil: + return errors.Wrapf(err, "%s: %s (%s)", msg, "unable to retrieve runc error", err.Error()) + case rMsg == "": + return errors.Wrap(err, msg) + default: + return errors.Errorf("%s: %s", msg, rMsg) + } +} + // criuError returns only the first line of the error message from criu // it tries to add an invalid dump log location when returning the message func criuError(err error) string {