Getting FreeBSD Compiling

This commit is contained in:
Kris Nova 2017-09-06 06:35:07 +08:00
parent 2c1089e6ec
commit ca2e851d87
17 changed files with 930 additions and 7 deletions

View File

@ -5,7 +5,18 @@ PROJECT=github.com/containerd/containerd
GIT_COMMIT := $(shell git rev-parse HEAD 2> /dev/null || true)
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2> /dev/null)
LDFLAGS := -X github.com/containerd/containerd.GitCommit=${GIT_COMMIT} ${LDFLAGS}
# --------------------------------------------------------------------
#
# epoll library flags for epoll_freebsd.go
# @(kris-nova)
#
#
CFLAGS=-I /usr/local/include/libepoll-shim ${CFLAGS}
LDFLAGS := -I ld -X github.com/containerd/containerd.GitCommit=${GIT_COMMIT} ${LDFLAGS}
#
#
# --------------------------------------------------------------------
TEST_TIMEOUT ?= 5m
TEST_SUITE_TIMEOUT ?= 10m

View File

@ -0,0 +1,43 @@
package server
// +build freebsd
import (
"fmt"
"github.com/containerd/containerd/api/grpc/types"
"github.com/containerd/containerd/specs"
"github.com/containerd/containerd/supervisor"
"golang.org/x/net/context"
)
var clockTicksPerSecond uint64
func (s *apiServer) AddProcess(ctx context.Context, r *types.AddProcessRequest) (*types.AddProcessResponse, error) {
process := &specs.ProcessSpec{
Terminal: r.Terminal,
Args: r.Args,
Env: r.Env,
Cwd: r.Cwd,
}
if r.Id == "" {
return nil, fmt.Errorf("container id cannot be empty")
}
if r.Pid == "" {
return nil, fmt.Errorf("process id cannot be empty")
}
e := &supervisor.AddProcessTask{}
e.ID = r.Id
e.PID = r.Pid
e.ProcessSpec = process
e.Stdin = r.Stdin
e.Stdout = r.Stdout
e.Stderr = r.Stderr
e.StartResponse = make(chan supervisor.StartResponse, 1)
s.sv.SendTask(e)
if err := <-e.ErrorCh(); err != nil {
return nil, err
}
<-e.StartResponse
return &types.AddProcessResponse{}, nil
}

126
archutils/epoll_freebsd.go Normal file
View File

@ -0,0 +1,126 @@
// +build freebsd
// -----------------------------------------------------------------------------------------
//
// (@kris-nova)
//
// Probably most of my god awful hacking is done in this file, most of this is in place to
// hack around the Go standard library since we are using a Linux package in FreeBSD.. we
// need to have certain constants defined that usually are only found based on $GOOS,.
//
// For more information or just to yell at me shoot me a line at kris@nivenly.com
//
package archutils
// #cgo CFLAGS: -I/usr/local/include/libepoll-shim
// #cgo LDFLAGS: -L/usr/local/lib -Ild -lepoll-shim -lrt
// #include <sys/epoll.h>
/*
int EpollCreate1(int flag) {
return epoll_create1(flag);
}
int EpollCtl(int efd, int op,int sfd, int events, int fd) {
struct epoll_event event;
event.events = events;
event.data.fd = fd;
return epoll_ctl(efd, op, sfd, &event);
}
struct event_t {
uint32_t events;
int fd;
};
struct epoll_event events[128];
int run_epoll_wait(int fd, struct event_t *event) {
int n, i;
n = epoll_wait(fd, events, 128, -1);
for (i = 0; i < n; i++) {
event[i].events = events[i].events;
event[i].fd = events[i].data.fd;
}
return n;
}
*/
import "C"
import (
"fmt"
"unsafe"
)
// EpollCreate1 calls a C implementation
func EpollCreate1(flag int) (int, error) {
fd := int(C.EpollCreate1(C.int(flag)))
if fd < 0 {
return fd, fmt.Errorf("failed to create epoll, errno is %d", fd)
}
return fd, nil
}
type FreeBSDEpollEventInterface interface {
}
type FreeBSDEpollEvent struct {
Events uint32
Fd int32
Pad int32
}
const (
// -----------------------------------------------------
// Hacking in control constants for FreeBSD Epoll port
// Note: these are not defined in the Go standard library
// so we define them here manually. Once the constants make it
// to the epoll.h file, the declaration in Go shouldn't matter.
//
// (@kris-nova)
//
FREEBSD_EPOLL_CTL_ADD = 0x1
FREEBSD_EPOLL_CLOEXEC = 0x80000
FREEBSD_EPOLL_CTL_DEL = 0x2
FREEBSD_EPOLLHUP = 0x10
FREEBSD_EPOLLIN = 0x1
FREEBSD_SYS_EPOLL_CTL = 233
)
// EpollCtl is a another hack to get the sys call running without having a dependency on linux
//func EpollCtl(epfd int, op int, fd int, eventInterface FreeBSDEpollEventInterface) (err error) {
// event := eventInterface.(*FreeBSDEpollEvent)
// _, _, e1 := syscall.RawSyscall6(FREEBSD_SYS_EPOLL_CTL, uintptr(epfd), uintptr(op), uintptr(fd), uintptr(unsafe.Pointer(event)), 0, 0)
// if e1 != 0 {=
// err = e1
// }
// return
//}
// EpollCtl calls a C implementation
func EpollCtl(epfd int, op int, fd int, eventInterface FreeBSDEpollEventInterface) error {
event := eventInterface.(*FreeBSDEpollEvent)
errno := C.EpollCtl(C.int(epfd), C.int(FREEBSD_EPOLL_CTL_ADD), C.int(fd), C.int(event.Events), C.int(event.Fd))
if errno < 0 {
return fmt.Errorf("Failed to ctl epoll")
}
return nil
}
// EpollWait calls a C implementation
func EpollWait(epfd int, events []FreeBSDEpollEvent, msec int) (int, error) {
//var events []FreeBSDEpollEvent
//for _, e := range eventInterfaces {
// events = append(events, e.(FreeBSDEpollEvent))
//}
var c_events [128]C.struct_event_t
n := int(C.run_epoll_wait(C.int(epfd), (*C.struct_event_t)(unsafe.Pointer(&c_events))))
if n < 0 {
return int(n), fmt.Errorf("Failed to wait epoll")
}
for i := 0; i < n; i++ {
events[i].Fd = int32(c_events[i].fd)
events[i].Events = uint32(c_events[i].events)
}
return int(n), nil
}

View File

@ -39,11 +39,15 @@ func ioctl(fd uintptr, flag, data uintptr) error {
return nil
}
const (
FREEBSD_TIOCSPTLCK = 0x40045431
)
// 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)))
return ioctl(f.Fd(), FREEBSD_TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
}
// ptsname retrieves the name of the first available pts for the given master.

View File

@ -0,0 +1,190 @@
// +build freebsd
package main
import (
"fmt"
"io"
"os/exec"
"syscall"
"time"
"github.com/containerd/console"
"github.com/containerd/containerd/osutils"
runc "github.com/containerd/go-runc"
"github.com/tonistiigi/fifo"
"golang.org/x/net/context"
)
// openIO opens the pre-created fifo's for use with the container
// in RDWR so that they remain open if the other side stops listening
func (p *process) openIO() error {
p.stdio = &stdio{}
var (
uid = p.state.RootUID
gid = p.state.RootGID
)
ctx, _ := context.WithTimeout(context.Background(), 15*time.Second)
stdinCloser, err := fifo.OpenFifo(ctx, p.state.Stdin, syscall.O_WRONLY|syscall.O_NONBLOCK, 0)
if err != nil {
return err
}
p.stdinCloser = stdinCloser
if p.state.Terminal {
socket, err := runc.NewTempConsoleSocket()
if err != nil {
return err
}
p.socket = socket
consoleCh := p.waitConsole(socket)
stdin, err := fifo.OpenFifo(ctx, p.state.Stdin, syscall.O_RDONLY, 0)
if err != nil {
return err
}
stdoutw, err := fifo.OpenFifo(ctx, p.state.Stdout, syscall.O_WRONLY, 0)
if err != nil {
return err
}
stdoutr, err := fifo.OpenFifo(ctx, p.state.Stdout, syscall.O_RDONLY, 0)
if err != nil {
return err
}
// open the fifos but wait until we receive the console before we start
// copying data back and forth between the two
go p.setConsole(consoleCh, stdin, stdoutw)
p.Add(1)
p.ioCleanupFn = func() {
stdoutr.Close()
stdoutw.Close()
}
return nil
}
close(p.consoleErrCh)
i, err := p.initializeIO(uid, gid)
if err != nil {
return err
}
p.shimIO = i
// non-tty
ioClosers := []io.Closer{}
for _, pair := range []struct {
name string
dest func(wc io.WriteCloser, rc io.Closer)
}{
{
p.state.Stdout,
func(wc io.WriteCloser, rc io.Closer) {
p.Add(1)
go func() {
io.Copy(wc, i.Stdout)
p.Done()
}()
},
},
{
p.state.Stderr,
func(wc io.WriteCloser, rc io.Closer) {
p.Add(1)
go func() {
io.Copy(wc, i.Stderr)
p.Done()
}()
},
},
} {
fw, err := fifo.OpenFifo(ctx, pair.name, syscall.O_WRONLY, 0)
if err != nil {
return fmt.Errorf("containerd-shim: opening %s failed: %s", pair.name, err)
}
fr, err := fifo.OpenFifo(ctx, pair.name, syscall.O_RDONLY, 0)
if err != nil {
return fmt.Errorf("containerd-shim: opening %s failed: %s", pair.name, err)
}
pair.dest(fw, fr)
ioClosers = append(ioClosers, fw, fr)
}
f, err := fifo.OpenFifo(ctx, p.state.Stdin, syscall.O_RDONLY, 0)
if err != nil {
return fmt.Errorf("containerd-shim: opening %s failed: %s", p.state.Stdin, err)
}
p.ioCleanupFn = func() {
for _, c := range ioClosers {
c.Close()
}
}
go func() {
io.Copy(i.Stdin, f)
i.Stdin.Close()
f.Close()
}()
return nil
}
func (p *process) Wait() {
p.WaitGroup.Wait()
if p.ioCleanupFn != nil {
p.ioCleanupFn()
}
if p.console != nil {
p.console.Close()
}
}
func (p *process) killAll() error {
if !p.state.Exec {
cmd := exec.Command(p.runtime, append(p.state.RuntimeArgs, "kill", "--all", p.id, "SIGKILL")...)
cmd.SysProcAttr = osutils.SetPDeathSig()
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("%s: %v", out, err)
}
}
return nil
}
func (p *process) setConsole(c <-chan *consoleR, stdin io.Reader, stdout io.Writer) {
r := <-c
if r.err != nil {
p.consoleErrCh <- r.err
return
}
close(p.consoleErrCh)
p.console = r.c
// copy from the console into the provided fifos
go io.Copy(r.c, stdin)
go func() {
io.Copy(stdout, r.c)
p.Done()
}()
}
type consoleR struct {
c console.Console
err error
}
func (p *process) waitConsole(socket *runc.Socket) <-chan *consoleR {
c := make(chan *consoleR, 1)
go func() {
master, err := socket.ReceiveMaster()
socket.Close()
if err != nil {
c <- &consoleR{
err: err,
}
return
}
c <- &consoleR{
c: master,
}
}()
return c
}

View File

@ -0,0 +1,6 @@
package main
// +build freebsd
func processMetrics() {
}

View File

@ -0,0 +1,15 @@
// +build freebsd
package osutils
import (
"syscall"
)
// SetPDeathSig sets the parent death signal to SIGKILL so that if the
// shim dies the container process also dies.
func SetPDeathSig() *syscall.SysProcAttr {
return &syscall.SysProcAttr{
//Pdeathsig: syscall.SIGKILL,
}
}

17
osutils/prctl_freebsd.go Normal file
View File

@ -0,0 +1,17 @@
// +build freebsd
package osutils
import (
"errors"
)
// GetSubreaper returns the subreaper setting for the calling process
func GetSubreaper() (int, error) {
return 0, errors.New("osutils GetSubreaper not implemented on FreeBSD")
}
// SetSubreaper sets the value i as the subreaper setting for the calling process
func SetSubreaper(i int) error {
return nil
}

View File

@ -0,0 +1,192 @@
package runtime
// +build freebsd
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"github.com/containerd/containerd/specs"
ocs "github.com/opencontainers/runtime-spec/specs-go"
)
func findCgroupMountpointAndRoot(pid int, subsystem string) (string, string, error) {
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
if err != nil {
return "", "", err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
txt := scanner.Text()
fields := strings.Split(txt, " ")
for _, opt := range strings.Split(fields[len(fields)-1], ",") {
if opt == subsystem {
return fields[4], fields[3], nil
}
}
}
if err := scanner.Err(); err != nil {
return "", "", err
}
return "", "", fmt.Errorf("cgroup path for %s not found", subsystem)
}
func parseCgroupFile(path string) (map[string]string, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
s := bufio.NewScanner(f)
cgroups := make(map[string]string)
for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
text := s.Text()
parts := strings.Split(text, ":")
for _, subs := range strings.Split(parts[1], ",") {
cgroups[subs] = parts[2]
}
}
return cgroups, nil
}
func (c *container) OOM() (OOM, error) {
p := c.processes[InitProcessID]
if p == nil {
return nil, fmt.Errorf("no init process found")
}
mountpoint, hostRoot, err := findCgroupMountpointAndRoot(os.Getpid(), "memory")
if err != nil {
return nil, err
}
cgroups, err := parseCgroupFile(fmt.Sprintf("/proc/%d/cgroup", p.pid))
if err != nil {
return nil, err
}
root, ok := cgroups["memory"]
if !ok {
return nil, fmt.Errorf("no memory cgroup for container %s", c.ID())
}
// Take care of the case were we're running inside a container
// ourself
root = strings.TrimPrefix(root, hostRoot)
return c.getMemoryEventFD(filepath.Join(mountpoint, root))
}
func (c *container) Pids() ([]int, error) {
var pids []int
args := c.runtimeArgs
args = append(args, "ps", "--format=json", c.id)
out, err := exec.Command(c.runtime, args...).CombinedOutput()
if err != nil {
return nil, fmt.Errorf("%s: %q", err.Error(), out)
}
if err := json.Unmarshal(out, &pids); err != nil {
return nil, err
}
return pids, nil
}
func u64Ptr(i uint64) *uint64 { return &i }
func i64Ptr(i int64) *int64 { return &i }
func (c *container) UpdateResources(r *Resource) error {
sr := ocs.LinuxResources{
Memory: &ocs.LinuxMemory{
Limit: u64Ptr(uint64(r.Memory)),
Reservation: u64Ptr(uint64(r.MemoryReservation)),
Swap: u64Ptr(uint64(r.MemorySwap)),
Kernel: u64Ptr(uint64(r.KernelMemory)),
KernelTCP: u64Ptr(uint64(r.KernelTCPMemory)),
},
CPU: &ocs.LinuxCPU{
Shares: u64Ptr(uint64(r.CPUShares)),
Quota: i64Ptr(int64(r.CPUQuota)),
Period: u64Ptr(uint64(r.CPUPeriod)),
Cpus: r.CpusetCpus,
Mems: r.CpusetMems,
},
BlockIO: &ocs.LinuxBlockIO{
Weight: &r.BlkioWeight,
},
Pids: &ocs.LinuxPids{
Limit: r.PidsLimit,
},
}
srStr := bytes.NewBuffer(nil)
if err := json.NewEncoder(srStr).Encode(&sr); err != nil {
return err
}
args := c.runtimeArgs
args = append(args, "update", "-r", "-", c.id)
cmd := exec.Command(c.runtime, args...)
cmd.Stdin = srStr
b, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf(string(b))
}
return nil
}
func getRootIDs(s *specs.Spec) (int, int, error) {
if s == nil {
return 0, 0, nil
}
var hasUserns bool
for _, ns := range s.Linux.Namespaces {
if ns.Type == ocs.UserNamespace {
hasUserns = true
break
}
}
if !hasUserns {
return 0, 0, nil
}
uid := hostIDFromMap(0, s.Linux.UIDMappings)
gid := hostIDFromMap(0, s.Linux.GIDMappings)
return uid, gid, nil
}
func (c *container) getMemoryEventFD(root string) (*oom, error) {
f, err := os.Open(filepath.Join(root, "memory.oom_control"))
if err != nil {
return nil, err
}
defer f.Close()
fd, _, serr := syscall.RawSyscall(syscall.SYS_KEVENT, 0, syscall.FD_CLOEXEC, 0)
if serr != 0 {
return nil, serr
}
if err := c.writeEventFD(root, int(f.Fd()), int(fd)); err != nil {
syscall.Close(int(fd))
return nil, err
}
return &oom{
root: root,
id: c.id,
eventfd: int(fd),
}, nil
}

View File

@ -0,0 +1,22 @@
// +build freebsd
package runtime
import (
"io/ioutil"
"path/filepath"
"strconv"
)
func (p *process) getPidFromFile() (int, error) {
data, err := ioutil.ReadFile(filepath.Join(p.root, "pid"))
if err != nil {
return -1, err
}
i, err := strconv.Atoi(string(data))
if err != nil {
return -1, errInvalidPidInt
}
p.pid = i
return i, nil
}

View File

@ -0,0 +1,149 @@
// +build freebsd
package supervisor
import (
"sync"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/containerd/containerd/archutils"
"github.com/containerd/containerd/runtime"
)
// NewMonitor starts a new process monitor and returns it
func NewMonitor() (*Monitor, error) {
m := &Monitor{
receivers: make(map[int]interface{}),
exits: make(chan runtime.Process, 1024),
ooms: make(chan string, 1024),
}
fd, err := archutils.EpollCreate1(archutils.FREEBSD_EPOLL_CLOEXEC)
if err != nil {
return nil, err
}
m.epollFd = fd
go m.start()
return m, nil
}
// Monitor represents a runtime.Process monitor
type Monitor struct {
m sync.Mutex
receivers map[int]interface{}
exits chan runtime.Process
ooms chan string
epollFd int
}
// Exits returns the channel used to notify of a process exit
func (m *Monitor) Exits() chan runtime.Process {
return m.exits
}
// OOMs returns the channel used to notify of a container exit due to OOM
func (m *Monitor) OOMs() chan string {
return m.ooms
}
// Monitor adds a process to the list of the one being monitored
func (m *Monitor) Monitor(p runtime.Process) error {
m.m.Lock()
defer m.m.Unlock()
fd := p.ExitFD()
event := archutils.FreeBSDEpollEvent{
Fd: int32(fd),
Events: archutils.FREEBSD_EPOLLHUP,
}
if err := archutils.EpollCtl(m.epollFd, archutils.FREEBSD_EPOLL_CTL_ADD, fd, &event); err != nil {
return err
}
EpollFdCounter.Inc(1)
m.receivers[fd] = p
return nil
}
// MonitorOOM adds a container to the list of the ones monitored for OOM
func (m *Monitor) MonitorOOM(c runtime.Container) error {
m.m.Lock()
defer m.m.Unlock()
o, err := c.OOM()
if err != nil {
return err
}
fd := o.FD()
event := archutils.FreeBSDEpollEvent{
Fd: int32(fd),
Events: archutils.FREEBSD_EPOLLHUP | archutils.FREEBSD_EPOLLIN,
}
if err := archutils.EpollCtl(m.epollFd, archutils.FREEBSD_EPOLL_CTL_ADD, fd, &event); err != nil {
return err
}
EpollFdCounter.Inc(1)
m.receivers[fd] = o
return nil
}
// Close cleans up resources allocated by NewMonitor()
func (m *Monitor) Close() error {
return syscall.Close(m.epollFd)
}
func (m *Monitor) processEvent(fd int, event uint32) {
m.m.Lock()
r := m.receivers[fd]
switch t := r.(type) {
case runtime.Process:
if event == archutils.FREEBSD_EPOLLHUP {
delete(m.receivers, fd)
if err := archutils.EpollCtl(m.epollFd, archutils.FREEBSD_EPOLL_CTL_DEL, fd, &archutils.FreeBSDEpollEvent{
Events: archutils.FREEBSD_EPOLLHUP,
Fd: int32(fd),
}); err != nil {
logrus.WithField("error", err).Error("containerd: epoll remove fd")
}
if err := t.Close(); err != nil {
logrus.WithField("error", err).Error("containerd: close process IO")
}
EpollFdCounter.Dec(1)
// defer until lock is released
defer func() {
m.exits <- t
}()
}
case runtime.OOM:
// always flush the event fd
t.Flush()
if t.Removed() {
delete(m.receivers, fd)
// epoll will remove the fd from its set after it has been closed
t.Close()
EpollFdCounter.Dec(1)
} else {
// defer until lock is released
defer func() {
m.ooms <- t.ContainerID()
}()
}
}
// This cannot be a defer to avoid a deadlock in case the channels
// above get full
m.m.Unlock()
}
func (m *Monitor) start() {
var events [128]archutils.FreeBSDEpollEvent
for {
n, err := archutils.EpollWait(m.epollFd, events[:], -1)
if err != nil {
if err == syscall.EINTR {
continue
}
logrus.WithField("error", err).Fatal("containerd: epoll wait")
}
// process events
for i := 0; i < n; i++ {
m.processEvent(int(events[i].Fd), events[i].Events)
}
}
}

View File

@ -0,0 +1,52 @@
// -------------------------------------------------------
// FreeBSD stub for compiling sigar
// @(kris-nova)
//
// Adding out stubs here to get sigar compiling without an
// implementation, this patch should handle getting docker
// working, and the Go compiler happy and NOTHING more.
// +build freebsd
package sigar
import "syscall"
func (self *FileSystemUsage) Get(path string) error {
stat := syscall.Statfs_t{}
err := syscall.Statfs(path, &stat)
if err != nil {
return err
}
bsize := stat.Bsize / 512
self.Total = (uint64(stat.Blocks) * uint64(bsize)) >> 1
self.Free = (uint64(stat.Bfree) * uint64(bsize)) >> 1
self.Avail = (uint64(stat.Bavail) * uint64(bsize)) >> 1
self.Used = self.Total - self.Free
self.Files = stat.Files
self.FreeFiles = uint64(stat.Ffree)
return nil
}
func (self *Cpu) Get() error {
return nil
}
func (self *Mem) Get() error {
return nil
}
func (self *Swap) Get() error {
return nil
}
func (self *LoadAverage) Get() error {
return nil
}
func (self *CpuList) Get() error {
return nil
}

View File

@ -1,6 +1,6 @@
// Copyright (c) 2012 VMware, Inc.
// +build darwin freebsd linux netbsd openbsd
// +build darwin linux netbsd openbsd
package sigar
@ -23,4 +23,4 @@ func (self *FileSystemUsage) Get(path string) error {
self.FreeFiles = stat.Ffree
return nil
}
}

View File

@ -531,7 +531,7 @@ func (r *Runc) command(context context.Context, args ...string) *exec.Cmd {
cmd := exec.CommandContext(context, command, append(r.args(), args...)...)
if r.PdeathSignal != 0 {
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: r.PdeathSignal,
//Pdeathsig: r.PdeathSignal,
}
}
return cmd

View File

@ -0,0 +1,95 @@
// +build freebsd
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

@ -124,3 +124,4 @@ func Annotations(labels []string) (bundle string, userAnnotations map[string]str
func GetIntSize() int {
return int(unsafe.Sizeof(1))
}

View File

@ -23,7 +23,7 @@ func getHandle(fn string) (*handle, error) {
h := &handle{
fn: fn,
dev: uint64(stat.Dev),
ino: stat.Ino,
ino: uint64(stat.Ino),
}
return h, nil
@ -34,7 +34,7 @@ func (h *handle) Path() (string, error) {
if err := syscall.Stat(h.fn, &stat); err != nil {
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", stat.Dev, h.dev, stat.Ino, h.ino)
}
return h.fn, nil