Skip to content

Commit

Permalink
Merge pull request #1240 from dfr/freebsd-unshare
Browse files Browse the repository at this point in the history
Limited support for pkg/unshare on FreeBSD
  • Loading branch information
rhatdan committed May 13, 2022
2 parents eb4f1bd + b193f00 commit 877a26a
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 29 deletions.
2 changes: 1 addition & 1 deletion pkg/unshare/unshare.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#ifndef UNSHARE_NO_CODE_AT_ALL
#if !defined(UNSHARE_NO_CODE_AT_ALL) && defined(__linux__)

#define _GNU_SOURCE
#include <sys/types.h>
Expand Down
24 changes: 9 additions & 15 deletions pkg/unshare/unshare.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"sync"

"github.com/pkg/errors"
"github.com/syndtr/gocapability/capability"
"github.com/sirupsen/logrus"
)

var (
Expand Down Expand Up @@ -38,19 +38,13 @@ func HomeDir() (string, error) {
return homeDir, homeDirErr
}

// HasCapSysAdmin returns whether the current process has CAP_SYS_ADMIN.
func HasCapSysAdmin() (bool, error) {
hasCapSysAdminOnce.Do(func() {
currentCaps, err := capability.NewPid2(0)
if err != nil {
hasCapSysAdminErr = err
return
}
if err = currentCaps.Load(); err != nil {
hasCapSysAdminErr = err
return
func bailOnError(err error, format string, a ...interface{}) { // nolint: golint,goprintffuncname
if err != nil {
if format != "" {
logrus.Errorf("%s: %v", fmt.Sprintf(format, a...), err)
} else {
logrus.Errorf("%v", err)
}
hasCapSysAdminRet = currentCaps.Get(capability.EFFECTIVE, capability.CAP_SYS_ADMIN)
})
return hasCapSysAdminRet, hasCapSysAdminErr
os.Exit(1)
}
}
3 changes: 2 additions & 1 deletion pkg/unshare/unshare_cgo.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// +build linux,cgo,!gccgo
//go:build (linux && cgo && !gccgo) || (freebsd && cgo)
// +build linux,cgo,!gccgo freebsd,cgo

package unshare

Expand Down
76 changes: 76 additions & 0 deletions pkg/unshare/unshare_freebsd.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#if !defined(UNSHARE_NO_CODE_AT_ALL) && defined(__FreeBSD__)


#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

static int _containers_unshare_parse_envint(const char *envname) {
char *p, *q;
long l;

p = getenv(envname);
if (p == NULL) {
return -1;
}
q = NULL;
l = strtol(p, &q, 10);
if ((q == NULL) || (*q != '\0')) {
fprintf(stderr, "Error parsing \"%s\"=\"%s\"!\n", envname, p);
_exit(1);
}
unsetenv(envname);
return l;
}

void _containers_unshare(void)
{
int pidfd, continuefd, n, pgrp, sid, ctty;
char buf[2048];

pidfd = _containers_unshare_parse_envint("_Containers-pid-pipe");
if (pidfd != -1) {
snprintf(buf, sizeof(buf), "%llu", (unsigned long long) getpid());
size_t size = write(pidfd, buf, strlen(buf));
if (size != strlen(buf)) {
fprintf(stderr, "Error writing PID to pipe on fd %d: %m\n", pidfd);
_exit(1);
}
close(pidfd);
}
continuefd = _containers_unshare_parse_envint("_Containers-continue-pipe");
if (continuefd != -1) {
n = read(continuefd, buf, sizeof(buf));
if (n > 0) {
fprintf(stderr, "Error: %.*s\n", n, buf);
_exit(1);
}
close(continuefd);
}
sid = _containers_unshare_parse_envint("_Containers-setsid");
if (sid == 1) {
if (setsid() == -1) {
fprintf(stderr, "Error during setsid: %m\n");
_exit(1);
}
}
pgrp = _containers_unshare_parse_envint("_Containers-setpgrp");
if (pgrp == 1) {
if (setpgrp(0, 0) == -1) {
fprintf(stderr, "Error during setpgrp: %m\n");
_exit(1);
}
}
ctty = _containers_unshare_parse_envint("_Containers-ctty");
if (ctty != -1) {
if (ioctl(ctty, TIOCSCTTY, 0) == -1) {
fprintf(stderr, "Error while setting controlling terminal to %d: %m\n", ctty);
_exit(1);
}
}
}

#endif
179 changes: 179 additions & 0 deletions pkg/unshare/unshare_freebsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
//go:build freebsd
// +build freebsd

package unshare

import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"runtime"
"strconv"
"syscall"

"github.com/containers/storage/pkg/reexec"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

// Cmd wraps an exec.Cmd created by the reexec package in unshare(),
// and one day might handle setting ID maps and other related setting*s
// by triggering initialization code in the child.
type Cmd struct {
*exec.Cmd
Setsid bool
Setpgrp bool
Ctty *os.File
Hook func(pid int) error
}

// Command creates a new Cmd which can be customized.
func Command(args ...string) *Cmd {
cmd := reexec.Command(args...)
return &Cmd{
Cmd: cmd,
}
}

func (c *Cmd) Start() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()

// Set environment variables to tell the child to synchronize its startup.
if c.Env == nil {
c.Env = os.Environ()
}

// Create the pipe for reading the child's PID.
pidRead, pidWrite, err := os.Pipe()
if err != nil {
return errors.Wrapf(err, "error creating pid pipe")
}
c.Env = append(c.Env, fmt.Sprintf("_Containers-pid-pipe=%d", len(c.ExtraFiles)+3))
c.ExtraFiles = append(c.ExtraFiles, pidWrite)

// Create the pipe for letting the child know to proceed.
continueRead, continueWrite, err := os.Pipe()
if err != nil {
pidRead.Close()
pidWrite.Close()
return errors.Wrapf(err, "error creating pid pipe")
}
c.Env = append(c.Env, fmt.Sprintf("_Containers-continue-pipe=%d", len(c.ExtraFiles)+3))
c.ExtraFiles = append(c.ExtraFiles, continueRead)

// Pass along other instructions.
if c.Setsid {
c.Env = append(c.Env, "_Containers-setsid=1")
}
if c.Setpgrp {
c.Env = append(c.Env, "_Containers-setpgrp=1")
}
if c.Ctty != nil {
c.Env = append(c.Env, fmt.Sprintf("_Containers-ctty=%d", len(c.ExtraFiles)+3))
c.ExtraFiles = append(c.ExtraFiles, c.Ctty)
}

// Make sure we clean up our pipes.
defer func() {
if pidRead != nil {
pidRead.Close()
}
if pidWrite != nil {
pidWrite.Close()
}
if continueRead != nil {
continueRead.Close()
}
if continueWrite != nil {
continueWrite.Close()
}
}()

// Start the new process.
err = c.Cmd.Start()
if err != nil {
return err
}

// Close the ends of the pipes that the parent doesn't need.
continueRead.Close()
continueRead = nil
pidWrite.Close()
pidWrite = nil

// Read the child's PID from the pipe.
pidString := ""
b := new(bytes.Buffer)
if _, err := io.Copy(b, pidRead); err != nil {
return errors.Wrapf(err, "Reading child PID")
}
pidString = b.String()
pid, err := strconv.Atoi(pidString)
if err != nil {
fmt.Fprintf(continueWrite, "error parsing PID %q: %v", pidString, err)
return errors.Wrapf(err, "error parsing PID %q", pidString)
}

// Run any additional setup that we want to do before the child starts running proper.
if c.Hook != nil {
if err = c.Hook(pid); err != nil {
fmt.Fprintf(continueWrite, "hook error: %v", err)
return err
}
}

return nil
}

func (c *Cmd) Run() error {
if err := c.Start(); err != nil {
return err
}
return c.Wait()
}

func (c *Cmd) CombinedOutput() ([]byte, error) {
return nil, errors.New("unshare: CombinedOutput() not implemented")
}

func (c *Cmd) Output() ([]byte, error) {
return nil, errors.New("unshare: Output() not implemented")
}

type Runnable interface {
Run() error
}

// ExecRunnable runs the specified unshare command, captures its exit status,
// and exits with the same status.
func ExecRunnable(cmd Runnable, cleanup func()) {
exit := func(status int) {
if cleanup != nil {
cleanup()
}
os.Exit(status)
}
if err := cmd.Run(); err != nil {
if exitError, ok := errors.Cause(err).(*exec.ExitError); ok {
if exitError.ProcessState.Exited() {
if waitStatus, ok := exitError.ProcessState.Sys().(syscall.WaitStatus); ok {
if waitStatus.Exited() {
logrus.Debugf("%v", exitError)
exit(waitStatus.ExitStatus())
}
if waitStatus.Signaled() {
logrus.Debugf("%v", exitError)
exit(int(waitStatus.Signal()) + 128)
}
}
}
}
logrus.Errorf("%v", err)
logrus.Errorf("(Unable to determine exit status)")
exit(1)
}
exit(0)
}
28 changes: 17 additions & 11 deletions pkg/unshare/unshare_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,17 +414,6 @@ type Runnable interface {
Run() error
}

func bailOnError(err error, format string, a ...interface{}) { // nolint: golint,goprintffuncname
if err != nil {
if format != "" {
logrus.Errorf("%s: %v", fmt.Sprintf(format, a...), err)
} else {
logrus.Errorf("%v", err)
}
os.Exit(1)
}
}

// MaybeReexecUsingUserNamespace re-exec the process in a new namespace
func MaybeReexecUsingUserNamespace(evenForRoot bool) {
// If we've already been through this once, no need to try again.
Expand Down Expand Up @@ -674,3 +663,20 @@ func ParseIDMappings(uidmap, gidmap []string) ([]idtools.IDMap, []idtools.IDMap,
}
return uid, gid, nil
}

// HasCapSysAdmin returns whether the current process has CAP_SYS_ADMIN.
func HasCapSysAdmin() (bool, error) {
hasCapSysAdminOnce.Do(func() {
currentCaps, err := capability.NewPid2(0)
if err != nil {
hasCapSysAdminErr = err
return
}
if err = currentCaps.Load(); err != nil {
hasCapSysAdminErr = err
return
}
hasCapSysAdminRet = currentCaps.Get(capability.EFFECTIVE, capability.CAP_SYS_ADMIN)
})
return hasCapSysAdminRet, hasCapSysAdminErr
}
6 changes: 6 additions & 0 deletions pkg/unshare/unshare_unsupported.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build !linux
// +build !linux

package unshare
Expand Down Expand Up @@ -43,3 +44,8 @@ func GetHostIDMappings(pid string) ([]specs.LinuxIDMapping, []specs.LinuxIDMappi
func ParseIDMappings(uidmap, gidmap []string) ([]idtools.IDMap, []idtools.IDMap, error) {
return nil, nil, nil
}

// HasCapSysAdmin returns whether the current process has CAP_SYS_ADMIN.
func HasCapSysAdmin() (bool, error) {
return os.Geteuid() == 0, nil
}
3 changes: 2 additions & 1 deletion pkg/unshare/unshare_unsupported_cgo.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// +build !linux,cgo
//go:build cgo && !(linux || freebsd)
// +build cgo,!linux,!freebsd

package unshare

Expand Down

0 comments on commit 877a26a

Please sign in to comment.