Limited support for pkg/unshare on FreeBSD
This does not really 'unshare' anything but does implement the setsid, setpgrp and ctty options as well as the pipe-based signalling protocol with the parent process which made it slightly easier to port buildah's chroot isolation mechanism. Signed-off-by: Doug Rabson <dfr@rabson.org>
This commit is contained in:
parent
b0a112f5f2
commit
b193f00d94
|
|
@ -1,4 +1,4 @@
|
||||||
#ifndef UNSHARE_NO_CODE_AT_ALL
|
#if !defined(UNSHARE_NO_CODE_AT_ALL) && defined(__linux__)
|
||||||
|
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/syndtr/gocapability/capability"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -38,19 +38,13 @@ func HomeDir() (string, error) {
|
||||||
return homeDir, homeDirErr
|
return homeDir, homeDirErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasCapSysAdmin returns whether the current process has CAP_SYS_ADMIN.
|
func bailOnError(err error, format string, a ...interface{}) { // nolint: golint,goprintffuncname
|
||||||
func HasCapSysAdmin() (bool, error) {
|
if err != nil {
|
||||||
hasCapSysAdminOnce.Do(func() {
|
if format != "" {
|
||||||
currentCaps, err := capability.NewPid2(0)
|
logrus.Errorf("%s: %v", fmt.Sprintf(format, a...), err)
|
||||||
if err != nil {
|
} else {
|
||||||
hasCapSysAdminErr = err
|
logrus.Errorf("%v", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if err = currentCaps.Load(); err != nil {
|
os.Exit(1)
|
||||||
hasCapSysAdminErr = err
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
hasCapSysAdminRet = currentCaps.Get(capability.EFFECTIVE, capability.CAP_SYS_ADMIN)
|
|
||||||
})
|
|
||||||
return hasCapSysAdminRet, hasCapSysAdminErr
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// +build linux,cgo,!gccgo
|
//go:build (linux && cgo && !gccgo) || (freebsd && cgo)
|
||||||
|
// +build linux,cgo,!gccgo freebsd,cgo
|
||||||
|
|
||||||
package unshare
|
package unshare
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -414,17 +414,6 @@ type Runnable interface {
|
||||||
Run() error
|
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
|
// MaybeReexecUsingUserNamespace re-exec the process in a new namespace
|
||||||
func MaybeReexecUsingUserNamespace(evenForRoot bool) {
|
func MaybeReexecUsingUserNamespace(evenForRoot bool) {
|
||||||
// If we've already been through this once, no need to try again.
|
// If we've already been through this once, no need to try again.
|
||||||
|
|
@ -674,3 +663,20 @@ func ParseIDMappings(uidmap, gidmap []string) ([]idtools.IDMap, []idtools.IDMap,
|
||||||
}
|
}
|
||||||
return uid, gid, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build !linux
|
||||||
// +build !linux
|
// +build !linux
|
||||||
|
|
||||||
package unshare
|
package unshare
|
||||||
|
|
@ -43,3 +44,8 @@ func GetHostIDMappings(pid string) ([]specs.LinuxIDMapping, []specs.LinuxIDMappi
|
||||||
func ParseIDMappings(uidmap, gidmap []string) ([]idtools.IDMap, []idtools.IDMap, error) {
|
func ParseIDMappings(uidmap, gidmap []string) ([]idtools.IDMap, []idtools.IDMap, error) {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasCapSysAdmin returns whether the current process has CAP_SYS_ADMIN.
|
||||||
|
func HasCapSysAdmin() (bool, error) {
|
||||||
|
return os.Geteuid() == 0, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// +build !linux,cgo
|
//go:build cgo && !(linux || freebsd)
|
||||||
|
// +build cgo,!linux,!freebsd
|
||||||
|
|
||||||
package unshare
|
package unshare
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue