diff --git a/Makefile b/Makefile index 4dc6b9f..f96460e 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ daemon-static: cd containerd && go build -ldflags "-w -extldflags -static ${LDFLAGS}" -tags "$(BUILDTAGS)" -o ../bin/containerd shim: bin - cd containerd-shim && go build -tags "$(BUILDTAGS)" -o ../bin/containerd-shim + cd containerd-shim && go build -tags "$(BUILDTAGS)" -ldflags "-w" -o ../bin/containerd-shim shim-static: cd containerd-shim && go build -ldflags "-w -extldflags -static ${LDFLAGS}" -tags "$(BUILDTAGS)" -o ../bin/containerd-shim diff --git a/containerd-shim/console.go b/containerd-shim/console.go new file mode 100644 index 0000000..6b15234 --- /dev/null +++ b/containerd-shim/console.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" + "os" + "syscall" + "unsafe" +) + +// NewConsole returns an initalized console that can be used within a container by copying bytes +// from the master side to the slave that is attached as the tty for the container's init process. +func newConsole(uid, gid int) (*os.File, string, error) { + master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0) + if err != nil { + return nil, "", err + } + console, err := ptsname(master) + if err != nil { + return nil, "", err + } + if err := unlockpt(master); err != nil { + return nil, "", err + } + if err := os.Chmod(console, 0600); err != nil { + return nil, "", err + } + if err := os.Chown(console, uid, gid); err != nil { + return nil, "", err + } + return master, console, nil +} + +func ioctl(fd uintptr, flag, data uintptr) error { + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, flag, data); err != 0 { + return err + } + return nil +} + +// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f. +// unlockpt should be called before opening the slave side of a pty. +func unlockpt(f *os.File) error { + var u int32 + return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) +} + +// ptsname retrieves the name of the first available pts for the given master. +func ptsname(f *os.File) (string, error) { + var n int32 + if err := ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))); err != nil { + return "", err + } + return fmt.Sprintf("/dev/pts/%d", n), nil +} diff --git a/containerd-shim/main.go b/containerd-shim/main.go index 6b84b77..afff86b 100644 --- a/containerd-shim/main.go +++ b/containerd-shim/main.go @@ -8,11 +8,14 @@ import ( "path/filepath" "syscall" - "github.com/Sirupsen/logrus" "github.com/docker/containerd/osutils" "github.com/docker/docker/pkg/term" ) +func writeMessage(f *os.File, level string, err error) { + fmt.Fprintf(f, `{"level": "%s","msg": "%s"}`, level, err) +} + // containerd-shim is a small shim that sits in front of a runtime implementation // that allows it to be repartented to init and handle reattach from the caller. // @@ -28,9 +31,7 @@ func main() { if err != nil { panic(err) } - logrus.SetOutput(f) - logrus.SetFormatter(&logrus.JSONFormatter{}) - if err := start(); err != nil { + if err := start(f); err != nil { // this means that the runtime failed starting the container and will have the // proper error messages in the runtime log so we should to treat this as a // shim failure because the sim executed properly @@ -41,13 +42,13 @@ func main() { // log the error instead of writing to stderr because the shim will have // /dev/null as it's stdio because it is supposed to be reparented to system // init and will not have anyone to read from it - logrus.Error(err) + writeMessage(f, "error", err) f.Close() os.Exit(1) } } -func start() error { +func start(log *os.File) error { // start handling signals as soon as possible so that things are properly reaped // or if runtime exits before we hit the handler signals := make(chan os.Signal, 2048) @@ -73,7 +74,7 @@ func start() error { } defer func() { if err := p.Close(); err != nil { - logrus.Warn(err) + writeMessage(log, "warn", err) } }() if err := p.start(); err != nil { @@ -84,7 +85,7 @@ func start() error { for { var msg, w, h int if _, err := fmt.Fscanf(control, "%d %d %d\n", &msg, &w, &h); err != nil { - logrus.Warn(err) + continue } switch msg { case 0: @@ -108,21 +109,12 @@ func start() error { for s := range signals { switch s { case syscall.SIGCHLD: - exits, err := osutils.Reap() - if err != nil { - logrus.Warn(err) - } + exits, _ := osutils.Reap() for _, e := range exits { // check to see if runtime is one of the processes that has exited if e.Pid == p.pid() { exitShim = true - logrus.WithFields(logrus.Fields{ - "pid": e.Pid, - "status": e.Status, - }).Info("shim: runtime exited") - if err := writeInt("exitStatus", e.Status); err != nil { - logrus.WithFields(logrus.Fields{"status": e.Status}).Warn(err) - } + writeInt("exitStatus", e.Status) } } } diff --git a/containerd-shim/process.go b/containerd-shim/process.go index 3ba71b0..1d37d38 100644 --- a/containerd-shim/process.go +++ b/containerd-shim/process.go @@ -12,13 +12,41 @@ import ( "strconv" "sync" "syscall" + "time" - "github.com/docker/containerd/runtime" - "github.com/opencontainers/runc/libcontainer" + "github.com/docker/containerd/specs" ) var errRuntime = errors.New("shim: runtime execution error") +type checkpoint struct { + // Timestamp is the time that checkpoint happened + Created time.Time `json:"created"` + // Name is the name of the checkpoint + Name string `json:"name"` + // Tcp checkpoints open tcp connections + Tcp bool `json:"tcp"` + // UnixSockets persists unix sockets in the checkpoint + UnixSockets bool `json:"unixSockets"` + // Shell persists tty sessions in the checkpoint + Shell bool `json:"shell"` + // Exit exits the container after the checkpoint is finished + Exit bool `json:"exit"` +} + +type processState struct { + specs.ProcessSpec + Exec bool `json:"exec"` + Stdin string `json:"containerdStdin"` + Stdout string `json:"containerdStdout"` + Stderr string `json:"containerdStderr"` + RuntimeArgs []string `json:"runtimeArgs"` + NoPivotRoot bool `json:"noPivotRoot"` + Checkpoint string `json:"checkpoint"` + RootUID int `json:"rootUID"` + RootGID int `json:"rootGID"` +} + type process struct { sync.WaitGroup id string @@ -26,12 +54,12 @@ type process struct { stdio *stdio exec bool containerPid int - checkpoint *runtime.Checkpoint + checkpoint *checkpoint shimIO *IO stdinCloser io.Closer - console libcontainer.Console + console *os.File consolePath string - state *runtime.ProcessState + state *processState runtime string } @@ -59,26 +87,26 @@ func newProcess(id, bundle, runtimeName string) (*process, error) { return p, nil } -func loadProcess() (*runtime.ProcessState, error) { +func loadProcess() (*processState, error) { f, err := os.Open("process.json") if err != nil { return nil, err } defer f.Close() - var s runtime.ProcessState + var s processState if err := json.NewDecoder(f).Decode(&s); err != nil { return nil, err } return &s, nil } -func loadCheckpoint(bundle, name string) (*runtime.Checkpoint, error) { +func loadCheckpoint(bundle, name string) (*checkpoint, error) { f, err := os.Open(filepath.Join(bundle, "checkpoints", name, "config.json")) if err != nil { return nil, err } defer f.Close() - var cpt runtime.Checkpoint + var cpt checkpoint if err := json.NewDecoder(f).Decode(&cpt); err != nil { return nil, err } @@ -200,25 +228,25 @@ func (p *process) openIO() error { }() if p.state.Terminal { - console, err := libcontainer.NewConsole(uid, gid) + master, console, err := newConsole(uid, gid) if err != nil { return err } - p.console = console - p.consolePath = console.Path() + p.console = master + p.consolePath = console stdin, err := os.OpenFile(p.state.Stdin, syscall.O_RDONLY, 0) if err != nil { return err } - go io.Copy(console, stdin) + go io.Copy(master, stdin) stdout, err := os.OpenFile(p.state.Stdout, syscall.O_RDWR, 0) if err != nil { return err } p.Add(1) go func() { - io.Copy(stdout, console) - console.Close() + io.Copy(stdout, master) + master.Close() p.Done() }() return nil diff --git a/osutils/reaper.go b/osutils/reaper.go index f8106b0..ab7d24d 100644 --- a/osutils/reaper.go +++ b/osutils/reaper.go @@ -2,11 +2,7 @@ package osutils -import ( - "syscall" - - "github.com/opencontainers/runc/libcontainer/utils" -) +import "syscall" // Exit is the wait4 information from an exited process type Exit struct { @@ -34,7 +30,18 @@ func Reap() (exits []Exit, err error) { } exits = append(exits, Exit{ Pid: pid, - Status: utils.ExitStatus(ws), + Status: exitStatus(ws), }) } } + +const exitSignalOffset = 128 + +// exitStatus returns the correct exit status for a process based on if it +// was signaled or exited cleanly +func exitStatus(status syscall.WaitStatus) int { + if status.Signaled() { + return exitSignalOffset + int(status.Signal()) + } + return status.ExitStatus() +}