Partial port to Darwin and FreeBSD

This mainly fixes Linux vs generic Unix differences, with some
differences between Darwin and Freebsd (which are close bit not
identical). Should make fixing for other Unix platforms easier.

Note there are not yet `runc` equivalents for these platforms;
my current use case is image manipulation for the `moby` tool.
However there is interest in OCI runtime ports for both platforms.

Current status is that MacOS can build and run `ctr`, `dist`
and `containerd` and some operations are supported. FreeBSD 11
still needs some more fixes to continuity for extended attributes.

Signed-off-by: Justin Cormack <justin.cormack@docker.com>
This commit is contained in:
Justin Cormack 2017-04-28 19:17:43 +01:00
parent 98be9f4b4e
commit 1d27259777
22 changed files with 357 additions and 125 deletions

View File

@ -1,3 +1,5 @@
// +build !windows
package archive package archive
import ( import (

14
archive/time_darwin.go Normal file
View File

@ -0,0 +1,14 @@
package archive
import (
"time"
"github.com/pkg/errors"
)
// as at MacOS 10.12 there is apparently no way to set timestamps
// with nanosecond precision. We could fall back to utimes/lutimes
// and lose the precision as a temporary workaround.
func chtimes(path string, atime, mtime time.Time) error {
return errors.New("OSX missing UtimesNanoAt")
}

View File

@ -1,3 +1,5 @@
// +build linux freebsd
package archive package archive
import ( import (

View File

@ -0,0 +1,7 @@
// +build darwin freebsd
package main
import (
_ "github.com/containerd/containerd/snapshot/naive"
)

View File

@ -0,0 +1,16 @@
package main
func defaultConfig() *config {
return &config{
Root: "/var/lib/containerd",
State: "/run/containerd",
GRPC: grpcConfig{
Address: "/run/containerd/containerd.sock",
},
Debug: debug{
Level: "info",
Address: "/run/containerd/debug.sock",
},
Snapshotter: "overlay",
}
}

View File

@ -1,4 +1,4 @@
// +build !windows // +build darwin freebsd
package main package main
@ -13,6 +13,6 @@ func defaultConfig() *config {
Level: "info", Level: "info",
Address: "/run/containerd/debug.sock", Address: "/run/containerd/debug.sock",
}, },
Snapshotter: "overlay", Snapshotter: "naive",
} }
} }

View File

@ -0,0 +1,59 @@
package main
import (
"os"
"golang.org/x/sys/unix"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/reaper"
"github.com/containerd/containerd/sys"
"github.com/urfave/cli"
"google.golang.org/grpc"
)
const (
defaultConfigPath = "/etc/containerd/config.toml"
)
var (
handledSignals = []os.Signal{unix.SIGTERM, unix.SIGINT, unix.SIGUSR1, unix.SIGCHLD}
)
func platformInit(context *cli.Context) error {
if conf.Subreaper {
log.G(global).Info("setting subreaper...")
if err := sys.SetSubreaper(1); err != nil {
return err
}
}
if conf.OOMScore != 0 {
log.G(global).Infof("changing OOM score to %d", conf.OOMScore)
if err := sys.SetOOMScore(os.Getpid(), conf.OOMScore); err != nil {
return err
}
}
if err := os.MkdirAll(conf.State, 0750); err != nil {
return err
}
if err := os.Chown(conf.State, conf.GRPC.Uid, conf.GRPC.Gid); err != nil {
return err
}
return nil
}
func handleSignals(signals chan os.Signal, server *grpc.Server) error {
for s := range signals {
log.G(global).WithField("signal", s).Debug("received signal")
switch s {
case unix.SIGCHLD:
if err := reaper.Reap(); err != nil {
log.G(global).WithError(err).Error("reap containerd processes")
}
default:
server.Stop()
return nil
}
}
return nil
}

View File

@ -1,4 +1,4 @@
// +build !windows // +build darwin freebsd
package main package main
@ -9,7 +9,6 @@ import (
"github.com/containerd/containerd/log" "github.com/containerd/containerd/log"
"github.com/containerd/containerd/reaper" "github.com/containerd/containerd/reaper"
"github.com/containerd/containerd/sys"
"github.com/urfave/cli" "github.com/urfave/cli"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
@ -23,18 +22,6 @@ var (
) )
func platformInit(context *cli.Context) error { func platformInit(context *cli.Context) error {
if conf.Subreaper {
log.G(global).Info("setting subreaper...")
if err := sys.SetSubreaper(1); err != nil {
return err
}
}
if conf.OOMScore != 0 {
log.G(global).Infof("changing OOM score to %d", conf.OOMScore)
if err := sys.SetOOMScore(os.Getpid(), conf.OOMScore); err != nil {
return err
}
}
if err := os.MkdirAll(conf.State, 0750); err != nil { if err := os.MkdirAll(conf.State, 0750); err != nil {
return err return err
} }

View File

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

45
cmd/ctr/signals_linux.go Normal file
View File

@ -0,0 +1,45 @@
package main
import (
"syscall"
"golang.org/x/sys/unix"
)
var signalMap = map[string]syscall.Signal{
"ABRT": unix.SIGABRT,
"ALRM": unix.SIGALRM,
"BUS": unix.SIGBUS,
"CHLD": unix.SIGCHLD,
"CLD": unix.SIGCLD,
"CONT": unix.SIGCONT,
"FPE": unix.SIGFPE,
"HUP": unix.SIGHUP,
"ILL": unix.SIGILL,
"INT": unix.SIGINT,
"IO": unix.SIGIO,
"IOT": unix.SIGIOT,
"KILL": unix.SIGKILL,
"PIPE": unix.SIGPIPE,
"POLL": unix.SIGPOLL,
"PROF": unix.SIGPROF,
"PWR": unix.SIGPWR,
"QUIT": unix.SIGQUIT,
"SEGV": unix.SIGSEGV,
"STKFLT": unix.SIGSTKFLT,
"STOP": unix.SIGSTOP,
"SYS": unix.SIGSYS,
"TERM": unix.SIGTERM,
"TRAP": unix.SIGTRAP,
"TSTP": unix.SIGTSTP,
"TTIN": unix.SIGTTIN,
"TTOU": unix.SIGTTOU,
"UNUSED": unix.SIGUNUSED,
"URG": unix.SIGURG,
"USR1": unix.SIGUSR1,
"USR2": unix.SIGUSR2,
"VTALRM": unix.SIGVTALRM,
"WINCH": unix.SIGWINCH,
"XCPU": unix.SIGXCPU,
"XFSZ": unix.SIGXFSZ,
}

42
cmd/ctr/signals_unix.go Normal file
View File

@ -0,0 +1,42 @@
// +build darwin freebsd
package main
import (
"syscall"
"golang.org/x/sys/unix"
)
var signalMap = map[string]syscall.Signal{
"ABRT": unix.SIGABRT,
"ALRM": unix.SIGALRM,
"BUS": unix.SIGBUS,
"CHLD": unix.SIGCHLD,
"CONT": unix.SIGCONT,
"FPE": unix.SIGFPE,
"HUP": unix.SIGHUP,
"ILL": unix.SIGILL,
"INT": unix.SIGINT,
"IO": unix.SIGIO,
"IOT": unix.SIGIOT,
"KILL": unix.SIGKILL,
"PIPE": unix.SIGPIPE,
"PROF": unix.SIGPROF,
"QUIT": unix.SIGQUIT,
"SEGV": unix.SIGSEGV,
"STOP": unix.SIGSTOP,
"SYS": unix.SIGSYS,
"TERM": unix.SIGTERM,
"TRAP": unix.SIGTRAP,
"TSTP": unix.SIGTSTP,
"TTIN": unix.SIGTTIN,
"TTOU": unix.SIGTTOU,
"URG": unix.SIGURG,
"USR1": unix.SIGUSR1,
"USR2": unix.SIGUSR2,
"VTALRM": unix.SIGVTALRM,
"WINCH": unix.SIGWINCH,
"XCPU": unix.SIGXCPU,
"XFSZ": unix.SIGXFSZ,
}

View File

@ -17,7 +17,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/tonistiigi/fifo" "github.com/tonistiigi/fifo"
"github.com/urfave/cli" "github.com/urfave/cli"
"golang.org/x/sys/unix"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
) )
@ -100,41 +99,3 @@ func getGRPCConnection(context *cli.Context) (*grpc.ClientConn, error) {
grpcConn = conn grpcConn = conn
return grpcConn, nil return grpcConn, nil
} }
var signalMap = map[string]syscall.Signal{
"ABRT": unix.SIGABRT,
"ALRM": unix.SIGALRM,
"BUS": unix.SIGBUS,
"CHLD": unix.SIGCHLD,
"CLD": unix.SIGCLD,
"CONT": unix.SIGCONT,
"FPE": unix.SIGFPE,
"HUP": unix.SIGHUP,
"ILL": unix.SIGILL,
"INT": unix.SIGINT,
"IO": unix.SIGIO,
"IOT": unix.SIGIOT,
"KILL": unix.SIGKILL,
"PIPE": unix.SIGPIPE,
"POLL": unix.SIGPOLL,
"PROF": unix.SIGPROF,
"PWR": unix.SIGPWR,
"QUIT": unix.SIGQUIT,
"SEGV": unix.SIGSEGV,
"STKFLT": unix.SIGSTKFLT,
"STOP": unix.SIGSTOP,
"SYS": unix.SIGSYS,
"TERM": unix.SIGTERM,
"TRAP": unix.SIGTRAP,
"TSTP": unix.SIGTSTP,
"TTIN": unix.SIGTTIN,
"TTOU": unix.SIGTTOU,
"UNUSED": unix.SIGUNUSED,
"URG": unix.SIGURG,
"USR1": unix.SIGUSR1,
"USR2": unix.SIGUSR2,
"VTALRM": unix.SIGVTALRM,
"WINCH": unix.SIGWINCH,
"XCPU": unix.SIGXCPU,
"XFSZ": unix.SIGXFSZ,
}

15
content/store_linux.go Normal file
View File

@ -0,0 +1,15 @@
package content
import (
"os"
"syscall"
"time"
)
func getStartTime(fi os.FileInfo) time.Time {
if st, ok := fi.Sys().(*syscall.Stat_t); ok {
return time.Unix(int64(st.Ctim.Sec), int64(st.Ctim.Nsec))
}
return fi.ModTime()
}

View File

@ -1,4 +1,4 @@
// +build linux // +build darwin freebsd
package content package content
@ -10,7 +10,7 @@ import (
func getStartTime(fi os.FileInfo) time.Time { func getStartTime(fi os.FileInfo) time.Time {
if st, ok := fi.Sys().(*syscall.Stat_t); ok { if st, ok := fi.Sys().(*syscall.Stat_t); ok {
return time.Unix(int64(st.Ctim.Sec), int64(st.Ctim.Nsec)) return time.Unix(int64(st.Ctimespec.Sec), int64(st.Ctimespec.Nsec))
} }
return fi.ModTime() return fi.ModTime()

65
fs/copy_unix.go Normal file
View File

@ -0,0 +1,65 @@
// +build darwin freebsd
package fs
import (
"io"
"os"
"syscall"
"github.com/containerd/continuity/sysx"
"github.com/pkg/errors"
)
func copyFileInfo(fi os.FileInfo, name string) error {
st := fi.Sys().(*syscall.Stat_t)
if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
return errors.Wrapf(err, "failed to chown %s", name)
}
if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
if err := os.Chmod(name, fi.Mode()); err != nil {
return errors.Wrapf(err, "failed to chmod %s", name)
}
}
if err := syscall.UtimesNano(name, []syscall.Timespec{st.Atimespec, st.Mtimespec}); err != nil {
return errors.Wrapf(err, "failed to utime %s", name)
}
return nil
}
func copyFileContent(dst, src *os.File) error {
buf := bufferPool.Get().([]byte)
_, err := io.CopyBuffer(dst, src, buf)
bufferPool.Put(buf)
return err
}
func copyXAttrs(dst, src string) error {
xattrKeys, err := sysx.LListxattr(src)
if err != nil {
return errors.Wrapf(err, "failed to list xattrs on %s", src)
}
for _, xattr := range xattrKeys {
data, err := sysx.LGetxattr(src, xattr)
if err != nil {
return errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
}
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
return errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
}
}
return nil
}
func copyDevice(dst string, fi os.FileInfo) error {
st, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return errors.New("unsupported stat type")
}
return syscall.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
}

View File

@ -1,3 +1,5 @@
// +build !windows
package fs package fs
import ( import (

View File

@ -27,7 +27,7 @@ func diskUsage(roots ...string) (Usage, error) {
} }
stat := fi.Sys().(*syscall.Stat_t) stat := fi.Sys().(*syscall.Stat_t)
inodes[inode{dev: stat.Dev, ino: stat.Ino}] = struct{}{} inodes[inode{dev: uint64(stat.Dev), ino: stat.Ino}] = struct{}{}
size += fi.Size() size += fi.Size()
return nil return nil
}); err != nil { }); err != nil {

70
mount_linux.go Normal file
View File

@ -0,0 +1,70 @@
package containerd
import (
"strings"
"golang.org/x/sys/unix"
)
func (m *Mount) Mount(target string) error {
flags, data := parseMountOptions(m.Options)
return unix.Mount(m.Source, target, m.Type, uintptr(flags), data)
}
func Unmount(mount string, flags int) error {
return unix.Unmount(mount, flags)
}
// parseMountOptions takes fstab style mount options and parses them for
// use with a standard mount() syscall
func parseMountOptions(options []string) (int, string) {
var (
flag int
data []string
)
flags := map[string]struct {
clear bool
flag int
}{
"async": {true, unix.MS_SYNCHRONOUS},
"atime": {true, unix.MS_NOATIME},
"bind": {false, unix.MS_BIND},
"defaults": {false, 0},
"dev": {true, unix.MS_NODEV},
"diratime": {true, unix.MS_NODIRATIME},
"dirsync": {false, unix.MS_DIRSYNC},
"exec": {true, unix.MS_NOEXEC},
"mand": {false, unix.MS_MANDLOCK},
"noatime": {false, unix.MS_NOATIME},
"nodev": {false, unix.MS_NODEV},
"nodiratime": {false, unix.MS_NODIRATIME},
"noexec": {false, unix.MS_NOEXEC},
"nomand": {true, unix.MS_MANDLOCK},
"norelatime": {true, unix.MS_RELATIME},
"nostrictatime": {true, unix.MS_STRICTATIME},
"nosuid": {false, unix.MS_NOSUID},
"rbind": {false, unix.MS_BIND | unix.MS_REC},
"relatime": {false, unix.MS_RELATIME},
"remount": {false, unix.MS_REMOUNT},
"ro": {false, unix.MS_RDONLY},
"rw": {true, unix.MS_RDONLY},
"strictatime": {false, unix.MS_STRICTATIME},
"suid": {true, unix.MS_NOSUID},
"sync": {false, unix.MS_SYNCHRONOUS},
}
for _, o := range options {
// If the option does not exist in the flags table or the flag
// is not supported on the platform,
// then it is a data value for a specific fs type
if f, exists := flags[o]; exists && f.flag != 0 {
if f.clear {
flag &^= f.flag
} else {
flag |= f.flag
}
} else {
data = append(data, o)
}
}
return flag, strings.Join(data, ",")
}

View File

@ -1,72 +1,17 @@
// +build linux // +build darwin freebsd
package containerd package containerd
import ( import "github.com/pkg/errors"
"strings"
"golang.org/x/sys/unix" var (
ErrNotImplementOnUnix = errors.New("not implemented under unix")
) )
func (m *Mount) Mount(target string) error { func (m *Mount) Mount(target string) error {
flags, data := parseMountOptions(m.Options) return ErrNotImplementOnUnix
return unix.Mount(m.Source, target, m.Type, uintptr(flags), data)
} }
func Unmount(mount string, flags int) error { func Unmount(mount string, flags int) error {
return unix.Unmount(mount, flags) return ErrNotImplementOnUnix
}
// parseMountOptions takes fstab style mount options and parses them for
// use with a standard mount() syscall
func parseMountOptions(options []string) (int, string) {
var (
flag int
data []string
)
flags := map[string]struct {
clear bool
flag int
}{
"async": {true, unix.MS_SYNCHRONOUS},
"atime": {true, unix.MS_NOATIME},
"bind": {false, unix.MS_BIND},
"defaults": {false, 0},
"dev": {true, unix.MS_NODEV},
"diratime": {true, unix.MS_NODIRATIME},
"dirsync": {false, unix.MS_DIRSYNC},
"exec": {true, unix.MS_NOEXEC},
"mand": {false, unix.MS_MANDLOCK},
"noatime": {false, unix.MS_NOATIME},
"nodev": {false, unix.MS_NODEV},
"nodiratime": {false, unix.MS_NODIRATIME},
"noexec": {false, unix.MS_NOEXEC},
"nomand": {true, unix.MS_MANDLOCK},
"norelatime": {true, unix.MS_RELATIME},
"nostrictatime": {true, unix.MS_STRICTATIME},
"nosuid": {false, unix.MS_NOSUID},
"rbind": {false, unix.MS_BIND | unix.MS_REC},
"relatime": {false, unix.MS_RELATIME},
"remount": {false, unix.MS_REMOUNT},
"ro": {false, unix.MS_RDONLY},
"rw": {true, unix.MS_RDONLY},
"strictatime": {false, unix.MS_STRICTATIME},
"suid": {true, unix.MS_NOSUID},
"sync": {false, unix.MS_SYNCHRONOUS},
}
for _, o := range options {
// If the option does not exist in the flags table or the flag
// is not supported on the platform,
// then it is a data value for a specific fs type
if f, exists := flags[o]; exists && f.flag != 0 {
if f.clear {
flag &^= f.flag
} else {
flag |= f.flag
}
} else {
data = append(data, o)
}
}
return flag, strings.Join(data, ",")
} }

View File

@ -20,7 +20,7 @@ github.com/containerd/btrfs e9c546f46bccffefe71a6bc137e4c21b5503cc18
github.com/stretchr/testify v1.1.4 github.com/stretchr/testify v1.1.4
github.com/davecgh/go-spew v1.1.0 github.com/davecgh/go-spew v1.1.0
github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib v1.0.0
github.com/tonistiigi/fifo fe870ccf293940774c2b44e23f6c71fff8f7547d github.com/tonistiigi/fifo f071cd4a2739654fec4e768a14efd9332b3e8af1
github.com/urfave/cli 8ba6f23b6e36d03666a14bd9421f5e3efcb59aca github.com/urfave/cli 8ba6f23b6e36d03666a14bd9421f5e3efcb59aca
golang.org/x/net 8b4af36cd21a1f85a7484b49feb7c79363106d8e golang.org/x/net 8b4af36cd21a1f85a7484b49feb7c79363106d8e
google.golang.org/grpc v1.0.5 google.golang.org/grpc v1.0.5

View File

@ -36,7 +36,7 @@ func getHandle(fn string) (*handle, error) {
h := &handle{ h := &handle{
f: f, f: f,
name: fn, name: fn,
dev: stat.Dev, dev: uint64(stat.Dev),
ino: stat.Ino, ino: stat.Ino,
} }
@ -62,7 +62,7 @@ func (h *handle) Path() (string, error) {
if err := syscall.Stat(h.procPath(), &stat); err != nil { if err := syscall.Stat(h.procPath(), &stat); err != nil {
return "", errors.Wrapf(err, "path %v could not be statted", h.procPath()) return "", errors.Wrapf(err, "path %v could not be statted", h.procPath())
} }
if stat.Dev != h.dev || stat.Ino != h.ino { if uint64(stat.Dev) != h.dev || stat.Ino != h.ino {
return "", errors.Errorf("failed to verify handle %v/%v %v/%v", stat.Dev, h.dev, stat.Ino, h.ino) return "", errors.Errorf("failed to verify handle %v/%v %v/%v", stat.Dev, h.dev, stat.Ino, h.ino)
} }
return h.procPath(), nil return h.procPath(), nil

View File

@ -23,7 +23,7 @@ func getHandle(fn string) (*handle, error) {
h := &handle{ h := &handle{
fn: fn, fn: fn,
dev: uint64(stat.Dev), dev: uint64(stat.Dev),
ino: stat.Ino, ino: uint64(stat.Ino),
} }
return h, nil return h, nil
@ -34,7 +34,7 @@ func (h *handle) Path() (string, error) {
if err := syscall.Stat(h.fn, &stat); err != nil { if err := syscall.Stat(h.fn, &stat); err != nil {
return "", errors.Wrapf(err, "path %v could not be statted", h.fn) return "", errors.Wrapf(err, "path %v could not be statted", h.fn)
} }
if uint64(stat.Dev) != h.dev || stat.Ino != h.ino { if uint64(stat.Dev) != h.dev || uint64(stat.Ino) != h.ino {
return "", errors.Errorf("failed to verify handle %v/%v %v/%v for %v", stat.Dev, h.dev, stat.Ino, h.ino, h.fn) return "", errors.Errorf("failed to verify handle %v/%v %v/%v for %v", stat.Dev, h.dev, stat.Ino, h.ino, h.fn)
} }
return h.fn, nil return h.fn, nil