mirror of
https://github.com/opencontainers/runc.git
synced 2025-10-05 23:46:57 +08:00
Merge pull request #1688 from AkihiroSuda/unshare-m-r
main: support rootless mode in userns
This commit is contained in:
@@ -44,7 +44,11 @@ checkpointed.`,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// XXX: Currently this is untested with rootless containers.
|
// XXX: Currently this is untested with rootless containers.
|
||||||
if isRootless() {
|
rootless, err := isRootless(context)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rootless {
|
||||||
return fmt.Errorf("runc checkpoint requires root")
|
return fmt.Errorf("runc checkpoint requires root")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -28,7 +28,6 @@ import (
|
|||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/syndtr/gocapability/capability"
|
|
||||||
"github.com/vishvananda/netlink/nl"
|
"github.com/vishvananda/netlink/nl"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@@ -1798,12 +1797,6 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
if requiresRootOrMappingTool(c.config) {
|
if requiresRootOrMappingTool(c.config) {
|
||||||
// check if we have CAP_SETGID to setgroup properly
|
|
||||||
pid, err := capability.NewPid(0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !pid.Get(capability.EFFECTIVE, capability.CAP_SETGID) {
|
|
||||||
r.AddData(&Boolmsg{
|
r.AddData(&Boolmsg{
|
||||||
Type: SetgroupAttr,
|
Type: SetgroupAttr,
|
||||||
Value: true,
|
Value: true,
|
||||||
@@ -1811,7 +1804,6 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if c.config.OomScoreAdj != nil {
|
if c.config.OomScoreAdj != nil {
|
||||||
// write oom_score_adj
|
// write oom_score_adj
|
||||||
|
@@ -3,13 +3,12 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall" // only for exec
|
"syscall" // only for exec
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/opencontainers/runc/libcontainer/user"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -102,34 +101,43 @@ func Setctty() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunningInUserNS detects whether we are currently running in a user namespace.
|
// RunningInUserNS detects whether we are currently running in a user namespace.
|
||||||
// Copied from github.com/lxc/lxd/shared/util.go
|
// Originally copied from github.com/lxc/lxd/shared/util.go
|
||||||
func RunningInUserNS() bool {
|
func RunningInUserNS() bool {
|
||||||
file, err := os.Open("/proc/self/uid_map")
|
uidmap, err := user.CurrentProcessUIDMap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// This kernel-provided file only exists if user namespaces are supported
|
// This kernel-provided file only exists if user namespaces are supported
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
defer file.Close()
|
return UIDMapInUserNS(uidmap)
|
||||||
|
}
|
||||||
|
|
||||||
buf := bufio.NewReader(file)
|
func UIDMapInUserNS(uidmap []user.IDMap) bool {
|
||||||
l, _, err := buf.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
line := string(l)
|
|
||||||
var a, b, c int64
|
|
||||||
fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
|
|
||||||
/*
|
/*
|
||||||
* We assume we are in the initial user namespace if we have a full
|
* We assume we are in the initial user namespace if we have a full
|
||||||
* range - 4294967295 uids starting at uid 0.
|
* range - 4294967295 uids starting at uid 0.
|
||||||
*/
|
*/
|
||||||
if a == 0 && b == 0 && c == 4294967295 {
|
if len(uidmap) == 1 && uidmap[0].ID == 0 && uidmap[0].ParentID == 0 && uidmap[0].Count == 4294967295 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetParentNSeuid returns the euid within the parent user namespace
|
||||||
|
func GetParentNSeuid() int {
|
||||||
|
euid := os.Geteuid()
|
||||||
|
uidmap, err := user.CurrentProcessUIDMap()
|
||||||
|
if err != nil {
|
||||||
|
// This kernel-provided file only exists if user namespaces are supported
|
||||||
|
return euid
|
||||||
|
}
|
||||||
|
for _, um := range uidmap {
|
||||||
|
if um.ID <= euid && euid <= um.ID+um.Count-1 {
|
||||||
|
return um.ParentID + euid - um.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return euid
|
||||||
|
}
|
||||||
|
|
||||||
// SetSubreaper sets the value i as the subreaper setting for the calling process
|
// SetSubreaper sets the value i as the subreaper setting for the calling process
|
||||||
func SetSubreaper(i int) error {
|
func SetSubreaper(i int) error {
|
||||||
return unix.Prctl(PR_SET_CHILD_SUBREAPER, uintptr(i), 0, 0, 0)
|
return unix.Prctl(PR_SET_CHILD_SUBREAPER, uintptr(i), 0, 0, 0)
|
||||||
|
45
libcontainer/system/linux_test.go
Normal file
45
libcontainer/system/linux_test.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/opencontainers/runc/libcontainer/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUIDMapInUserNS(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
s string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
s: " 0 0 4294967295\n",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
s: " 0 0 1\n",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
s: " 0 1001 1\n 1 231072 65536\n",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// file exist but empty (the initial state when userns is created. see man 7 user_namespaces)
|
||||||
|
s: "",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
uidmap, err := user.ParseIDMap(strings.NewReader(c.s))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
actual := UIDMapInUserNS(uidmap)
|
||||||
|
if c.expected != actual {
|
||||||
|
t.Fatalf("expected %v, got %v for %q", c.expected, actual, c.s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2,8 +2,26 @@
|
|||||||
|
|
||||||
package system
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/opencontainers/runc/libcontainer/user"
|
||||||
|
)
|
||||||
|
|
||||||
// RunningInUserNS is a stub for non-Linux systems
|
// RunningInUserNS is a stub for non-Linux systems
|
||||||
// Always returns false
|
// Always returns false
|
||||||
func RunningInUserNS() bool {
|
func RunningInUserNS() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UIDMapInUserNS is a stub for non-Linux systems
|
||||||
|
// Always returns false
|
||||||
|
func UIDMapInUserNS(uidmap []user.IDMap) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetParentNSeuid returns the euid within the parent user namespace
|
||||||
|
// Always returns os.Geteuid on non-linux
|
||||||
|
func GetParentNSeuid() int {
|
||||||
|
return os.Geteuid()
|
||||||
|
}
|
||||||
|
@@ -114,3 +114,29 @@ func CurrentUser() (User, error) {
|
|||||||
func CurrentGroup() (Group, error) {
|
func CurrentGroup() (Group, error) {
|
||||||
return LookupGid(unix.Getgid())
|
return LookupGid(unix.Getgid())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CurrentUserSubUIDs() ([]SubID, error) {
|
||||||
|
u, err := CurrentUser()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ParseSubIDFileFilter("/etc/subuid",
|
||||||
|
func(entry SubID) bool { return entry.Name == u.Name })
|
||||||
|
}
|
||||||
|
|
||||||
|
func CurrentGroupSubGIDs() ([]SubID, error) {
|
||||||
|
g, err := CurrentGroup()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ParseSubIDFileFilter("/etc/subgid",
|
||||||
|
func(entry SubID) bool { return entry.Name == g.Name })
|
||||||
|
}
|
||||||
|
|
||||||
|
func CurrentProcessUIDMap() ([]IDMap, error) {
|
||||||
|
return ParseIDMapFile("/proc/self/uid_map")
|
||||||
|
}
|
||||||
|
|
||||||
|
func CurrentProcessGIDMap() ([]IDMap, error) {
|
||||||
|
return ParseIDMapFile("/proc/self/gid_map")
|
||||||
|
}
|
||||||
|
@@ -75,12 +75,29 @@ func groupFromOS(g *user.Group) (Group, error) {
|
|||||||
return newGroup, nil
|
return newGroup, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubID represents an entry in /etc/sub{u,g}id
|
||||||
|
type SubID struct {
|
||||||
|
Name string
|
||||||
|
SubID int
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDMap represents an entry in /proc/PID/{u,g}id_map
|
||||||
|
type IDMap struct {
|
||||||
|
ID int
|
||||||
|
ParentID int
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
func parseLine(line string, v ...interface{}) {
|
func parseLine(line string, v ...interface{}) {
|
||||||
if line == "" {
|
parseParts(strings.Split(line, ":"), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseParts(parts []string, v ...interface{}) {
|
||||||
|
if len(parts) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.Split(line, ":")
|
|
||||||
for i, p := range parts {
|
for i, p := range parts {
|
||||||
// Ignore cases where we don't have enough fields to populate the arguments.
|
// Ignore cases where we don't have enough fields to populate the arguments.
|
||||||
// Some configuration files like to misbehave.
|
// Some configuration files like to misbehave.
|
||||||
@@ -479,3 +496,111 @@ func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int
|
|||||||
}
|
}
|
||||||
return GetAdditionalGroups(additionalGroups, group)
|
return GetAdditionalGroups(additionalGroups, group)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseSubIDFile(path string) ([]SubID, error) {
|
||||||
|
subid, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer subid.Close()
|
||||||
|
return ParseSubID(subid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseSubID(subid io.Reader) ([]SubID, error) {
|
||||||
|
return ParseSubIDFilter(subid, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseSubIDFileFilter(path string, filter func(SubID) bool) ([]SubID, error) {
|
||||||
|
subid, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer subid.Close()
|
||||||
|
return ParseSubIDFilter(subid, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseSubIDFilter(r io.Reader, filter func(SubID) bool) ([]SubID, error) {
|
||||||
|
if r == nil {
|
||||||
|
return nil, fmt.Errorf("nil source for subid-formatted data")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
s = bufio.NewScanner(r)
|
||||||
|
out = []SubID{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for s.Scan() {
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
line := strings.TrimSpace(s.Text())
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// see: man 5 subuid
|
||||||
|
p := SubID{}
|
||||||
|
parseLine(line, &p.Name, &p.SubID, &p.Count)
|
||||||
|
|
||||||
|
if filter == nil || filter(p) {
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseIDMapFile(path string) ([]IDMap, error) {
|
||||||
|
r, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
return ParseIDMap(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseIDMap(r io.Reader) ([]IDMap, error) {
|
||||||
|
return ParseIDMapFilter(r, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseIDMapFileFilter(path string, filter func(IDMap) bool) ([]IDMap, error) {
|
||||||
|
r, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
return ParseIDMapFilter(r, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseIDMapFilter(r io.Reader, filter func(IDMap) bool) ([]IDMap, error) {
|
||||||
|
if r == nil {
|
||||||
|
return nil, fmt.Errorf("nil source for idmap-formatted data")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
s = bufio.NewScanner(r)
|
||||||
|
out = []IDMap{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for s.Scan() {
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
line := strings.TrimSpace(s.Text())
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// see: man 7 user_namespaces
|
||||||
|
p := IDMap{}
|
||||||
|
parseParts(strings.Fields(line), &p.ID, &p.ParentID, &p.Count)
|
||||||
|
|
||||||
|
if filter == nil || filter(p) {
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
11
main.go
11
main.go
@@ -63,7 +63,11 @@ func main() {
|
|||||||
app.Version = strings.Join(v, "\n")
|
app.Version = strings.Join(v, "\n")
|
||||||
|
|
||||||
root := "/run/runc"
|
root := "/run/runc"
|
||||||
if os.Geteuid() != 0 {
|
rootless, err := isRootless(nil)
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
if rootless {
|
||||||
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
|
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
|
||||||
if runtimeDir != "" {
|
if runtimeDir != "" {
|
||||||
root = runtimeDir + "/runc"
|
root = runtimeDir + "/runc"
|
||||||
@@ -108,6 +112,11 @@ func main() {
|
|||||||
Name: "systemd-cgroup",
|
Name: "systemd-cgroup",
|
||||||
Usage: "enable systemd cgroup support, expects cgroupsPath to be of form \"slice:prefix:name\" for e.g. \"system.slice:runc:434234\"",
|
Usage: "enable systemd cgroup support, expects cgroupsPath to be of form \"slice:prefix:name\" for e.g. \"system.slice:runc:434234\"",
|
||||||
},
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "rootless",
|
||||||
|
Value: "auto",
|
||||||
|
Usage: "enable rootless mode ('true', 'false', or 'auto')",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
checkpointCommand,
|
checkpointCommand,
|
||||||
|
6
ps.go
6
ps.go
@@ -29,7 +29,11 @@ var psCommand = cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// XXX: Currently not supported with rootless containers.
|
// XXX: Currently not supported with rootless containers.
|
||||||
if isRootless() {
|
rootless, err := isRootless(context)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rootless {
|
||||||
return fmt.Errorf("runc ps requires root")
|
return fmt.Errorf("runc ps requires root")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -96,7 +96,11 @@ using the runc checkpoint command.`,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// XXX: Currently this is untested with rootless containers.
|
// XXX: Currently this is untested with rootless containers.
|
||||||
if isRootless() {
|
rootless, err := isRootless(context)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rootless {
|
||||||
return fmt.Errorf("runc restore requires root")
|
return fmt.Errorf("runc restore requires root")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
utils.go
11
utils.go
@@ -4,6 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
|
||||||
@@ -81,3 +83,12 @@ func revisePidFile(context *cli.Context) error {
|
|||||||
}
|
}
|
||||||
return context.Set("pid-file", pidFile)
|
return context.Set("pid-file", pidFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseBoolOrAuto returns (nil, nil) if s is empty or "auto"
|
||||||
|
func parseBoolOrAuto(s string) (*bool, error) {
|
||||||
|
if s == "" || strings.ToLower(s) == "auto" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
b, err := strconv.ParseBool(s)
|
||||||
|
return &b, err
|
||||||
|
}
|
||||||
|
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/opencontainers/runc/libcontainer/configs"
|
"github.com/opencontainers/runc/libcontainer/configs"
|
||||||
"github.com/opencontainers/runc/libcontainer/intelrdt"
|
"github.com/opencontainers/runc/libcontainer/intelrdt"
|
||||||
"github.com/opencontainers/runc/libcontainer/specconv"
|
"github.com/opencontainers/runc/libcontainer/specconv"
|
||||||
|
"github.com/opencontainers/runc/libcontainer/system"
|
||||||
"github.com/opencontainers/runc/libcontainer/utils"
|
"github.com/opencontainers/runc/libcontainer/utils"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
|
||||||
@@ -220,19 +221,37 @@ func createPidFile(path string, process *libcontainer.Process) error {
|
|||||||
return os.Rename(tmpName, path)
|
return os.Rename(tmpName, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: Currently we autodetect rootless mode.
|
func isRootless(context *cli.Context) (bool, error) {
|
||||||
func isRootless() bool {
|
if context != nil {
|
||||||
return os.Geteuid() != 0
|
b, err := parseBoolOrAuto(context.GlobalString("rootless"))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if b != nil {
|
||||||
|
return *b, nil
|
||||||
|
}
|
||||||
|
// nil b stands for "auto detect"
|
||||||
|
}
|
||||||
|
// Even if os.Geteuid() == 0, it might still require rootless mode,
|
||||||
|
// especially when running within userns.
|
||||||
|
// So we use system.GetParentNSeuid() here.
|
||||||
|
//
|
||||||
|
// TODO(AkihiroSuda): how to support nested userns?
|
||||||
|
return system.GetParentNSeuid() != 0 || system.RunningInUserNS(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error) {
|
func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error) {
|
||||||
|
rootless, err := isRootless(context)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{
|
config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{
|
||||||
CgroupName: id,
|
CgroupName: id,
|
||||||
UseSystemdCgroup: context.GlobalBool("systemd-cgroup"),
|
UseSystemdCgroup: context.GlobalBool("systemd-cgroup"),
|
||||||
NoPivotRoot: context.Bool("no-pivot"),
|
NoPivotRoot: context.Bool("no-pivot"),
|
||||||
NoNewKeyring: context.Bool("no-new-keyring"),
|
NoNewKeyring: context.Bool("no-new-keyring"),
|
||||||
Spec: spec,
|
Spec: spec,
|
||||||
Rootless: isRootless(),
|
Rootless: rootless,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
Reference in New Issue
Block a user