Bump runc and runtime-specs deps

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2016-04-26 13:38:36 -07:00
parent 715ac711b8
commit 7a1d28e42b
33 changed files with 731 additions and 180 deletions

View file

@ -15,7 +15,7 @@ import (
"github.com/opencontainers/runc/libcontainer" "github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/system" "github.com/opencontainers/runc/libcontainer/system"
ocs "github.com/opencontainers/specs/specs-go" ocs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"

View file

@ -14,8 +14,8 @@ clone git github.com/docker/go-units 5d2041e26a699eaca682e2ea41c8f891e1060444
clone git github.com/godbus/dbus e2cf28118e66a6a63db46cf6088a35d2054d3bb0 clone git github.com/godbus/dbus e2cf28118e66a6a63db46cf6088a35d2054d3bb0
clone git github.com/golang/glog 23def4e6c14b4da8ac2ed8007337bc5eb5007998 clone git github.com/golang/glog 23def4e6c14b4da8ac2ed8007337bc5eb5007998
clone git github.com/golang/protobuf 8d92cf5fc15a4382f8964b08e1f42a75c0591aa3 clone git github.com/golang/protobuf 8d92cf5fc15a4382f8964b08e1f42a75c0591aa3
clone git github.com/opencontainers/runc e87436998478d222be209707503c27f6f91be0c5 clone git github.com/opencontainers/runc 9c89737e6e117a8be5a4980bc9795fe1a2b1028e
clone git github.com/opencontainers/specs 3ce138b1934bf227a418e241ead496c383eaba1c clone git github.com/opencontainers/runtime-spec f955d90e70a98ddfb886bd930ffd076da9b67998
clone git github.com/rcrowley/go-metrics eeba7bd0dd01ace6e690fa833b3f22aaec29af43 clone git github.com/rcrowley/go-metrics eeba7bd0dd01ace6e690fa833b3f22aaec29af43
clone git github.com/satori/go.uuid f9ab0dce87d815821e221626b772e3475a0d2749 clone git github.com/satori/go.uuid f9ab0dce87d815821e221626b772e3475a0d2749
clone git github.com/syndtr/gocapability 2c00daeb6c3b45114c80ac44119e7b8801fdd852 clone git github.com/syndtr/gocapability 2c00daeb6c3b45114c80ac44119e7b8801fdd852

View file

@ -8,7 +8,7 @@ import (
"path/filepath" "path/filepath"
"reflect" "reflect"
ocs "github.com/opencontainers/specs/specs-go" ocs "github.com/opencontainers/runtime-spec/specs-go"
) )
var ( var (

View file

@ -14,7 +14,7 @@ import (
"github.com/docker/containerd/specs" "github.com/docker/containerd/specs"
"github.com/opencontainers/runc/libcontainer" "github.com/opencontainers/runc/libcontainer"
ocs "github.com/opencontainers/specs/specs-go" ocs "github.com/opencontainers/runtime-spec/specs-go"
) )
func getRootIDs(s *specs.Spec) (int, int, error) { func getRootIDs(s *specs.Spec) (int, int, error) {

View file

@ -1,6 +1,6 @@
package specs package specs
import ocs "github.com/opencontainers/specs/specs-go" import ocs "github.com/opencontainers/runtime-spec/specs-go"
type ( type (
ProcessSpec ocs.Process ProcessSpec ocs.Process

View file

@ -1,6 +1,6 @@
package specs package specs
// Temporary Windows version of the spec in lieu of opencontainers/specs/specs-go having // Temporary Windows version of the spec in lieu of opencontainers/runtime-spec/specs-go having
// Windows support currently. // Windows support currently.
type ( type (
@ -8,7 +8,7 @@ type (
ProcessSpec Process ProcessSpec Process
) )
// This is a temporary module in lieu of opencontainers/specs/specs-go being compatible // This is a temporary module in lieu of opencontainers/runtime-spec/specs-go being compatible
// currently on Windows. // currently on Windows.
// Process contains information to start a specific application inside the container. // Process contains information to start a specific application inside the container.

View file

@ -90,7 +90,7 @@ in tmpfs.
After `/dev/null` has been setup we check for any external links between After `/dev/null` has been setup we check for any external links between
the container's io, STDIN, STDOUT, STDERR. If the container's io is pointing the container's io, STDIN, STDOUT, STDERR. If the container's io is pointing
to `/dev/null` outside the container we close and `dup2` the the `/dev/null` to `/dev/null` outside the container we close and `dup2` the `/dev/null`
that is local to the container's rootfs. that is local to the container's rootfs.

View file

@ -349,7 +349,7 @@ func writeFile(dir, file, data string) error {
// Normally dir should not be empty, one case is that cgroup subsystem // Normally dir should not be empty, one case is that cgroup subsystem
// is not mounted, we will get empty dir, and we want it fail here. // is not mounted, we will get empty dir, and we want it fail here.
if dir == "" { if dir == "" {
return fmt.Errorf("no such directory for %s.", file) return fmt.Errorf("no such directory for %s", file)
} }
if err := ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700); err != nil { if err := ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700); err != nil {
return fmt.Errorf("failed to write %v to %v: %v", data, file, err) return fmt.Errorf("failed to write %v to %v: %v", data, file, err)

View file

@ -11,6 +11,7 @@ type ThrottlingData struct {
ThrottledTime uint64 `json:"throttled_time,omitempty"` ThrottledTime uint64 `json:"throttled_time,omitempty"`
} }
// CpuUsage denotes the usage of a CPU.
// All CPU stats are aggregate since container inception. // All CPU stats are aggregate since container inception.
type CpuUsage struct { type CpuUsage struct {
// Total CPU time consumed. // Total CPU time consumed.

View file

@ -273,7 +273,7 @@ func writeFile(dir, file, data string) error {
// Normally dir should not be empty, one case is that cgroup subsystem // Normally dir should not be empty, one case is that cgroup subsystem
// is not mounted, we will get empty dir, and we want it fail here. // is not mounted, we will get empty dir, and we want it fail here.
if dir == "" { if dir == "" {
return fmt.Errorf("no such directory for %s.", file) return fmt.Errorf("no such directory for %s", file)
} }
return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700)
} }

View file

@ -23,6 +23,9 @@ func FindCgroupMountpoint(subsystem string) (string, error) {
// We are not using mount.GetMounts() because it's super-inefficient, // We are not using mount.GetMounts() because it's super-inefficient,
// parsing it directly sped up x10 times because of not using Sscanf. // parsing it directly sped up x10 times because of not using Sscanf.
// It was one of two major performance drawbacks in container start. // It was one of two major performance drawbacks in container start.
if !isSubsystemAvailable(subsystem) {
return "", NewNotFoundError(subsystem)
}
f, err := os.Open("/proc/self/mountinfo") f, err := os.Open("/proc/self/mountinfo")
if err != nil { if err != nil {
return "", err return "", err
@ -47,6 +50,9 @@ func FindCgroupMountpoint(subsystem string) (string, error) {
} }
func FindCgroupMountpointAndRoot(subsystem string) (string, string, error) { func FindCgroupMountpointAndRoot(subsystem string) (string, string, error) {
if !isSubsystemAvailable(subsystem) {
return "", "", NewNotFoundError(subsystem)
}
f, err := os.Open("/proc/self/mountinfo") f, err := os.Open("/proc/self/mountinfo")
if err != nil { if err != nil {
return "", "", err return "", "", err
@ -70,6 +76,15 @@ func FindCgroupMountpointAndRoot(subsystem string) (string, string, error) {
return "", "", NewNotFoundError(subsystem) return "", "", NewNotFoundError(subsystem)
} }
func isSubsystemAvailable(subsystem string) bool {
cgroups, err := ParseCgroupFile("/proc/self/cgroup")
if err != nil {
return false
}
_, avail := cgroups[subsystem]
return avail
}
func FindCgroupMountpointDir() (string, error) { func FindCgroupMountpointDir() (string, error) {
f, err := os.Open("/proc/self/mountinfo") f, err := os.Open("/proc/self/mountinfo")
if err != nil { if err != nil {
@ -124,7 +139,8 @@ func (m Mount) GetThisCgroupDir(cgroups map[string]string) (string, error) {
func getCgroupMountsHelper(ss map[string]bool, mi io.Reader) ([]Mount, error) { func getCgroupMountsHelper(ss map[string]bool, mi io.Reader) ([]Mount, error) {
res := make([]Mount, 0, len(ss)) res := make([]Mount, 0, len(ss))
scanner := bufio.NewScanner(mi) scanner := bufio.NewScanner(mi)
for scanner.Scan() { numFound := 0
for scanner.Scan() && numFound < len(ss) {
txt := scanner.Text() txt := scanner.Text()
sepIdx := strings.Index(txt, " - ") sepIdx := strings.Index(txt, " - ")
if sepIdx == -1 { if sepIdx == -1 {
@ -139,12 +155,15 @@ func getCgroupMountsHelper(ss map[string]bool, mi io.Reader) ([]Mount, error) {
Root: fields[3], Root: fields[3],
} }
for _, opt := range strings.Split(fields[len(fields)-1], ",") { for _, opt := range strings.Split(fields[len(fields)-1], ",") {
if !ss[opt] {
continue
}
if strings.HasPrefix(opt, cgroupNamePrefix) { if strings.HasPrefix(opt, cgroupNamePrefix) {
m.Subsystems = append(m.Subsystems, opt[len(cgroupNamePrefix):]) m.Subsystems = append(m.Subsystems, opt[len(cgroupNamePrefix):])
} } else {
if ss[opt] {
m.Subsystems = append(m.Subsystems, opt) m.Subsystems = append(m.Subsystems, opt)
} }
numFound++
} }
res = append(res, m) res = append(res, m)
} }
@ -161,19 +180,19 @@ func GetCgroupMounts() ([]Mount, error) {
} }
defer f.Close() defer f.Close()
all, err := GetAllSubsystems() all, err := ParseCgroupFile("/proc/self/cgroup")
if err != nil { if err != nil {
return nil, err return nil, err
} }
allMap := make(map[string]bool) allMap := make(map[string]bool)
for _, s := range all { for s := range all {
allMap[s] = true allMap[s] = true
} }
return getCgroupMountsHelper(allMap, f) return getCgroupMountsHelper(allMap, f)
} }
// Returns all the cgroup subsystems supported by the kernel // GetAllSubsystems returns all the cgroup subsystems supported by the kernel
func GetAllSubsystems() ([]string, error) { func GetAllSubsystems() ([]string, error) {
f, err := os.Open("/proc/cgroups") f, err := os.Open("/proc/cgroups")
if err != nil { if err != nil {
@ -199,7 +218,7 @@ func GetAllSubsystems() ([]string, error) {
return subsystems, nil return subsystems, nil
} }
// Returns the relative path to the cgroup docker is running in. // GetThisCgroupDir returns the relative path to the cgroup docker is running in.
func GetThisCgroupDir(subsystem string) (string, error) { func GetThisCgroupDir(subsystem string) (string, error) {
cgroups, err := ParseCgroupFile("/proc/self/cgroup") cgroups, err := ParseCgroupFile("/proc/self/cgroup")
if err != nil { if err != nil {

View file

@ -33,7 +33,7 @@ type Seccomp struct {
Syscalls []*Syscall `json:"syscalls"` Syscalls []*Syscall `json:"syscalls"`
} }
// An action to be taken upon rule match in Seccomp // Action is taken upon rule match in Seccomp
type Action int type Action int
const ( const (
@ -44,7 +44,7 @@ const (
Trace Trace
) )
// A comparison operator to be used when matching syscall arguments in Seccomp // Operator is a comparison operator to be used when matching syscall arguments in Seccomp
type Operator int type Operator int
const ( const (
@ -57,7 +57,7 @@ const (
MaskEqualTo MaskEqualTo
) )
// A rule to match a specific syscall argument in Seccomp // Arg is a rule to match a specific syscall argument in Seccomp
type Arg struct { type Arg struct {
Index uint `json:"index"` Index uint `json:"index"`
Value uint64 `json:"value"` Value uint64 `json:"value"`
@ -65,7 +65,7 @@ type Arg struct {
Op Operator `json:"op"` Op Operator `json:"op"`
} }
// An rule to match a syscall in Seccomp // Syscall is a rule to match a syscall in Seccomp
type Syscall struct { type Syscall struct {
Name string `json:"name"` Name string `json:"name"`
Action Action `json:"action"` Action Action `json:"action"`
@ -261,7 +261,7 @@ type Hook interface {
Run(HookState) error Run(HookState) error
} }
// NewFunctionHooks will call the provided function when the hook is run. // NewFunctionHook will call the provided function when the hook is run.
func NewFunctionHook(f func(HookState) error) FuncHook { func NewFunctionHook(f func(HookState) error) FuncHook {
return FuncHook{ return FuncHook{
run: f, run: f,
@ -284,7 +284,7 @@ type Command struct {
Timeout *time.Duration `json:"timeout"` Timeout *time.Duration `json:"timeout"`
} }
// NewCommandHooks will execute the provided command when the hook is run. // NewCommandHook will execute the provided command when the hook is run.
func NewCommandHook(cmd Command) CommandHook { func NewCommandHook(cmd Command) CommandHook {
return CommandHook{ return CommandHook{
Command: cmd, Command: cmd,

View file

@ -4,7 +4,7 @@ package configs
import "fmt" import "fmt"
// Gets the root uid for the process on host which could be non-zero // HostUID gets the root uid for the process on host which could be non-zero
// when user namespaces are enabled. // when user namespaces are enabled.
func (c Config) HostUID() (int, error) { func (c Config) HostUID() (int, error) {
if c.Namespaces.Contains(NEWUSER) { if c.Namespaces.Contains(NEWUSER) {
@ -21,7 +21,7 @@ func (c Config) HostUID() (int, error) {
return 0, nil return 0, nil
} }
// Gets the root gid for the process on host which could be non-zero // HostGID gets the root gid for the process on host which could be non-zero
// when user namespaces are enabled. // when user namespaces are enabled.
func (c Config) HostGID() (int, error) { func (c Config) HostGID() (int, error) {
if c.Namespaces.Contains(NEWUSER) { if c.Namespaces.Contains(NEWUSER) {

View file

@ -3,7 +3,7 @@
package configs package configs
var ( var (
// These are devices that are to be both allowed and created. // DefaultSimpleDevices are devices that are to be both allowed and created.
DefaultSimpleDevices = []*Device{ DefaultSimpleDevices = []*Device{
// /dev/null and zero // /dev/null and zero
{ {

View file

@ -7,6 +7,7 @@ import (
"strings" "strings"
"github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/selinux"
) )
type Validator interface { type Validator interface {
@ -42,7 +43,7 @@ func (v *ConfigValidator) Validate(config *configs.Config) error {
return nil return nil
} }
// rootfs validates the the rootfs is an absolute path and is not a symlink // rootfs validates if the rootfs is an absolute path and is not a symlink
// to the container's root filesystem. // to the container's root filesystem.
func (v *ConfigValidator) rootfs(config *configs.Config) error { func (v *ConfigValidator) rootfs(config *configs.Config) error {
cleaned, err := filepath.Abs(config.Rootfs) cleaned, err := filepath.Abs(config.Rootfs)
@ -52,7 +53,7 @@ func (v *ConfigValidator) rootfs(config *configs.Config) error {
if cleaned, err = filepath.EvalSymlinks(cleaned); err != nil { if cleaned, err = filepath.EvalSymlinks(cleaned); err != nil {
return err return err
} }
if config.Rootfs != cleaned { if filepath.Clean(config.Rootfs) != cleaned {
return fmt.Errorf("%s is not an absolute path or is a symlink", config.Rootfs) return fmt.Errorf("%s is not an absolute path or is a symlink", config.Rootfs)
} }
return nil return nil
@ -80,6 +81,10 @@ func (v *ConfigValidator) security(config *configs.Config) error {
!config.Namespaces.Contains(configs.NEWNS) { !config.Namespaces.Contains(configs.NEWNS) {
return fmt.Errorf("unable to restrict sys entries without a private MNT namespace") return fmt.Errorf("unable to restrict sys entries without a private MNT namespace")
} }
if config.ProcessLabel != "" && !selinux.SelinuxEnabled() {
return fmt.Errorf("selinux label is specified in config, but selinux is disabled or not supported")
}
return nil return nil
} }
@ -100,14 +105,7 @@ func (v *ConfigValidator) usernamespace(config *configs.Config) error {
// /proc/sys isn't completely namespaced and depending on which namespaces // /proc/sys isn't completely namespaced and depending on which namespaces
// are specified, a subset of sysctls are permitted. // are specified, a subset of sysctls are permitted.
func (v *ConfigValidator) sysctl(config *configs.Config) error { func (v *ConfigValidator) sysctl(config *configs.Config) error {
validSysctlPrefixes := []string{} validSysctlMap := map[string]bool{
validSysctlMap := make(map[string]bool)
if config.Namespaces.Contains(configs.NEWNET) {
validSysctlPrefixes = append(validSysctlPrefixes, "net.")
}
if config.Namespaces.Contains(configs.NEWIPC) {
validSysctlPrefixes = append(validSysctlPrefixes, "fs.mqueue.")
validSysctlMap = map[string]bool{
"kernel.msgmax": true, "kernel.msgmax": true,
"kernel.msgmnb": true, "kernel.msgmnb": true,
"kernel.msgmni": true, "kernel.msgmni": true,
@ -117,22 +115,24 @@ func (v *ConfigValidator) sysctl(config *configs.Config) error {
"kernel.shmmni": true, "kernel.shmmni": true,
"kernel.shm_rmid_forced": true, "kernel.shm_rmid_forced": true,
} }
}
for s := range config.Sysctl { for s := range config.Sysctl {
if validSysctlMap[s] { if validSysctlMap[s] || strings.HasPrefix(s, "fs.mqueue.") {
if config.Namespaces.Contains(configs.NEWIPC) {
continue continue
} } else {
valid := false return fmt.Errorf("sysctl %q is not allowed in the hosts ipc namespace", s)
for _, vp := range validSysctlPrefixes {
if strings.HasPrefix(s, vp) {
valid = true
break
} }
} }
if !valid { if strings.HasPrefix(s, "net.") {
return fmt.Errorf("sysctl %q is not permitted in the config", s) if config.Namespaces.Contains(configs.NEWNET) {
continue
} else {
return fmt.Errorf("sysctl %q is not allowed in the hosts network namespace", s)
} }
} }
return fmt.Errorf("sysctl %q is not in a separate kernel namespace", s)
}
return nil return nil
} }

View file

@ -1,4 +1,4 @@
// Libcontainer provides a native Go implementation for creating containers // Package libcontainer provides a native Go implementation for creating containers
// with namespaces, cgroups, capabilities, and filesystem access controls. // with namespaces, cgroups, capabilities, and filesystem access controls.
// It allows you to manage the lifecycle of the container performing additional operations // It allows you to manage the lifecycle of the container performing additional operations
// after the container is created. // after the container is created.
@ -11,23 +11,23 @@ import (
"github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/configs"
) )
// The status of a container. // Status is the status of a container.
type Status int type Status int
const ( const (
// The container exists but has not been run yet // Created is the status that denotes the container exists but has not been run yet
Created Status = iota Created Status = iota
// The container exists and is running. // Created is the status that denotes the container exists and is running.
Running Running
// The container exists, it is in the process of being paused. // Pausing is the status that denotes the container exists, it is in the process of being paused.
Pausing Pausing
// The container exists, but all its processes are paused. // Paused is the status that denotes the container exists, but all its processes are paused.
Paused Paused
// The container does not exist. // Destroyed is the status that denotes the container does not exist.
Destroyed Destroyed
) )
@ -67,7 +67,7 @@ type BaseState struct {
Config configs.Config `json:"config"` Config configs.Config `json:"config"`
} }
// A libcontainer container object. // BaseContainer is a libcontainer container object.
// //
// Each container is thread-safe within the same process. Since a container can // Each container is thread-safe within the same process. Since a container can
// be destroyed by a separate process, any function may return that the container // be destroyed by a separate process, any function may return that the container

View file

@ -62,7 +62,7 @@ type State struct {
ExternalDescriptors []string `json:"external_descriptors,omitempty"` ExternalDescriptors []string `json:"external_descriptors,omitempty"`
} }
// A libcontainer container object. // Container is a libcontainer container object.
// //
// Each container is thread-safe within the same process. Since a container can // Each container is thread-safe within the same process. Since a container can
// be destroyed by a separate process, any function may return that the container // be destroyed by a separate process, any function may return that the container
@ -84,7 +84,7 @@ type Container interface {
// Systemerror - System error. // Systemerror - System error.
Restore(process *Process, criuOpts *CriuOpts) error Restore(process *Process, criuOpts *CriuOpts) error
// If the Container state is RUNNING or PAUSING, sets the Container state to PAUSING and pauses // If the Container state is RUNNING, sets the Container state to PAUSING and pauses
// the execution of any user processes. Asynchronously, when the container finished being paused the // the execution of any user processes. Asynchronously, when the container finished being paused the
// state is changed to PAUSED. // state is changed to PAUSED.
// If the Container state is PAUSED, do nothing. // If the Container state is PAUSED, do nothing.
@ -141,7 +141,7 @@ func (c *linuxContainer) State() (*State, error) {
func (c *linuxContainer) Processes() ([]int, error) { func (c *linuxContainer) Processes() ([]int, error) {
pids, err := c.cgroupManager.GetAllPids() pids, err := c.cgroupManager.GetAllPids()
if err != nil { if err != nil {
return nil, newSystemError(err) return nil, newSystemErrorWithCause(err, "getting all container pids from cgroups")
} }
return pids, nil return pids, nil
} }
@ -152,14 +152,14 @@ func (c *linuxContainer) Stats() (*Stats, error) {
stats = &Stats{} stats = &Stats{}
) )
if stats.CgroupStats, err = c.cgroupManager.GetStats(); err != nil { if stats.CgroupStats, err = c.cgroupManager.GetStats(); err != nil {
return stats, newSystemError(err) return stats, newSystemErrorWithCause(err, "getting container stats from cgroups")
} }
for _, iface := range c.config.Networks { for _, iface := range c.config.Networks {
switch iface.Type { switch iface.Type {
case "veth": case "veth":
istats, err := getNetworkInterfaceStats(iface.HostInterfaceName) istats, err := getNetworkInterfaceStats(iface.HostInterfaceName)
if err != nil { if err != nil {
return stats, newSystemError(err) return stats, newSystemErrorWithCausef(err, "getting network stats for interface %q", iface.HostInterfaceName)
} }
stats.Interfaces = append(stats.Interfaces, istats) stats.Interfaces = append(stats.Interfaces, istats)
} }
@ -184,14 +184,14 @@ func (c *linuxContainer) Start(process *Process) error {
doInit := status == Destroyed doInit := status == Destroyed
parent, err := c.newParentProcess(process, doInit) parent, err := c.newParentProcess(process, doInit)
if err != nil { if err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "creating new parent process")
} }
if err := parent.start(); err != nil { if err := parent.start(); err != nil {
// terminate the process to ensure that it properly is reaped. // terminate the process to ensure that it properly is reaped.
if err := parent.terminate(); err != nil { if err := parent.terminate(); err != nil {
logrus.Warn(err) logrus.Warn(err)
} }
return newSystemError(err) return newSystemErrorWithCause(err, "starting container process")
} }
// generate a timestamp indicating when the container was started // generate a timestamp indicating when the container was started
c.created = time.Now().UTC() c.created = time.Now().UTC()
@ -211,12 +211,12 @@ func (c *linuxContainer) Start(process *Process) error {
Root: c.config.Rootfs, Root: c.config.Rootfs,
BundlePath: utils.SearchLabels(c.config.Labels, "bundle"), BundlePath: utils.SearchLabels(c.config.Labels, "bundle"),
} }
for _, hook := range c.config.Hooks.Poststart { for i, hook := range c.config.Hooks.Poststart {
if err := hook.Run(s); err != nil { if err := hook.Run(s); err != nil {
if err := parent.terminate(); err != nil { if err := parent.terminate(); err != nil {
logrus.Warn(err) logrus.Warn(err)
} }
return newSystemError(err) return newSystemErrorWithCausef(err, "running poststart hook %d", i)
} }
} }
} }
@ -226,7 +226,7 @@ func (c *linuxContainer) Start(process *Process) error {
func (c *linuxContainer) Signal(s os.Signal) error { func (c *linuxContainer) Signal(s os.Signal) error {
if err := c.initProcess.signal(s); err != nil { if err := c.initProcess.signal(s); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "signaling init process")
} }
return nil return nil
} }
@ -234,11 +234,11 @@ func (c *linuxContainer) Signal(s os.Signal) error {
func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error) { func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error) {
parentPipe, childPipe, err := newPipe() parentPipe, childPipe, err := newPipe()
if err != nil { if err != nil {
return nil, newSystemError(err) return nil, newSystemErrorWithCause(err, "creating new init pipe")
} }
cmd, err := c.commandTemplate(p, childPipe) cmd, err := c.commandTemplate(p, childPipe)
if err != nil { if err != nil {
return nil, newSystemError(err) return nil, newSystemErrorWithCause(err, "creating new command template")
} }
if !doInit { if !doInit {
return c.newSetnsProcess(p, cmd, parentPipe, childPipe) return c.newSetnsProcess(p, cmd, parentPipe, childPipe)
@ -299,7 +299,7 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initSetns)) cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initSetns))
state, err := c.currentState() state, err := c.currentState()
if err != nil { if err != nil {
return nil, newSystemError(err) return nil, newSystemErrorWithCause(err, "getting container's current state")
} }
// for setns process, we dont have to set cloneflags as the process namespaces // for setns process, we dont have to set cloneflags as the process namespaces
// will only be set via setns syscall // will only be set via setns syscall
@ -408,13 +408,13 @@ func (c *linuxContainer) NotifyMemoryPressure(level PressureLevel) (<-chan struc
return notifyMemoryPressure(c.cgroupManager.GetPaths(), level) return notifyMemoryPressure(c.cgroupManager.GetPaths(), level)
} }
// check Criu version greater than or equal to min_version // checkCriuVersion checks Criu version greater than or equal to minVersion
func (c *linuxContainer) checkCriuVersion(min_version string) error { func (c *linuxContainer) checkCriuVersion(minVersion string) error {
var x, y, z, versionReq int var x, y, z, versionReq int
_, err := fmt.Sscanf(min_version, "%d.%d.%d\n", &x, &y, &z) // 1.5.2 _, err := fmt.Sscanf(minVersion, "%d.%d.%d\n", &x, &y, &z) // 1.5.2
if err != nil { if err != nil {
_, err = fmt.Sscanf(min_version, "Version: %d.%d\n", &x, &y) // 1.6 _, err = fmt.Sscanf(minVersion, "Version: %d.%d\n", &x, &y) // 1.6
} }
versionReq = x*10000 + y*100 + z versionReq = x*10000 + y*100 + z
@ -459,7 +459,7 @@ func (c *linuxContainer) checkCriuVersion(min_version string) error {
c.criuVersion = x*10000 + y*100 + z c.criuVersion = x*10000 + y*100 + z
if c.criuVersion < versionReq { if c.criuVersion < versionReq {
return fmt.Errorf("CRIU version must be %s or higher", min_version) return fmt.Errorf("CRIU version must be %s or higher", minVersion)
} }
return nil return nil
@ -955,9 +955,9 @@ func (c *linuxContainer) criuNotifications(resp *criurpc.CriuResp, process *Proc
Pid: int(notify.GetPid()), Pid: int(notify.GetPid()),
Root: c.config.Rootfs, Root: c.config.Rootfs,
} }
for _, hook := range c.config.Hooks.Prestart { for i, hook := range c.config.Hooks.Prestart {
if err := hook.Run(s); err != nil { if err := hook.Run(s); err != nil {
return newSystemError(err) return newSystemErrorWithCausef(err, "running prestart hook %d", i)
} }
} }
} }
@ -1046,7 +1046,7 @@ func (c *linuxContainer) isRunning() (bool, error) {
if err == syscall.ESRCH { if err == syscall.ESRCH {
return false, nil return false, nil
} }
return false, newSystemError(err) return false, newSystemErrorWithCausef(err, "sending signal 0 to pid %d", c.initProcess.pid())
} }
return true, nil return true, nil
} }
@ -1057,7 +1057,7 @@ func (c *linuxContainer) isPaused() (bool, error) {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return false, nil return false, nil
} }
return false, newSystemError(err) return false, newSystemErrorWithCause(err, "checking if container is paused")
} }
return bytes.Equal(bytes.TrimSpace(data), []byte("FROZEN")), nil return bytes.Equal(bytes.TrimSpace(data), []byte("FROZEN")), nil
} }
@ -1125,7 +1125,7 @@ func (c *linuxContainer) orderNamespacePaths(namespaces map[configs.NamespaceTyp
} }
// only set to join this namespace if it exists // only set to join this namespace if it exists
if _, err := os.Lstat(p); err != nil { if _, err := os.Lstat(p); err != nil {
return nil, newSystemError(err) return nil, newSystemErrorWithCausef(err, "running lstat on namespace path %q", p)
} }
// do not allow namespace path with comma as we use it to separate // do not allow namespace path with comma as we use it to separate
// the namespace paths // the namespace paths

View file

@ -3,10 +3,10 @@
package libcontainer package libcontainer
// cgroup restoring strategy provided by criu // cgroup restoring strategy provided by criu
type cg_mode uint32 type cgMode uint32
const ( const (
CRIU_CG_MODE_SOFT cg_mode = 3 + iota // restore cgroup properties if only dir created by criu CRIU_CG_MODE_SOFT cgMode = 3 + iota // restore cgroup properties if only dir created by criu
CRIU_CG_MODE_FULL // always restore all cgroups and their properties CRIU_CG_MODE_FULL // always restore all cgroups and their properties
CRIU_CG_MODE_STRICT // restore all, requiring them to not present in the system CRIU_CG_MODE_STRICT // restore all, requiring them to not present in the system
CRIU_CG_MODE_DEFAULT // the same as CRIU_CG_MODE_SOFT CRIU_CG_MODE_DEFAULT // the same as CRIU_CG_MODE_SOFT
@ -32,6 +32,6 @@ type CriuOpts struct {
FileLocks bool // handle file locks, for safety FileLocks bool // handle file locks, for safety
PageServer CriuPageServerInfo // allow to dump to criu page server PageServer CriuPageServerInfo // allow to dump to criu page server
VethPairs []VethPairName // pass the veth to criu when restore VethPairs []VethPairName // pass the veth to criu when restore
ManageCgroupsMode cg_mode // dump or restore cgroup mode ManageCgroupsMode cgMode // dump or restore cgroup mode
EmptyNs uint32 // don't c/r properties for namespace from this mask EmptyNs uint32 // don't c/r properties for namespace from this mask
} }

View file

@ -2,7 +2,7 @@ package libcontainer
import "io" import "io"
// API error code type. // ErrorCode is the API error code type.
type ErrorCode int type ErrorCode int
// API error codes. // API error codes.
@ -56,7 +56,7 @@ func (c ErrorCode) String() string {
} }
} }
// API Error type. // Error is the API error type.
type Error interface { type Error interface {
error error

View file

@ -1,6 +1,7 @@
package libcontainer package libcontainer
import ( import (
"fmt"
"io" "io"
"text/template" "text/template"
"time" "time"
@ -51,6 +52,21 @@ func newGenericError(err error, c ErrorCode) Error {
} }
func newSystemError(err error) Error { func newSystemError(err error) Error {
return createSystemError(err, "")
}
func newSystemErrorWithCausef(err error, cause string, v ...interface{}) Error {
return createSystemError(err, fmt.Sprintf(cause, v...))
}
func newSystemErrorWithCause(err error, cause string) Error {
return createSystemError(err, cause)
}
// createSystemError creates the specified error with the correct number of
// stack frames skipped. This is only to be called by the other functions for
// formatting the error.
func createSystemError(err error, cause string) Error {
if le, ok := err.(Error); ok { if le, ok := err.(Error); ok {
return le return le
} }
@ -58,7 +74,8 @@ func newSystemError(err error) Error {
Timestamp: time.Now(), Timestamp: time.Now(),
Err: err, Err: err,
ECode: SystemError, ECode: SystemError,
Stack: stacktrace.Capture(1), Cause: cause,
Stack: stacktrace.Capture(2),
} }
if err != nil { if err != nil {
gerr.Message = err.Error() gerr.Message = err.Error()
@ -70,13 +87,18 @@ type genericError struct {
Timestamp time.Time Timestamp time.Time
ECode ErrorCode ECode ErrorCode
Err error `json:"-"` Err error `json:"-"`
Cause string
Message string Message string
Stack stacktrace.Stacktrace Stack stacktrace.Stacktrace
} }
func (e *genericError) Error() string { func (e *genericError) Error() string {
if e.Cause == "" {
return e.Message return e.Message
} }
frame := e.Stack.Frames[0]
return fmt.Sprintf("%s:%d: %s caused %q", frame.File, frame.Line, e.Cause, e.Message)
}
func (e *genericError) Code() ErrorCode { func (e *genericError) Code() ErrorCode {
return e.ECode return e.ECode

View file

@ -4,9 +4,9 @@ package keyctl
import ( import (
"fmt" "fmt"
"syscall"
"strings"
"strconv" "strconv"
"strings"
"syscall"
"unsafe" "unsafe"
) )
@ -17,7 +17,7 @@ const KEYCTL_DESCRIBE = 6
type KeySerial uint32 type KeySerial uint32
func JoinSessionKeyring(name string) (KeySerial, error) { func JoinSessionKeyring(name string) (KeySerial, error) {
var _name *byte = nil var _name *byte
var err error var err error
if len(name) > 0 { if len(name) > 0 {
@ -34,7 +34,7 @@ func JoinSessionKeyring(name string) (KeySerial, error) {
return KeySerial(sessKeyId), nil return KeySerial(sessKeyId), nil
} }
// modify permissions on a keyring by reading the current permissions, // ModKeyringPerm modifies permissions on a keyring by reading the current permissions,
// anding the bits with the given mask (clearing permissions) and setting // anding the bits with the given mask (clearing permissions) and setting
// additional permission bits // additional permission bits
func ModKeyringPerm(ringId KeySerial, mask, setbits uint32) error { func ModKeyringPerm(ringId KeySerial, mask, setbits uint32) error {
@ -64,4 +64,3 @@ func ModKeyringPerm(ringId KeySerial, mask, setbits uint32) error {
return nil return nil
} }

View file

@ -107,7 +107,7 @@ func SetFileLabel(path string, fileLabel string) error {
return nil return nil
} }
// Tell the kernel the label for all files to be created // SetFileCreateLabel tells the kernel the label for all files to be created
func SetFileCreateLabel(fileLabel string) error { func SetFileCreateLabel(fileLabel string) error {
if selinux.SelinuxEnabled() { if selinux.SelinuxEnabled() {
return selinux.Setfscreatecon(fileLabel) return selinux.Setfscreatecon(fileLabel)
@ -115,7 +115,7 @@ func SetFileCreateLabel(fileLabel string) error {
return nil return nil
} }
// Change the label of path to the filelabel string. // Relabel changes the label of path to the filelabel string.
// It changes the MCS label to s0 if shared is true. // It changes the MCS label to s0 if shared is true.
// This will allow all containers to share the content. // This will allow all containers to share the content.
func Relabel(path string, fileLabel string, shared bool) error { func Relabel(path string, fileLabel string, shared bool) error {

View file

@ -27,7 +27,8 @@ type Int32msg struct {
Value uint32 Value uint32
} }
// int32msg has the following representation // Serialize serializes the message.
// Int32msg has the following representation
// | nlattr len | nlattr type | // | nlattr len | nlattr type |
// | uint32 value | // | uint32 value |
func (msg *Int32msg) Serialize() []byte { func (msg *Int32msg) Serialize() []byte {
@ -43,7 +44,7 @@ func (msg *Int32msg) Len() int {
return syscall_NLA_HDRLEN + 4 return syscall_NLA_HDRLEN + 4
} }
// bytemsg has the following representation // Bytemsg has the following representation
// | nlattr len | nlattr type | // | nlattr len | nlattr type |
// | value | pad | // | value | pad |
type Bytemsg struct { type Bytemsg struct {

View file

@ -70,47 +70,47 @@ func (p *setnsProcess) start() (err error) {
err = p.cmd.Start() err = p.cmd.Start()
p.childPipe.Close() p.childPipe.Close()
if err != nil { if err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "starting setns process")
} }
if p.bootstrapData != nil { if p.bootstrapData != nil {
if _, err := io.Copy(p.parentPipe, p.bootstrapData); err != nil { if _, err := io.Copy(p.parentPipe, p.bootstrapData); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "copying bootstrap data to pipe")
} }
} }
if err = p.execSetns(); err != nil { if err = p.execSetns(); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "executing setns process")
} }
if len(p.cgroupPaths) > 0 { if len(p.cgroupPaths) > 0 {
if err := cgroups.EnterPid(p.cgroupPaths, p.pid()); err != nil { if err := cgroups.EnterPid(p.cgroupPaths, p.pid()); err != nil {
return newSystemError(err) return newSystemErrorWithCausef(err, "adding pid %d to cgroups", p.pid())
} }
} }
// set oom_score_adj // set oom_score_adj
if err := setOomScoreAdj(p.config.Config.OomScoreAdj, p.pid()); err != nil { if err := setOomScoreAdj(p.config.Config.OomScoreAdj, p.pid()); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "setting oom score")
} }
// set rlimits, this has to be done here because we lose permissions // set rlimits, this has to be done here because we lose permissions
// to raise the limits once we enter a user-namespace // to raise the limits once we enter a user-namespace
if err := setupRlimits(p.config.Rlimits, p.pid()); err != nil { if err := setupRlimits(p.config.Rlimits, p.pid()); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "setting rlimits for process")
} }
if err := utils.WriteJSON(p.parentPipe, p.config); err != nil { if err := utils.WriteJSON(p.parentPipe, p.config); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "writing config to pipe")
} }
if err := syscall.Shutdown(int(p.parentPipe.Fd()), syscall.SHUT_WR); err != nil { if err := syscall.Shutdown(int(p.parentPipe.Fd()), syscall.SHUT_WR); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "calling shutdown on init pipe")
} }
// wait for the child process to fully complete and receive an error message // wait for the child process to fully complete and receive an error message
// if one was encoutered // if one was encoutered
var ierr *genericError var ierr *genericError
if err := json.NewDecoder(p.parentPipe).Decode(&ierr); err != nil && err != io.EOF { if err := json.NewDecoder(p.parentPipe).Decode(&ierr); err != nil && err != io.EOF {
return newSystemError(err) return newSystemErrorWithCause(err, "decoding init error from pipe")
} }
// Must be done after Shutdown so the child will exit and we can wait for it. // Must be done after Shutdown so the child will exit and we can wait for it.
if ierr != nil { if ierr != nil {
p.wait() p.wait()
return newSystemError(ierr) return ierr
} }
return nil return nil
} }
@ -123,7 +123,7 @@ func (p *setnsProcess) execSetns() error {
status, err := p.cmd.Process.Wait() status, err := p.cmd.Process.Wait()
if err != nil { if err != nil {
p.cmd.Wait() p.cmd.Wait()
return newSystemError(err) return newSystemErrorWithCause(err, "waiting on setns process to finish")
} }
if !status.Success() { if !status.Success() {
p.cmd.Wait() p.cmd.Wait()
@ -132,7 +132,7 @@ func (p *setnsProcess) execSetns() error {
var pid *pid var pid *pid
if err := json.NewDecoder(p.parentPipe).Decode(&pid); err != nil { if err := json.NewDecoder(p.parentPipe).Decode(&pid); err != nil {
p.cmd.Wait() p.cmd.Wait()
return newSystemError(err) return newSystemErrorWithCause(err, "reading pid from init pipe")
} }
process, err := os.FindProcess(pid.Pid) process, err := os.FindProcess(pid.Pid)
if err != nil { if err != nil {
@ -231,26 +231,26 @@ func (p *initProcess) start() error {
p.childPipe.Close() p.childPipe.Close()
if err != nil { if err != nil {
p.process.ops = nil p.process.ops = nil
return newSystemError(err) return newSystemErrorWithCause(err, "starting init process command")
} }
if _, err := io.Copy(p.parentPipe, p.bootstrapData); err != nil { if _, err := io.Copy(p.parentPipe, p.bootstrapData); err != nil {
return err return err
} }
if err := p.execSetns(); err != nil { if err := p.execSetns(); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "running exec setns process for init")
} }
// Save the standard descriptor names before the container process // Save the standard descriptor names before the container process
// can potentially move them (e.g., via dup2()). If we don't do this now, // can potentially move them (e.g., via dup2()). If we don't do this now,
// we won't know at checkpoint time which file descriptor to look up. // we won't know at checkpoint time which file descriptor to look up.
fds, err := getPipeFds(p.pid()) fds, err := getPipeFds(p.pid())
if err != nil { if err != nil {
return newSystemError(err) return newSystemErrorWithCausef(err, "getting pipe fds for pid %d", p.pid())
} }
p.setExternalDescriptors(fds) p.setExternalDescriptors(fds)
// Do this before syncing with child so that no children // Do this before syncing with child so that no children
// can escape the cgroup // can escape the cgroup
if err := p.manager.Apply(p.pid()); err != nil { if err := p.manager.Apply(p.pid()); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "applying cgroup configuration for process")
} }
defer func() { defer func() {
if err != nil { if err != nil {
@ -259,10 +259,10 @@ func (p *initProcess) start() error {
} }
}() }()
if err := p.createNetworkInterfaces(); err != nil { if err := p.createNetworkInterfaces(); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "creating nework interfaces")
} }
if err := p.sendConfig(); err != nil { if err := p.sendConfig(); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "sending config to init process")
} }
var ( var (
procSync syncT procSync syncT
@ -278,21 +278,21 @@ loop:
if err == io.EOF { if err == io.EOF {
break loop break loop
} }
return newSystemError(err) return newSystemErrorWithCause(err, "decoding sync type from init pipe")
} }
switch procSync.Type { switch procSync.Type {
case procReady: case procReady:
if err := p.manager.Set(p.config.Config); err != nil { if err := p.manager.Set(p.config.Config); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "setting cgroup config for ready process")
} }
// set oom_score_adj // set oom_score_adj
if err := setOomScoreAdj(p.config.Config.OomScoreAdj, p.pid()); err != nil { if err := setOomScoreAdj(p.config.Config.OomScoreAdj, p.pid()); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "setting oom score for ready process")
} }
// set rlimits, this has to be done here because we lose permissions // set rlimits, this has to be done here because we lose permissions
// to raise the limits once we enter a user-namespace // to raise the limits once we enter a user-namespace
if err := setupRlimits(p.config.Rlimits, p.pid()); err != nil { if err := setupRlimits(p.config.Rlimits, p.pid()); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "setting rlimits for ready process")
} }
// call prestart hooks // call prestart hooks
if !p.config.Config.Namespaces.Contains(configs.NEWNS) { if !p.config.Config.Namespaces.Contains(configs.NEWNS) {
@ -303,16 +303,16 @@ loop:
Pid: p.pid(), Pid: p.pid(),
Root: p.config.Config.Rootfs, Root: p.config.Config.Rootfs,
} }
for _, hook := range p.config.Config.Hooks.Prestart { for i, hook := range p.config.Config.Hooks.Prestart {
if err := hook.Run(s); err != nil { if err := hook.Run(s); err != nil {
return newSystemError(err) return newSystemErrorWithCausef(err, "running prestart hook %d", i)
} }
} }
} }
} }
// Sync with child. // Sync with child.
if err := utils.WriteJSON(p.parentPipe, syncT{procRun}); err != nil { if err := utils.WriteJSON(p.parentPipe, syncT{procRun}); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "reading syncT run type")
} }
sentRun = true sentRun = true
case procHooks: case procHooks:
@ -324,22 +324,22 @@ loop:
Root: p.config.Config.Rootfs, Root: p.config.Config.Rootfs,
BundlePath: utils.SearchLabels(p.config.Config.Labels, "bundle"), BundlePath: utils.SearchLabels(p.config.Config.Labels, "bundle"),
} }
for _, hook := range p.config.Config.Hooks.Prestart { for i, hook := range p.config.Config.Hooks.Prestart {
if err := hook.Run(s); err != nil { if err := hook.Run(s); err != nil {
return newSystemError(err) return newSystemErrorWithCausef(err, "running prestart hook %d", i)
} }
} }
} }
// Sync with child. // Sync with child.
if err := utils.WriteJSON(p.parentPipe, syncT{procResume}); err != nil { if err := utils.WriteJSON(p.parentPipe, syncT{procResume}); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "reading syncT resume type")
} }
sentResume = true sentResume = true
case procError: case procError:
// wait for the child process to fully complete and receive an error message // wait for the child process to fully complete and receive an error message
// if one was encoutered // if one was encoutered
if err := dec.Decode(&ierr); err != nil && err != io.EOF { if err := dec.Decode(&ierr); err != nil && err != io.EOF {
return newSystemError(err) return newSystemErrorWithCause(err, "decoding proc error from init")
} }
if ierr != nil { if ierr != nil {
break loop break loop
@ -347,22 +347,22 @@ loop:
// Programmer error. // Programmer error.
panic("No error following JSON procError payload.") panic("No error following JSON procError payload.")
default: default:
return newSystemError(fmt.Errorf("invalid JSON synchronisation payload from child")) return newSystemError(fmt.Errorf("invalid JSON payload from child"))
} }
} }
if !sentRun { if !sentRun {
return newSystemError(fmt.Errorf("could not synchronise with container process: %v", ierr)) return newSystemErrorWithCause(ierr, "container init failed")
} }
if p.config.Config.Namespaces.Contains(configs.NEWNS) && !sentResume { if p.config.Config.Namespaces.Contains(configs.NEWNS) && !sentResume {
return newSystemError(fmt.Errorf("could not synchronise after executing prestart hooks with container process")) return newSystemError(fmt.Errorf("could not synchronise after executing prestart hooks with container process"))
} }
if err := syscall.Shutdown(int(p.parentPipe.Fd()), syscall.SHUT_WR); err != nil { if err := syscall.Shutdown(int(p.parentPipe.Fd()), syscall.SHUT_WR); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "shutting down init pipe")
} }
// Must be done after Shutdown so the child will exit and we can wait for it. // Must be done after Shutdown so the child will exit and we can wait for it.
if ierr != nil { if ierr != nil {
p.wait() p.wait()
return newSystemError(ierr) return ierr
} }
return nil return nil
} }

View file

@ -25,7 +25,7 @@ import (
const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
// setupDev returns true if /dev needs to be set up. // needsSetupDev returns true if /dev needs to be set up.
func needsSetupDev(config *configs.Config) bool { func needsSetupDev(config *configs.Config) bool {
for _, m := range config.Mounts { for _, m := range config.Mounts {
if m.Device == "bind" && (m.Destination == "/dev" || m.Destination == "/dev/") { if m.Device == "bind" && (m.Destination == "/dev" || m.Destination == "/dev/") {
@ -39,35 +39,35 @@ func needsSetupDev(config *configs.Config) bool {
// new mount namespace. // new mount namespace.
func setupRootfs(config *configs.Config, console *linuxConsole, pipe io.ReadWriter) (err error) { func setupRootfs(config *configs.Config, console *linuxConsole, pipe io.ReadWriter) (err error) {
if err := prepareRoot(config); err != nil { if err := prepareRoot(config); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "preparing rootfs")
} }
setupDev := needsSetupDev(config) setupDev := needsSetupDev(config)
for _, m := range config.Mounts { for _, m := range config.Mounts {
for _, precmd := range m.PremountCmds { for _, precmd := range m.PremountCmds {
if err := mountCmd(precmd); err != nil { if err := mountCmd(precmd); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "running premount command")
} }
} }
if err := mountToRootfs(m, config.Rootfs, config.MountLabel); err != nil { if err := mountToRootfs(m, config.Rootfs, config.MountLabel); err != nil {
return newSystemError(err) return newSystemErrorWithCausef(err, "mounting %q to rootfs %q", m.Destination, config.Rootfs)
} }
for _, postcmd := range m.PostmountCmds { for _, postcmd := range m.PostmountCmds {
if err := mountCmd(postcmd); err != nil { if err := mountCmd(postcmd); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "running postmount command")
} }
} }
} }
if setupDev { if setupDev {
if err := createDevices(config); err != nil { if err := createDevices(config); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "creating device nodes")
} }
if err := setupPtmx(config, console); err != nil { if err := setupPtmx(config, console); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "setting up ptmx")
} }
if err := setupDevSymlinks(config.Rootfs); err != nil { if err := setupDevSymlinks(config.Rootfs); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "setting up /dev symlinks")
} }
} }
// Signal the parent to run the pre-start hooks. // Signal the parent to run the pre-start hooks.
@ -78,7 +78,7 @@ func setupRootfs(config *configs.Config, console *linuxConsole, pipe io.ReadWrit
return err return err
} }
if err := syscall.Chdir(config.Rootfs); err != nil { if err := syscall.Chdir(config.Rootfs); err != nil {
return newSystemError(err) return newSystemErrorWithCausef(err, "changing dir to %q", config.Rootfs)
} }
if config.NoPivotRoot { if config.NoPivotRoot {
err = msMoveRoot(config.Rootfs) err = msMoveRoot(config.Rootfs)
@ -86,11 +86,11 @@ func setupRootfs(config *configs.Config, console *linuxConsole, pipe io.ReadWrit
err = pivotRoot(config.Rootfs, config.PivotDir) err = pivotRoot(config.Rootfs, config.PivotDir)
} }
if err != nil { if err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "jailing process inside rootfs")
} }
if setupDev { if setupDev {
if err := reOpenDevNull(); err != nil { if err := reOpenDevNull(); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "reopening /dev/null inside container")
} }
} }
// remount dev as ro if specifed // remount dev as ro if specifed
@ -98,7 +98,7 @@ func setupRootfs(config *configs.Config, console *linuxConsole, pipe io.ReadWrit
if m.Destination == "/dev" { if m.Destination == "/dev" {
if m.Flags&syscall.MS_RDONLY != 0 { if m.Flags&syscall.MS_RDONLY != 0 {
if err := remountReadonly(m.Destination); err != nil { if err := remountReadonly(m.Destination); err != nil {
return newSystemError(err) return newSystemErrorWithCausef(err, "remounting %q as readonly", m.Destination)
} }
} }
break break
@ -107,7 +107,7 @@ func setupRootfs(config *configs.Config, console *linuxConsole, pipe io.ReadWrit
// set rootfs ( / ) as readonly // set rootfs ( / ) as readonly
if config.Readonlyfs { if config.Readonlyfs {
if err := setReadonly(); err != nil { if err := setReadonly(); err != nil {
return newSystemError(err) return newSystemErrorWithCause(err, "setting rootfs as readonly")
} }
} }
syscall.Umask(0022) syscall.Umask(0022)
@ -115,14 +115,12 @@ func setupRootfs(config *configs.Config, console *linuxConsole, pipe io.ReadWrit
} }
func mountCmd(cmd configs.Command) error { func mountCmd(cmd configs.Command) error {
command := exec.Command(cmd.Path, cmd.Args[:]...) command := exec.Command(cmd.Path, cmd.Args[:]...)
command.Env = cmd.Env command.Env = cmd.Env
command.Dir = cmd.Dir command.Dir = cmd.Dir
if out, err := command.CombinedOutput(); err != nil { if out, err := command.CombinedOutput(); err != nil {
return fmt.Errorf("%#v failed: %s: %v", cmd, string(out), err) return fmt.Errorf("%#v failed: %s: %v", cmd, string(out), err)
} }
return nil return nil
} }
@ -266,8 +264,10 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
if m.Flags&syscall.MS_RDONLY != 0 { if m.Flags&syscall.MS_RDONLY != 0 {
// remount cgroup root as readonly // remount cgroup root as readonly
mcgrouproot := &configs.Mount{ mcgrouproot := &configs.Mount{
Source: m.Destination,
Device: "bind",
Destination: m.Destination, Destination: m.Destination,
Flags: defaultMountFlags | syscall.MS_RDONLY, Flags: defaultMountFlags | syscall.MS_RDONLY | syscall.MS_BIND,
} }
if err := remount(mcgrouproot, rootfs); err != nil { if err := remount(mcgrouproot, rootfs); err != nil {
return err return err
@ -515,10 +515,10 @@ func getParentMount(rootfs string) (string, string, error) {
} }
// Make parent mount private if it was shared // Make parent mount private if it was shared
func rootfsParentMountPrivate(config *configs.Config) error { func rootfsParentMountPrivate(rootfs string) error {
sharedMount := false sharedMount := false
parentMount, optionalOpts, err := getParentMount(config.Rootfs) parentMount, optionalOpts, err := getParentMount(rootfs)
if err != nil { if err != nil {
return err return err
} }
@ -550,10 +550,11 @@ func prepareRoot(config *configs.Config) error {
if err := syscall.Mount("", "/", "", uintptr(flag), ""); err != nil { if err := syscall.Mount("", "/", "", uintptr(flag), ""); err != nil {
return err return err
} }
if config.NoPivotRoot {
if err := rootfsParentMountPrivate(config); err != nil { if err := rootfsParentMountPrivate(config.Rootfs); err != nil {
return err return err
} }
}
return syscall.Mount(config.Rootfs, config.Rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, "") return syscall.Mount(config.Rootfs, config.Rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, "")
} }
@ -594,9 +595,16 @@ func pivotRoot(rootfs, pivotBaseDir string) (err error) {
err = errVal err = errVal
} }
}() }()
if err := syscall.PivotRoot(rootfs, pivotDir); err != nil {
// Make the parent mount private
if err := rootfsParentMountPrivate(rootfs); err != nil {
return err
}
// Try again
if err := syscall.PivotRoot(rootfs, pivotDir); err != nil { if err := syscall.PivotRoot(rootfs, pivotDir); err != nil {
return fmt.Errorf("pivot_root %s", err) return fmt.Errorf("pivot_root %s", err)
} }
}
if err := syscall.Chdir("/"); err != nil { if err := syscall.Chdir("/"); err != nil {
return fmt.Errorf("chdir / %s", err) return fmt.Errorf("chdir / %s", err)
} }

View file

@ -10,7 +10,7 @@ import (
var ErrSeccompNotEnabled = errors.New("seccomp: config provided but seccomp not supported") var ErrSeccompNotEnabled = errors.New("seccomp: config provided but seccomp not supported")
// Seccomp not supported, do nothing // InitSeccomp does nothing because seccomp is not supported.
func InitSeccomp(config *configs.Seccomp) error { func InitSeccomp(config *configs.Seccomp) error {
if config != nil { if config != nil {
return ErrSeccompNotEnabled return ErrSeccompNotEnabled

View file

@ -0,0 +1,499 @@
// +build linux
package selinux
import (
"bufio"
"crypto/rand"
"encoding/binary"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"syscall"
"github.com/opencontainers/runc/libcontainer/system"
)
const (
Enforcing = 1
Permissive = 0
Disabled = -1
selinuxDir = "/etc/selinux/"
selinuxConfig = selinuxDir + "config"
selinuxTypeTag = "SELINUXTYPE"
selinuxTag = "SELINUX"
selinuxPath = "/sys/fs/selinux"
xattrNameSelinux = "security.selinux"
stRdOnly = 0x01
)
var (
assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`)
mcsList = make(map[string]bool)
mcsLock sync.Mutex
selinuxfs = "unknown"
selinuxEnabled = false // Stores whether selinux is currently enabled
selinuxEnabledChecked = false // Stores whether selinux enablement has been checked or established yet
)
type SELinuxContext map[string]string
// SetDisabled disables selinux support for the package
func SetDisabled() {
selinuxEnabled, selinuxEnabledChecked = false, true
}
// getSelinuxMountPoint returns the path to the mountpoint of an selinuxfs
// filesystem or an empty string if no mountpoint is found. Selinuxfs is
// a proc-like pseudo-filesystem that exposes the selinux policy API to
// processes. The existence of an selinuxfs mount is used to determine
// whether selinux is currently enabled or not.
func getSelinuxMountPoint() string {
if selinuxfs != "unknown" {
return selinuxfs
}
selinuxfs = ""
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return selinuxfs
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
txt := scanner.Text()
// Safe as mountinfo encodes mountpoints with spaces as \040.
sepIdx := strings.Index(txt, " - ")
if sepIdx == -1 {
continue
}
if !strings.Contains(txt[sepIdx:], "selinuxfs") {
continue
}
fields := strings.Split(txt, " ")
if len(fields) < 5 {
continue
}
selinuxfs = fields[4]
break
}
if selinuxfs != "" {
var buf syscall.Statfs_t
syscall.Statfs(selinuxfs, &buf)
if (buf.Flags & stRdOnly) == 1 {
selinuxfs = ""
}
}
return selinuxfs
}
// SelinuxEnabled returns whether selinux is currently enabled.
func SelinuxEnabled() bool {
if selinuxEnabledChecked {
return selinuxEnabled
}
selinuxEnabledChecked = true
if fs := getSelinuxMountPoint(); fs != "" {
if con, _ := Getcon(); con != "kernel" {
selinuxEnabled = true
}
}
return selinuxEnabled
}
func readConfig(target string) (value string) {
var (
val, key string
bufin *bufio.Reader
)
in, err := os.Open(selinuxConfig)
if err != nil {
return ""
}
defer in.Close()
bufin = bufio.NewReader(in)
for done := false; !done; {
var line string
if line, err = bufin.ReadString('\n'); err != nil {
if err != io.EOF {
return ""
}
done = true
}
line = strings.TrimSpace(line)
if len(line) == 0 {
// Skip blank lines
continue
}
if line[0] == ';' || line[0] == '#' {
// Skip comments
continue
}
if groups := assignRegex.FindStringSubmatch(line); groups != nil {
key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
if key == target {
return strings.Trim(val, "\"")
}
}
}
return ""
}
func getSELinuxPolicyRoot() string {
return selinuxDir + readConfig(selinuxTypeTag)
}
func readCon(name string) (string, error) {
var val string
in, err := os.Open(name)
if err != nil {
return "", err
}
defer in.Close()
_, err = fmt.Fscanf(in, "%s", &val)
return val, err
}
// Setfilecon sets the SELinux label for this path or returns an error.
func Setfilecon(path string, scon string) error {
return system.Lsetxattr(path, xattrNameSelinux, []byte(scon), 0)
}
// Getfilecon returns the SELinux label for this path or returns an error.
func Getfilecon(path string) (string, error) {
con, err := system.Lgetxattr(path, xattrNameSelinux)
if err != nil {
return "", err
}
// Trim the NUL byte at the end of the byte buffer, if present.
if len(con) > 0 && con[len(con)-1] == '\x00' {
con = con[:len(con)-1]
}
return string(con), nil
}
func Setfscreatecon(scon string) error {
return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid()), scon)
}
func Getfscreatecon() (string, error) {
return readCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid()))
}
// Getcon returns the SELinux label of the current process thread, or an error.
func Getcon() (string, error) {
return readCon(fmt.Sprintf("/proc/self/task/%d/attr/current", syscall.Gettid()))
}
// Getpidcon returns the SELinux label of the given pid, or an error.
func Getpidcon(pid int) (string, error) {
return readCon(fmt.Sprintf("/proc/%d/attr/current", pid))
}
func Getexeccon() (string, error) {
return readCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()))
}
func writeCon(name string, val string) error {
out, err := os.OpenFile(name, os.O_WRONLY, 0)
if err != nil {
return err
}
defer out.Close()
if val != "" {
_, err = out.Write([]byte(val))
} else {
_, err = out.Write(nil)
}
return err
}
func Setexeccon(scon string) error {
return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()), scon)
}
func (c SELinuxContext) Get() string {
return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"])
}
func NewContext(scon string) SELinuxContext {
c := make(SELinuxContext)
if len(scon) != 0 {
con := strings.SplitN(scon, ":", 4)
c["user"] = con[0]
c["role"] = con[1]
c["type"] = con[2]
c["level"] = con[3]
}
return c
}
func ReserveLabel(scon string) {
if len(scon) != 0 {
con := strings.SplitN(scon, ":", 4)
mcsAdd(con[3])
}
}
func selinuxEnforcePath() string {
return fmt.Sprintf("%s/enforce", selinuxPath)
}
func SelinuxGetEnforce() int {
var enforce int
enforceS, err := readCon(selinuxEnforcePath())
if err != nil {
return -1
}
enforce, err = strconv.Atoi(string(enforceS))
if err != nil {
return -1
}
return enforce
}
func SelinuxSetEnforce(mode int) error {
return writeCon(selinuxEnforcePath(), fmt.Sprintf("%d", mode))
}
func SelinuxGetEnforceMode() int {
switch readConfig(selinuxTag) {
case "enforcing":
return Enforcing
case "permissive":
return Permissive
}
return Disabled
}
func mcsAdd(mcs string) error {
mcsLock.Lock()
defer mcsLock.Unlock()
if mcsList[mcs] {
return fmt.Errorf("MCS Label already exists")
}
mcsList[mcs] = true
return nil
}
func mcsDelete(mcs string) {
mcsLock.Lock()
mcsList[mcs] = false
mcsLock.Unlock()
}
func IntToMcs(id int, catRange uint32) string {
var (
SETSIZE = int(catRange)
TIER = SETSIZE
ORD = id
)
if id < 1 || id > 523776 {
return ""
}
for ORD > TIER {
ORD = ORD - TIER
TIER--
}
TIER = SETSIZE - TIER
ORD = ORD + TIER
return fmt.Sprintf("s0:c%d,c%d", TIER, ORD)
}
func uniqMcs(catRange uint32) string {
var (
n uint32
c1, c2 uint32
mcs string
)
for {
binary.Read(rand.Reader, binary.LittleEndian, &n)
c1 = n % catRange
binary.Read(rand.Reader, binary.LittleEndian, &n)
c2 = n % catRange
if c1 == c2 {
continue
} else {
if c1 > c2 {
t := c1
c1 = c2
c2 = t
}
}
mcs = fmt.Sprintf("s0:c%d,c%d", c1, c2)
if err := mcsAdd(mcs); err != nil {
continue
}
break
}
return mcs
}
func FreeLxcContexts(scon string) {
if len(scon) != 0 {
con := strings.SplitN(scon, ":", 4)
mcsDelete(con[3])
}
}
func GetLxcContexts() (processLabel string, fileLabel string) {
var (
val, key string
bufin *bufio.Reader
)
if !SelinuxEnabled() {
return "", ""
}
lxcPath := fmt.Sprintf("%s/contexts/lxc_contexts", getSELinuxPolicyRoot())
in, err := os.Open(lxcPath)
if err != nil {
return "", ""
}
defer in.Close()
bufin = bufio.NewReader(in)
for done := false; !done; {
var line string
if line, err = bufin.ReadString('\n'); err != nil {
if err == io.EOF {
done = true
} else {
goto exit
}
}
line = strings.TrimSpace(line)
if len(line) == 0 {
// Skip blank lines
continue
}
if line[0] == ';' || line[0] == '#' {
// Skip comments
continue
}
if groups := assignRegex.FindStringSubmatch(line); groups != nil {
key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
if key == "process" {
processLabel = strings.Trim(val, "\"")
}
if key == "file" {
fileLabel = strings.Trim(val, "\"")
}
}
}
if processLabel == "" || fileLabel == "" {
return "", ""
}
exit:
// mcs := IntToMcs(os.Getpid(), 1024)
mcs := uniqMcs(1024)
scon := NewContext(processLabel)
scon["level"] = mcs
processLabel = scon.Get()
scon = NewContext(fileLabel)
scon["level"] = mcs
fileLabel = scon.Get()
return processLabel, fileLabel
}
func SecurityCheckContext(val string) error {
return writeCon(fmt.Sprintf("%s.context", selinuxPath), val)
}
func CopyLevel(src, dest string) (string, error) {
if src == "" {
return "", nil
}
if err := SecurityCheckContext(src); err != nil {
return "", err
}
if err := SecurityCheckContext(dest); err != nil {
return "", err
}
scon := NewContext(src)
tcon := NewContext(dest)
mcsDelete(tcon["level"])
mcsAdd(scon["level"])
tcon["level"] = scon["level"]
return tcon.Get(), nil
}
// Prevent users from relabing system files
func badPrefix(fpath string) error {
var badprefixes = []string{"/usr"}
for _, prefix := range badprefixes {
if fpath == prefix || strings.HasPrefix(fpath, fmt.Sprintf("%s/", prefix)) {
return fmt.Errorf("Relabeling content in %s is not allowed.", prefix)
}
}
return nil
}
// Chcon changes the fpath file object to the SELinux label scon.
// If the fpath is a directory and recurse is true Chcon will walk the
// directory tree setting the label
func Chcon(fpath string, scon string, recurse bool) error {
if scon == "" {
return nil
}
if err := badPrefix(fpath); err != nil {
return err
}
callback := func(p string, info os.FileInfo, err error) error {
return Setfilecon(p, scon)
}
if recurse {
return filepath.Walk(fpath, callback)
}
return Setfilecon(fpath, scon)
}
// DupSecOpt takes an SELinux process label and returns security options that
// can will set the SELinux Type and Level for future container processes
func DupSecOpt(src string) []string {
if src == "" {
return nil
}
con := NewContext(src)
if con["user"] == "" ||
con["role"] == "" ||
con["type"] == "" ||
con["level"] == "" {
return nil
}
return []string{"label:user:" + con["user"],
"label:role:" + con["role"],
"label:type:" + con["type"],
"label:level:" + con["level"]}
}
// DisableSecOpt returns a security opt that can be used to disabling SELinux
// labeling support for future container processes
func DisableSecOpt() []string {
return []string{"label:disable"}
}

View file

@ -2,14 +2,14 @@ package stacktrace
import "runtime" import "runtime"
// Caputure captures a stacktrace for the current calling go program // Capture captures a stacktrace for the current calling go program
// //
// skip is the number of frames to skip // skip is the number of frames to skip
func Capture(userSkip int) Stacktrace { func Capture(userSkip int) Stacktrace {
var ( var (
skip = userSkip + 1 // add one for our own function skip = userSkip + 1 // add one for our own function
frames []Frame frames []Frame
prevPc uintptr = 0 prevPc uintptr
) )
for i := skip; ; i++ { for i := skip; ; i++ {
pc, file, line, ok := runtime.Caller(i) pc, file, line, ok := runtime.Caller(i)

View file

@ -100,17 +100,12 @@ func Setctty() error {
return nil return nil
} }
/* // RunningInUserNS detects whether we are currently running in a user namespace.
* Detect whether we are currently running in a user namespace. // Copied from github.com/lxc/lxd/shared/util.go
* Copied from github.com/lxc/lxd/shared/util.go
*/
func RunningInUserNS() bool { func RunningInUserNS() bool {
file, err := os.Open("/proc/self/uid_map") file, err := os.Open("/proc/self/uid_map")
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() defer file.Close()

View file

@ -49,7 +49,7 @@ type Process struct {
// ApparmorProfile specified the apparmor profile for the container. (this field is platform dependent) // ApparmorProfile specified the apparmor profile for the container. (this field is platform dependent)
ApparmorProfile string `json:"apparmorProfile,omitempty" platform:"linux"` ApparmorProfile string `json:"apparmorProfile,omitempty" platform:"linux"`
// SelinuxProcessLabel specifies the selinux context that the container process is run as. (this field is platform dependent) // SelinuxLabel specifies the selinux context that the container process is run as. (this field is platform dependent)
SelinuxLabel string `json:"selinuxLabel,omitempty" platform:"linux"` SelinuxLabel string `json:"selinuxLabel,omitempty" platform:"linux"`
} }
@ -99,6 +99,7 @@ type Hook struct {
Path string `json:"path"` Path string `json:"path"`
Args []string `json:"args,omitempty"` Args []string `json:"args,omitempty"`
Env []string `json:"env,omitempty"` Env []string `json:"env,omitempty"`
Timeout *int `json:"timeout,omitempty"`
} }
// Hooks for container setup and teardown // Hooks for container setup and teardown
@ -128,13 +129,19 @@ type Linux struct {
// If resources are specified, the cgroups at CgroupsPath will be updated based on resources. // If resources are specified, the cgroups at CgroupsPath will be updated based on resources.
CgroupsPath *string `json:"cgroupsPath,omitempty"` CgroupsPath *string `json:"cgroupsPath,omitempty"`
// Namespaces contains the namespaces that are created and/or joined by the container // Namespaces contains the namespaces that are created and/or joined by the container
Namespaces []Namespace `json:"namespaces"` Namespaces []Namespace `json:"namespaces,omitempty"`
// Devices are a list of device nodes that are created for the container // Devices are a list of device nodes that are created for the container
Devices []Device `json:"devices"` Devices []Device `json:"devices,omitempty"`
// Seccomp specifies the seccomp security settings for the container. // Seccomp specifies the seccomp security settings for the container.
Seccomp *Seccomp `json:"seccomp,omitempty"` Seccomp *Seccomp `json:"seccomp,omitempty"`
// RootfsPropagation is the rootfs mount propagation mode for the container. // RootfsPropagation is the rootfs mount propagation mode for the container.
RootfsPropagation string `json:"rootfsPropagation,omitempty"` RootfsPropagation string `json:"rootfsPropagation,omitempty"`
// MaskedPaths masks over the provided paths inside the container.
MaskedPaths []string `json:"maskedPaths,omitempty"`
// ReadonlyPaths sets the provided paths as RO inside the container.
ReadonlyPaths []string `json:"readonlyPaths,omitempty"`
// MountLabel specifies the selinux context for the mounts in the container.
MountLabel string `json:"mountLabel,omitempty"`
} }
// Namespace is the configuration for a Linux namespace // Namespace is the configuration for a Linux namespace

View file

@ -6,12 +6,12 @@ const (
// VersionMajor is for an API incompatible changes // VersionMajor is for an API incompatible changes
VersionMajor = 0 VersionMajor = 0
// VersionMinor is for functionality in a backwards-compatible manner // VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 4 VersionMinor = 6
// VersionPatch is for backwards-compatible bug fixes // VersionPatch is for backwards-compatible bug fixes
VersionPatch = 0 VersionPatch = 0
// VersionDev indicates development branch. Releases will be empty string. // VersionDev indicates development branch. Releases will be empty string.
VersionDev = "" VersionDev = "-dev"
) )
// Version is the specification version that the package types support. // Version is the specification version that the package types support.