From 6a571ecd40532918e3ec8cae363fb095d528dbbd Mon Sep 17 00:00:00 2001 From: Justin Cormack Date: Mon, 15 May 2017 08:20:19 +0100 Subject: [PATCH] Portability fixes for containerd shim Update go-runc to master with portability fixes. Subreaper only exists on Linux, and only Linux runs the shim in a mount namespace. With these changes the shim compiles on Darwin, which means the whole build compiles without errors now. Signed-off-by: Justin Cormack --- cmd/containerd-shim/main_unix.go | 23 ---- cmd/containerd-shim/shim_linux.go | 32 +++++ cmd/containerd-shim/shim_unix.go | 27 ++++ linux/shim/exec.go | 2 +- linux/shim/init.go | 2 +- linux/shim/io.go | 2 +- linux/shim/process.go | 2 +- linux/shim/service.go | 2 +- vendor.conf | 2 +- .../containerd/go-runc/command_linux.go | 21 +++ .../containerd/go-runc/command_other.go | 16 +++ .../github.com/containerd/go-runc/console.go | 75 ++++++++--- vendor/github.com/containerd/go-runc/runc.go | 14 -- .../runc/libcontainer/utils/cmsg.go | 95 ------------- .../runc/libcontainer/utils/utils.go | 126 ------------------ .../runc/libcontainer/utils/utils_unix.go | 43 ------ 16 files changed, 162 insertions(+), 322 deletions(-) create mode 100644 cmd/containerd-shim/shim_linux.go create mode 100644 cmd/containerd-shim/shim_unix.go create mode 100644 vendor/github.com/containerd/go-runc/command_linux.go create mode 100644 vendor/github.com/containerd/go-runc/command_other.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go diff --git a/cmd/containerd-shim/main_unix.go b/cmd/containerd-shim/main_unix.go index 0ad41ac1b..d5b24498f 100644 --- a/cmd/containerd-shim/main_unix.go +++ b/cmd/containerd-shim/main_unix.go @@ -6,7 +6,6 @@ import ( "fmt" "net" "os" - "os/signal" "strings" "golang.org/x/sys/unix" @@ -17,9 +16,7 @@ import ( shimapi "github.com/containerd/containerd/api/services/shim" "github.com/containerd/containerd/linux/shim" "github.com/containerd/containerd/reaper" - "github.com/containerd/containerd/sys" "github.com/containerd/containerd/version" - runc "github.com/containerd/go-runc" "github.com/urfave/cli" ) @@ -81,21 +78,6 @@ func main() { } } -// setupSignals creates a new signal handler for all signals and sets the shim as a -// sub-reaper so that the container processes are reparented -func setupSignals() (chan os.Signal, error) { - signals := make(chan os.Signal, 2048) - signal.Notify(signals) - // make sure runc is setup to use the monitor - // for waiting on processes - runc.Monitor = reaper.Default - // set the shim as the subreaper for all orphaned processes created by the container - if err := sys.SetSubreaper(1); err != nil { - return nil, err - } - return signals, nil -} - // serve serves the grpc API over a unix socket at the provided path // this function does not block func serve(server *grpc.Server, path string) error { @@ -131,8 +113,3 @@ func handleSignals(signals chan os.Signal, server *grpc.Server) error { } return nil } - -// setupRoot sets up the root as the shim is started in its own mount namespace -func setupRoot() error { - return unix.Mount("", "/", "", unix.MS_SLAVE|unix.MS_REC, "") -} diff --git a/cmd/containerd-shim/shim_linux.go b/cmd/containerd-shim/shim_linux.go new file mode 100644 index 000000000..e8cb32294 --- /dev/null +++ b/cmd/containerd-shim/shim_linux.go @@ -0,0 +1,32 @@ +package main + +import ( + "os" + "os/signal" + + "golang.org/x/sys/unix" + + "github.com/containerd/containerd/reaper" + "github.com/containerd/containerd/sys" + runc "github.com/containerd/go-runc" +) + +// setupSignals creates a new signal handler for all signals and sets the shim as a +// sub-reaper so that the container processes are reparented +func setupSignals() (chan os.Signal, error) { + signals := make(chan os.Signal, 2048) + signal.Notify(signals) + // make sure runc is setup to use the monitor + // for waiting on processes + runc.Monitor = reaper.Default + // set the shim as the subreaper for all orphaned processes created by the container + if err := sys.SetSubreaper(1); err != nil { + return nil, err + } + return signals, nil +} + +// setupRoot sets up the root as the shim is started in its own mount namespace +func setupRoot() error { + return unix.Mount("", "/", "", unix.MS_SLAVE|unix.MS_REC, "") +} diff --git a/cmd/containerd-shim/shim_unix.go b/cmd/containerd-shim/shim_unix.go new file mode 100644 index 000000000..0ff70d499 --- /dev/null +++ b/cmd/containerd-shim/shim_unix.go @@ -0,0 +1,27 @@ +// +build !linux + +package main + +import ( + "os" + "os/signal" + + "github.com/containerd/containerd/reaper" + runc "github.com/containerd/go-runc" +) + +// setupSignals creates a new signal handler for all signals and sets the shim as a +// sub-reaper so that the container processes are reparented +func setupSignals() (chan os.Signal, error) { + signals := make(chan os.Signal, 2048) + signal.Notify(signals) + // make sure runc is setup to use the monitor + // for waiting on processes + runc.Monitor = reaper.Default + return signals, nil +} + +// setupRoot is a no op except on Linux +func setupRoot() error { + return nil +} diff --git a/linux/shim/exec.go b/linux/shim/exec.go index f92ea31e5..72703677b 100644 --- a/linux/shim/exec.go +++ b/linux/shim/exec.go @@ -1,4 +1,4 @@ -// +build linux +// +build !windows package shim diff --git a/linux/shim/init.go b/linux/shim/init.go index 039ccb606..a5a9a6498 100644 --- a/linux/shim/init.go +++ b/linux/shim/init.go @@ -1,4 +1,4 @@ -// +build linux +// +build !windows package shim diff --git a/linux/shim/io.go b/linux/shim/io.go index 71c54daaf..66b79398d 100644 --- a/linux/shim/io.go +++ b/linux/shim/io.go @@ -1,4 +1,4 @@ -// +build linux +// +build !windows package shim diff --git a/linux/shim/process.go b/linux/shim/process.go index 69efea679..a08bacd6d 100644 --- a/linux/shim/process.go +++ b/linux/shim/process.go @@ -1,4 +1,4 @@ -// +build linux +// +build !windows package shim diff --git a/linux/shim/service.go b/linux/shim/service.go index 568ccaa05..8ccc456e8 100644 --- a/linux/shim/service.go +++ b/linux/shim/service.go @@ -1,4 +1,4 @@ -// +build linux +// +build !windows package shim diff --git a/vendor.conf b/vendor.conf index f3417bc4f..7fdbed588 100644 --- a/vendor.conf +++ b/vendor.conf @@ -1,5 +1,5 @@ github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6 -github.com/containerd/go-runc 5fe4d8cb7fdc0fae5f5a7f4f1d65a565032401b2 +github.com/containerd/go-runc 66d084de463f26efe84e6a5669e573fa6202e814 github.com/containerd/console a3863895279f5104533fd999c1babf80faffd98c github.com/containerd/cgroups 7b2d1a0f50963678d5799e29d17a4d611f5a5dee github.com/docker/go-metrics 8fd5772bf1584597834c6f7961a530f06cbfbb87 diff --git a/vendor/github.com/containerd/go-runc/command_linux.go b/vendor/github.com/containerd/go-runc/command_linux.go new file mode 100644 index 000000000..37149ced9 --- /dev/null +++ b/vendor/github.com/containerd/go-runc/command_linux.go @@ -0,0 +1,21 @@ +package runc + +import ( + "context" + "os/exec" + "syscall" +) + +func (r *Runc) command(context context.Context, args ...string) *exec.Cmd { + command := r.Command + if command == "" { + command = DefaultCommand + } + cmd := exec.CommandContext(context, command, append(r.args(), args...)...) + if r.PdeathSignal != 0 { + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: r.PdeathSignal, + } + } + return cmd +} diff --git a/vendor/github.com/containerd/go-runc/command_other.go b/vendor/github.com/containerd/go-runc/command_other.go new file mode 100644 index 000000000..bf03a2f9d --- /dev/null +++ b/vendor/github.com/containerd/go-runc/command_other.go @@ -0,0 +1,16 @@ +// +build !linux + +package runc + +import ( + "context" + "os/exec" +) + +func (r *Runc) command(context context.Context, args ...string) *exec.Cmd { + command := r.Command + if command == "" { + command = DefaultCommand + } + return exec.CommandContext(context, command, append(r.args(), args...)...) +} diff --git a/vendor/github.com/containerd/go-runc/console.go b/vendor/github.com/containerd/go-runc/console.go index d84bd9f0f..11314ead0 100644 --- a/vendor/github.com/containerd/go-runc/console.go +++ b/vendor/github.com/containerd/go-runc/console.go @@ -10,7 +10,7 @@ import ( "path/filepath" "github.com/containerd/console" - "github.com/opencontainers/runc/libcontainer/utils" + "golang.org/x/sys/unix" ) // NewConsoleSocket creates a new unix socket at the provided path to accept a @@ -20,13 +20,16 @@ func NewConsoleSocket(path string) (*Socket, error) { if err != nil { return nil, err } - l, err := net.Listen("unix", abs) + addr, err := net.ResolveUnixAddr("unix", abs) + if err != nil { + return nil, err + } + l, err := net.ListenUnix("unix", addr) if err != nil { return nil, err } return &Socket{ l: l, - path: abs, }, nil } @@ -41,27 +44,73 @@ func NewTempConsoleSocket() (*Socket, error) { if err != nil { return nil, err } - l, err := net.Listen("unix", abs) + addr, err := net.ResolveUnixAddr("unix", abs) + if err != nil { + return nil, err + } + l, err := net.ListenUnix("unix", addr) if err != nil { return nil, err } return &Socket{ l: l, rmdir: true, - path: abs, }, nil } // Socket is a unix socket that accepts the pty master created by runc type Socket struct { - path string rmdir bool - l net.Listener + l *net.UnixListener } // Path returns the path to the unix socket on disk func (c *Socket) Path() string { - return c.path + return c.l.Addr().String() +} + +// recvFd waits for a file descriptor to be sent over the given AF_UNIX +// socket. The file name of the remote file descriptor will be recreated +// locally (it is sent as non-auxiliary data in the same payload). +func recvFd(socket *net.UnixConn) (*os.File, error) { + const MaxNameLen = 4096 + var oobSpace = unix.CmsgSpace(4) + + name := make([]byte, MaxNameLen) + oob := make([]byte, oobSpace) + + n, oobn, _, _, err := socket.ReadMsgUnix(name, oob) + if err != nil { + return nil, err + } + + if n >= MaxNameLen || oobn != oobSpace { + return nil, fmt.Errorf("recvfd: incorrect number of bytes read (n=%d oobn=%d)", n, oobn) + } + + // Truncate. + name = name[:n] + oob = oob[:oobn] + + scms, err := unix.ParseSocketControlMessage(oob) + if err != nil { + return nil, err + } + if len(scms) != 1 { + return nil, fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms)) + } + scm := scms[0] + + fds, err := unix.ParseUnixRights(&scm) + if err != nil { + return nil, err + } + if len(fds) != 1 { + return nil, fmt.Errorf("recvfd: number of fds is not 1: %d", len(fds)) + } + fd := uintptr(fds[0]) + + return os.NewFile(fd, string(name)), nil } // ReceiveMaster blocks until the socket receives the pty master @@ -71,15 +120,11 @@ func (c *Socket) ReceiveMaster() (console.Console, error) { return nil, err } defer conn.Close() - unix, ok := conn.(*net.UnixConn) + uc, ok := conn.(*net.UnixConn) if !ok { return nil, fmt.Errorf("received connection which was not a unix socket") } - sock, err := unix.File() - if err != nil { - return nil, err - } - f, err := utils.RecvFd(sock) + f, err := recvFd(uc) if err != nil { return nil, err } @@ -90,7 +135,7 @@ func (c *Socket) ReceiveMaster() (console.Console, error) { func (c *Socket) Close() error { err := c.l.Close() if c.rmdir { - if rerr := os.RemoveAll(filepath.Dir(c.path)); err == nil { + if rerr := os.RemoveAll(filepath.Dir(c.Path())); err == nil { err = rerr } } diff --git a/vendor/github.com/containerd/go-runc/runc.go b/vendor/github.com/containerd/go-runc/runc.go index 9515deb7f..5f2fd52d2 100644 --- a/vendor/github.com/containerd/go-runc/runc.go +++ b/vendor/github.com/containerd/go-runc/runc.go @@ -523,20 +523,6 @@ func (r *Runc) args() (out []string) { return out } -func (r *Runc) command(context context.Context, args ...string) *exec.Cmd { - command := r.Command - if command == "" { - command = DefaultCommand - } - cmd := exec.CommandContext(context, command, append(r.args(), args...)...) - if r.PdeathSignal != 0 { - cmd.SysProcAttr = &syscall.SysProcAttr{ - Pdeathsig: r.PdeathSignal, - } - } - return cmd -} - // runOrError will run the provided command. If an error is // encountered and neither Stdout or Stderr was set the error and the // stderr of the command will be returned in the format of : diff --git a/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go b/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go deleted file mode 100644 index 2cbb6491a..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go +++ /dev/null @@ -1,95 +0,0 @@ -// +build linux - -package utils - -/* - * Copyright 2016, 2017 SUSE LLC - * - * 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. - */ - -import ( - "fmt" - "os" - - "golang.org/x/sys/unix" -) - -// MaxSendfdLen is the maximum length of the name of a file descriptor being -// sent using SendFd. The name of the file handle returned by RecvFd will never -// be larger than this value. -const MaxNameLen = 4096 - -// oobSpace is the size of the oob slice required to store a single FD. Note -// that unix.UnixRights appears to make the assumption that fd is always int32, -// so sizeof(fd) = 4. -var oobSpace = unix.CmsgSpace(4) - -// RecvFd waits for a file descriptor to be sent over the given AF_UNIX -// socket. The file name of the remote file descriptor will be recreated -// locally (it is sent as non-auxiliary data in the same payload). -func RecvFd(socket *os.File) (*os.File, error) { - // For some reason, unix.Recvmsg uses the length rather than the capacity - // when passing the msg_controllen and other attributes to recvmsg. So we - // have to actually set the length. - name := make([]byte, MaxNameLen) - oob := make([]byte, oobSpace) - - sockfd := socket.Fd() - n, oobn, _, _, err := unix.Recvmsg(int(sockfd), name, oob, 0) - if err != nil { - return nil, err - } - - if n >= MaxNameLen || oobn != oobSpace { - return nil, fmt.Errorf("recvfd: incorrect number of bytes read (n=%d oobn=%d)", n, oobn) - } - - // Truncate. - name = name[:n] - oob = oob[:oobn] - - scms, err := unix.ParseSocketControlMessage(oob) - if err != nil { - return nil, err - } - if len(scms) != 1 { - return nil, fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms)) - } - scm := scms[0] - - fds, err := unix.ParseUnixRights(&scm) - if err != nil { - return nil, err - } - if len(fds) != 1 { - return nil, fmt.Errorf("recvfd: number of fds is not 1: %d", len(fds)) - } - fd := uintptr(fds[0]) - - return os.NewFile(fd, string(name)), nil -} - -// SendFd sends a file descriptor over the given AF_UNIX socket. In -// addition, the file.Name() of the given file will also be sent as -// non-auxiliary data in the same payload (allowing to send contextual -// information for a file descriptor). -func SendFd(socket, file *os.File) error { - name := []byte(file.Name()) - if len(name) >= MaxNameLen { - return fmt.Errorf("sendfd: filename too long: %s", file.Name()) - } - oob := unix.UnixRights(int(file.Fd())) - - return unix.Sendmsg(int(socket.Fd()), name, oob, nil, 0) -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go b/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go deleted file mode 100644 index 2b35b9a7b..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go +++ /dev/null @@ -1,126 +0,0 @@ -package utils - -import ( - "crypto/rand" - "encoding/hex" - "encoding/json" - "io" - "os" - "path/filepath" - "strings" - "syscall" - "unsafe" -) - -const ( - exitSignalOffset = 128 -) - -// GenerateRandomName returns a new name joined with a prefix. This size -// specified is used to truncate the randomly generated value -func GenerateRandomName(prefix string, size int) (string, error) { - id := make([]byte, 32) - if _, err := io.ReadFull(rand.Reader, id); err != nil { - return "", err - } - if size > 64 { - size = 64 - } - return prefix + hex.EncodeToString(id)[:size], nil -} - -// ResolveRootfs ensures that the current working directory is -// not a symlink and returns the absolute path to the rootfs -func ResolveRootfs(uncleanRootfs string) (string, error) { - rootfs, err := filepath.Abs(uncleanRootfs) - if err != nil { - return "", err - } - return filepath.EvalSymlinks(rootfs) -} - -// 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() -} - -// WriteJSON writes the provided struct v to w using standard json marshaling -func WriteJSON(w io.Writer, v interface{}) error { - data, err := json.Marshal(v) - if err != nil { - return err - } - _, err = w.Write(data) - return err -} - -// CleanPath makes a path safe for use with filepath.Join. This is done by not -// only cleaning the path, but also (if the path is relative) adding a leading -// '/' and cleaning it (then removing the leading '/'). This ensures that a -// path resulting from prepending another path will always resolve to lexically -// be a subdirectory of the prefixed path. This is all done lexically, so paths -// that include symlinks won't be safe as a result of using CleanPath. -func CleanPath(path string) string { - // Deal with empty strings nicely. - if path == "" { - return "" - } - - // Ensure that all paths are cleaned (especially problematic ones like - // "/../../../../../" which can cause lots of issues). - path = filepath.Clean(path) - - // If the path isn't absolute, we need to do more processing to fix paths - // such as "../../../..//some/path". We also shouldn't convert absolute - // paths to relative ones. - if !filepath.IsAbs(path) { - path = filepath.Clean(string(os.PathSeparator) + path) - // This can't fail, as (by definition) all paths are relative to root. - path, _ = filepath.Rel(string(os.PathSeparator), path) - } - - // Clean the path again for good measure. - return filepath.Clean(path) -} - -// SearchLabels searches a list of key-value pairs for the provided key and -// returns the corresponding value. The pairs must be separated with '='. -func SearchLabels(labels []string, query string) string { - for _, l := range labels { - parts := strings.SplitN(l, "=", 2) - if len(parts) < 2 { - continue - } - if parts[0] == query { - return parts[1] - } - } - return "" -} - -// Annotations returns the bundle path and user defined annotations from the -// libcontainer state. We need to remove the bundle because that is a label -// added by libcontainer. -func Annotations(labels []string) (bundle string, userAnnotations map[string]string) { - userAnnotations = make(map[string]string) - for _, l := range labels { - parts := strings.SplitN(l, "=", 2) - if len(parts) < 2 { - continue - } - if parts[0] == "bundle" { - bundle = parts[1] - } else { - userAnnotations[parts[0]] = parts[1] - } - } - return -} - -func GetIntSize() int { - return int(unsafe.Sizeof(1)) -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go b/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go deleted file mode 100644 index 7b798cc79..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go +++ /dev/null @@ -1,43 +0,0 @@ -// +build !windows - -package utils - -import ( - "io/ioutil" - "os" - "strconv" - "syscall" -) - -func CloseExecFrom(minFd int) error { - fdList, err := ioutil.ReadDir("/proc/self/fd") - if err != nil { - return err - } - for _, fi := range fdList { - fd, err := strconv.Atoi(fi.Name()) - if err != nil { - // ignore non-numeric file names - continue - } - - if fd < minFd { - // ignore descriptors lower than our specified minimum - continue - } - - // intentionally ignore errors from syscall.CloseOnExec - syscall.CloseOnExec(fd) - // the cases where this might fail are basically file descriptors that have already been closed (including and especially the one that was created when ioutil.ReadDir did the "opendir" syscall) - } - return nil -} - -// NewSockPair returns a new unix socket pair -func NewSockPair(name string) (parent *os.File, child *os.File, err error) { - fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0) - if err != nil { - return nil, nil, err - } - return os.NewFile(uintptr(fds[1]), name+"-p"), os.NewFile(uintptr(fds[0]), name+"-c"), nil -}