Merge pull request #861 from justincormack/go-runc-port

Portability fixes for containerd shim
This commit is contained in:
Kenfe-Mickaël Laventure 2017-05-16 12:07:08 -07:00 committed by GitHub
commit 47718b0930
16 changed files with 162 additions and 322 deletions

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"net" "net"
"os" "os"
"os/signal"
"strings" "strings"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@ -17,9 +16,7 @@ import (
shimapi "github.com/containerd/containerd/api/services/shim" shimapi "github.com/containerd/containerd/api/services/shim"
"github.com/containerd/containerd/linux/shim" "github.com/containerd/containerd/linux/shim"
"github.com/containerd/containerd/reaper" "github.com/containerd/containerd/reaper"
"github.com/containerd/containerd/sys"
"github.com/containerd/containerd/version" "github.com/containerd/containerd/version"
runc "github.com/containerd/go-runc"
"github.com/urfave/cli" "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 // serve serves the grpc API over a unix socket at the provided path
// this function does not block // this function does not block
func serve(server *grpc.Server, path string) error { func serve(server *grpc.Server, path string) error {
@ -131,8 +113,3 @@ func handleSignals(signals chan os.Signal, server *grpc.Server) error {
} }
return nil 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, "")
}

View File

@ -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, "")
}

View File

@ -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
}

View File

@ -1,4 +1,4 @@
// +build linux // +build !windows
package shim package shim

View File

@ -1,4 +1,4 @@
// +build linux // +build !windows
package shim package shim

View File

@ -1,4 +1,4 @@
// +build linux // +build !windows
package shim package shim

View File

@ -1,4 +1,4 @@
// +build linux // +build !windows
package shim package shim

View File

@ -1,4 +1,4 @@
// +build linux // +build !windows
package shim package shim

View File

@ -1,5 +1,5 @@
github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6 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/console a3863895279f5104533fd999c1babf80faffd98c
github.com/containerd/cgroups 7b2d1a0f50963678d5799e29d17a4d611f5a5dee github.com/containerd/cgroups 7b2d1a0f50963678d5799e29d17a4d611f5a5dee
github.com/docker/go-metrics 8fd5772bf1584597834c6f7961a530f06cbfbb87 github.com/docker/go-metrics 8fd5772bf1584597834c6f7961a530f06cbfbb87

21
vendor/github.com/containerd/go-runc/command_linux.go generated vendored Normal file
View File

@ -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
}

16
vendor/github.com/containerd/go-runc/command_other.go generated vendored Normal file
View File

@ -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...)...)
}

View File

@ -10,7 +10,7 @@ import (
"path/filepath" "path/filepath"
"github.com/containerd/console" "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 // 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 { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
return &Socket{ return &Socket{
l: l, l: l,
path: abs,
}, nil }, nil
} }
@ -41,27 +44,73 @@ func NewTempConsoleSocket() (*Socket, error) {
if err != nil { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
return &Socket{ return &Socket{
l: l, l: l,
rmdir: true, rmdir: true,
path: abs,
}, nil }, nil
} }
// Socket is a unix socket that accepts the pty master created by runc // Socket is a unix socket that accepts the pty master created by runc
type Socket struct { type Socket struct {
path string
rmdir bool rmdir bool
l net.Listener l *net.UnixListener
} }
// Path returns the path to the unix socket on disk // Path returns the path to the unix socket on disk
func (c *Socket) Path() string { 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 // ReceiveMaster blocks until the socket receives the pty master
@ -71,15 +120,11 @@ func (c *Socket) ReceiveMaster() (console.Console, error) {
return nil, err return nil, err
} }
defer conn.Close() defer conn.Close()
unix, ok := conn.(*net.UnixConn) uc, ok := conn.(*net.UnixConn)
if !ok { if !ok {
return nil, fmt.Errorf("received connection which was not a unix socket") return nil, fmt.Errorf("received connection which was not a unix socket")
} }
sock, err := unix.File() f, err := recvFd(uc)
if err != nil {
return nil, err
}
f, err := utils.RecvFd(sock)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -90,7 +135,7 @@ func (c *Socket) ReceiveMaster() (console.Console, error) {
func (c *Socket) Close() error { func (c *Socket) Close() error {
err := c.l.Close() err := c.l.Close()
if c.rmdir { 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 err = rerr
} }
} }

View File

@ -523,20 +523,6 @@ func (r *Runc) args() (out []string) {
return out 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 // runOrError will run the provided command. If an error is
// encountered and neither Stdout or Stderr was set the error and the // encountered and neither Stdout or Stderr was set the error and the
// stderr of the command will be returned in the format of <error>: // stderr of the command will be returned in the format of <error>:

View File

@ -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)
}

View File

@ -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 "../../../../<etc>/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))
}

View File

@ -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
}