diff --git a/libcontainer/cgroups/fs/apply_raw.go b/libcontainer/cgroups/fs/apply_raw.go index a906d35b..0c4d207e 100644 --- a/libcontainer/cgroups/fs/apply_raw.go +++ b/libcontainer/cgroups/fs/apply_raw.go @@ -23,7 +23,6 @@ var ( &MemoryGroup{}, &CpuGroup{}, &CpuacctGroup{}, - &PidsGroup{}, &BlkioGroup{}, &HugetlbGroup{}, &NetClsGroup{}, @@ -180,24 +179,11 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) { } func (m *Manager) Set(container *configs.Config) error { - for _, sys := range subsystems { - // We can't set this here, because after being applied, memcg doesn't - // allow a non-empty cgroup from having its limits changed. - if sys.Name() == "memory" { + for name, path := range m.Paths { + sys, err := subsystems.Get(name) + if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) { continue } - - // Generate fake cgroup data. - d, err := getCgroupData(container.Cgroups, -1) - if err != nil { - return err - } - // Get the path, but don't error out if the cgroup wasn't found. - path, err := d.path(sys.Name()) - if err != nil && !cgroups.IsNotFound(err) { - return err - } - if err := sys.Set(path, container.Cgroups); err != nil { return err } diff --git a/libcontainer/cgroups/fs/blkio.go b/libcontainer/cgroups/fs/blkio.go index a142cb99..518cb63f 100644 --- a/libcontainer/cgroups/fs/blkio.go +++ b/libcontainer/cgroups/fs/blkio.go @@ -22,10 +22,15 @@ func (s *BlkioGroup) Name() string { } func (s *BlkioGroup) Apply(d *cgroupData) error { - _, err := d.join("blkio") + dir, err := d.join("blkio") if err != nil && !cgroups.IsNotFound(err) { return err } + + if err := s.Set(dir, d.config); err != nil { + return err + } + return nil } diff --git a/libcontainer/cgroups/fs/cpu.go b/libcontainer/cgroups/fs/cpu.go index a4ef28a6..ad5f427e 100644 --- a/libcontainer/cgroups/fs/cpu.go +++ b/libcontainer/cgroups/fs/cpu.go @@ -22,10 +22,15 @@ func (s *CpuGroup) Name() string { func (s *CpuGroup) Apply(d *cgroupData) error { // We always want to join the cpu group, to allow fair cpu scheduling // on a container basis - _, err := d.join("cpu") + dir, err := d.join("cpu") if err != nil && !cgroups.IsNotFound(err) { return err } + + if err := s.Set(dir, d.config); err != nil { + return err + } + return nil } diff --git a/libcontainer/cgroups/fs/cpuset.go b/libcontainer/cgroups/fs/cpuset.go index a9e3ab72..b7632108 100644 --- a/libcontainer/cgroups/fs/cpuset.go +++ b/libcontainer/cgroups/fs/cpuset.go @@ -63,6 +63,11 @@ func (s *CpusetGroup) ApplyDir(dir string, cgroup *configs.Cgroup, pid int) erro if err := s.ensureParent(dir, root); err != nil { return err } + // the default values inherit from parent cgroup are already set in + // s.ensureParent, cover these if we have our own + if err := s.Set(dir, cgroup); err != nil { + return err + } // because we are not using d.join we need to place the pid into the procs file // unlike the other subsystems if err := writeFile(dir, "cgroup.procs", strconv.Itoa(pid)); err != nil { diff --git a/libcontainer/cgroups/fs/devices.go b/libcontainer/cgroups/fs/devices.go index a41ce801..a9883eb4 100644 --- a/libcontainer/cgroups/fs/devices.go +++ b/libcontainer/cgroups/fs/devices.go @@ -15,12 +15,17 @@ func (s *DevicesGroup) Name() string { } func (s *DevicesGroup) Apply(d *cgroupData) error { - _, err := d.join("devices") + dir, err := d.join("devices") if err != nil { // We will return error even it's `not found` error, devices // cgroup is hard requirement for container's security. return err } + + if err := s.Set(dir, d.config); err != nil { + return err + } + return nil } diff --git a/libcontainer/cgroups/fs/freezer.go b/libcontainer/cgroups/fs/freezer.go index e70dfe3b..6aaad4e0 100644 --- a/libcontainer/cgroups/fs/freezer.go +++ b/libcontainer/cgroups/fs/freezer.go @@ -19,10 +19,15 @@ func (s *FreezerGroup) Name() string { } func (s *FreezerGroup) Apply(d *cgroupData) error { - _, err := d.join("freezer") + dir, err := d.join("freezer") if err != nil && !cgroups.IsNotFound(err) { return err } + + if err := s.Set(dir, d.config); err != nil { + return err + } + return nil } diff --git a/libcontainer/cgroups/fs/hugetlb.go b/libcontainer/cgroups/fs/hugetlb.go index 2f972771..ca106da4 100644 --- a/libcontainer/cgroups/fs/hugetlb.go +++ b/libcontainer/cgroups/fs/hugetlb.go @@ -19,10 +19,15 @@ func (s *HugetlbGroup) Name() string { } func (s *HugetlbGroup) Apply(d *cgroupData) error { - _, err := d.join("hugetlb") + dir, err := d.join("hugetlb") if err != nil && !cgroups.IsNotFound(err) { return err } + + if err := s.Set(dir, d.config); err != nil { + return err + } + return nil } diff --git a/libcontainer/cgroups/fs/memory.go b/libcontainer/cgroups/fs/memory.go index 0329692a..6b9687ca 100644 --- a/libcontainer/cgroups/fs/memory.go +++ b/libcontainer/cgroups/fs/memory.go @@ -32,6 +32,7 @@ func (s *MemoryGroup) Apply(d *cgroupData) (err error) { return err } } + if err := s.Set(path, d.config); err != nil { return err } diff --git a/libcontainer/cgroups/fs/net_cls.go b/libcontainer/cgroups/fs/net_cls.go index 8a4054ba..63823731 100644 --- a/libcontainer/cgroups/fs/net_cls.go +++ b/libcontainer/cgroups/fs/net_cls.go @@ -15,10 +15,15 @@ func (s *NetClsGroup) Name() string { } func (s *NetClsGroup) Apply(d *cgroupData) error { - _, err := d.join("net_cls") + dir, err := d.join("net_cls") if err != nil && !cgroups.IsNotFound(err) { return err } + + if err := s.Set(dir, d.config); err != nil { + return err + } + return nil } diff --git a/libcontainer/cgroups/fs/net_prio.go b/libcontainer/cgroups/fs/net_prio.go index d0ab2af8..0dabaae7 100644 --- a/libcontainer/cgroups/fs/net_prio.go +++ b/libcontainer/cgroups/fs/net_prio.go @@ -15,10 +15,15 @@ func (s *NetPrioGroup) Name() string { } func (s *NetPrioGroup) Apply(d *cgroupData) error { - _, err := d.join("net_prio") + dir, err := d.join("net_prio") if err != nil && !cgroups.IsNotFound(err) { return err } + + if err := s.Set(dir, d.config); err != nil { + return err + } + return nil } diff --git a/libcontainer/cgroups/fs/pids.go b/libcontainer/cgroups/fs/pids.go deleted file mode 100644 index 96cbb896..00000000 --- a/libcontainer/cgroups/fs/pids.go +++ /dev/null @@ -1,57 +0,0 @@ -// +build linux - -package fs - -import ( - "fmt" - "strconv" - - "github.com/opencontainers/runc/libcontainer/cgroups" - "github.com/opencontainers/runc/libcontainer/configs" -) - -type PidsGroup struct { -} - -func (s *PidsGroup) Name() string { - return "pids" -} - -func (s *PidsGroup) Apply(d *cgroupData) error { - _, err := d.join("pids") - if err != nil && !cgroups.IsNotFound(err) { - return err - } - return nil -} - -func (s *PidsGroup) Set(path string, cgroup *configs.Cgroup) error { - if cgroup.Resources.PidsLimit != 0 { - // "max" is the fallback value. - limit := "max" - - if cgroup.Resources.PidsLimit > 0 { - limit = strconv.FormatInt(cgroup.Resources.PidsLimit, 10) - } - - if err := writeFile(path, "pids.max", limit); err != nil { - return err - } - } - - return nil -} - -func (s *PidsGroup) Remove(d *cgroupData) error { - return removePath(d.path("pids")) -} - -func (s *PidsGroup) GetStats(path string, stats *cgroups.Stats) error { - value, err := getCgroupParamUint(path, "pids.current") - if err != nil { - return fmt.Errorf("failed to parse pids.current - %s", err) - } - - stats.PidsStats.Current = value - return nil -} diff --git a/libcontainer/cgroups/fs/pids_test.go b/libcontainer/cgroups/fs/pids_test.go deleted file mode 100644 index 06b11927..00000000 --- a/libcontainer/cgroups/fs/pids_test.go +++ /dev/null @@ -1,83 +0,0 @@ -// +build linux - -package fs - -import ( - "strconv" - "testing" - - "github.com/opencontainers/runc/libcontainer/cgroups" -) - -const ( - maxUnlimited = -1 - maxLimited = 1024 -) - -func TestPidsSetMax(t *testing.T) { - helper := NewCgroupTestUtil("pids", t) - defer helper.cleanup() - - helper.writeFileContents(map[string]string{ - "pids.max": "max", - }) - - helper.CgroupData.config.Resources.PidsLimit = maxLimited - pids := &PidsGroup{} - if err := pids.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { - t.Fatal(err) - } - - value, err := getCgroupParamUint(helper.CgroupPath, "pids.max") - if err != nil { - t.Fatalf("Failed to parse pids.max - %s", err) - } - - if value != maxLimited { - t.Fatalf("Expected %d, got %d for setting pids.max - limited", maxLimited, value) - } -} - -func TestPidsSetUnlimited(t *testing.T) { - helper := NewCgroupTestUtil("pids", t) - defer helper.cleanup() - - helper.writeFileContents(map[string]string{ - "pids.max": strconv.Itoa(maxLimited), - }) - - helper.CgroupData.config.Resources.PidsLimit = maxUnlimited - pids := &PidsGroup{} - if err := pids.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { - t.Fatal(err) - } - - value, err := getCgroupParamString(helper.CgroupPath, "pids.max") - if err != nil { - t.Fatalf("Failed to parse pids.max - %s", err) - } - - if value != "max" { - t.Fatalf("Expected %s, got %s for setting pids.max - unlimited", "max", value) - } -} - -func TestPidsStats(t *testing.T) { - helper := NewCgroupTestUtil("pids", t) - defer helper.cleanup() - - helper.writeFileContents(map[string]string{ - "pids.current": strconv.Itoa(1337), - "pids.max": strconv.Itoa(maxLimited), - }) - - pids := &PidsGroup{} - stats := *cgroups.NewStats() - if err := pids.GetStats(helper.CgroupPath, &stats); err != nil { - t.Fatal(err) - } - - if stats.PidsStats.Current != 1337 { - t.Fatalf("Expected %d, got %d for pids.current", 1337, stats.PidsStats.Current) - } -} diff --git a/libcontainer/cgroups/stats.go b/libcontainer/cgroups/stats.go index 74c65abf..bda32b20 100644 --- a/libcontainer/cgroups/stats.go +++ b/libcontainer/cgroups/stats.go @@ -49,11 +49,6 @@ type MemoryStats struct { Stats map[string]uint64 `json:"stats,omitempty"` } -type PidsStats struct { - // number of pids in the cgroup - Current uint64 `json:"current,omitempty"` -} - type BlkioStatEntry struct { Major uint64 `json:"major,omitempty"` Minor uint64 `json:"minor,omitempty"` @@ -85,7 +80,6 @@ type HugetlbStats struct { type Stats struct { CpuStats CpuStats `json:"cpu_stats,omitempty"` MemoryStats MemoryStats `json:"memory_stats,omitempty"` - PidsStats PidsStats `json:"pids_stats,omitempty"` BlkioStats BlkioStats `json:"blkio_stats,omitempty"` // the map is in the format "size of hugepage: stats of the hugepage" HugetlbStats map[string]HugetlbStats `json:"hugetlb_stats,omitempty"` diff --git a/libcontainer/cgroups/systemd/apply_systemd.go b/libcontainer/cgroups/systemd/apply_systemd.go index 73e7369c..93300cda 100644 --- a/libcontainer/cgroups/systemd/apply_systemd.go +++ b/libcontainer/cgroups/systemd/apply_systemd.go @@ -55,7 +55,6 @@ var subsystems = subsystemSet{ &fs.MemoryGroup{}, &fs.CpuGroup{}, &fs.CpuacctGroup{}, - &fs.PidsGroup{}, &fs.BlkioGroup{}, &fs.HugetlbGroup{}, &fs.PerfEventGroup{}, @@ -234,7 +233,7 @@ func (m *Manager) Apply(pid int) error { return err } - // we need to manually join the freezer, net_cls, net_prio, pids and cpuset cgroup in systemd + // we need to manually join the freezer, net_cls, net_prio and cpuset cgroup in systemd // because it does not currently support it via the dbus api. if err := joinFreezer(c, pid); err != nil { return err @@ -247,10 +246,6 @@ func (m *Manager) Apply(pid int) error { return err } - if err := joinPids(c, pid); err != nil { - return err - } - if err := joinCpuset(c, pid); err != nil { return err } @@ -335,43 +330,68 @@ func join(c *configs.Cgroup, subsystem string, pid int) (string, error) { } func joinCpu(c *configs.Cgroup, pid int) error { - _, err := getSubsystemPath(c, "cpu") + path, err := getSubsystemPath(c, "cpu") if err != nil && !cgroups.IsNotFound(err) { return err } + if c.Resources.CpuQuota != 0 { + if err = writeFile(path, "cpu.cfs_quota_us", strconv.FormatInt(c.Resources.CpuQuota, 10)); err != nil { + return err + } + } + if c.Resources.CpuPeriod != 0 { + if err = writeFile(path, "cpu.cfs_period_us", strconv.FormatInt(c.Resources.CpuPeriod, 10)); err != nil { + return err + } + } + if c.Resources.CpuRtPeriod != 0 { + if err = writeFile(path, "cpu.rt_period_us", strconv.FormatInt(c.Resources.CpuRtPeriod, 10)); err != nil { + return err + } + } + if c.Resources.CpuRtRuntime != 0 { + if err = writeFile(path, "cpu.rt_runtime_us", strconv.FormatInt(c.Resources.CpuRtRuntime, 10)); err != nil { + return err + } + } + return nil } func joinFreezer(c *configs.Cgroup, pid int) error { - _, err := join(c, "freezer", pid) + path, err := join(c, "freezer", pid) if err != nil && !cgroups.IsNotFound(err) { return err } - return nil + freezer, err := subsystems.Get("freezer") + if err != nil { + return err + } + return freezer.Set(path, c) } func joinNetPrio(c *configs.Cgroup, pid int) error { - _, err := join(c, "net_prio", pid) + path, err := join(c, "net_prio", pid) if err != nil && !cgroups.IsNotFound(err) { return err } - return nil + netPrio, err := subsystems.Get("net_prio") + if err != nil { + return err + } + return netPrio.Set(path, c) } func joinNetCls(c *configs.Cgroup, pid int) error { - _, err := join(c, "net_cls", pid) + path, err := join(c, "net_cls", pid) if err != nil && !cgroups.IsNotFound(err) { return err } - return nil -} - -func joinPids(c *configs.Cgroup, pid int) error { - _, err := join(c, "pids", pid) - if err != nil && !cgroups.IsNotFound(err) { + netcls, err := subsystems.Get("net_cls") + if err != nil { return err } - return nil + return netcls.Set(path, c) } func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) { @@ -438,19 +458,11 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) { } func (m *Manager) Set(container *configs.Config) error { - for _, sys := range subsystems { - // We can't set this here, because after being applied, memcg doesn't - // allow a non-empty cgroup from having its limits changed. - if sys.Name() == "memory" { + for name, path := range m.Paths { + sys, err := subsystems.Get(name) + if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) { continue } - - // Get the subsystem path, but don't error out for not found cgroups. - path, err := getSubsystemPath(container.Cgroups, sys.Name()) - if err != nil && !cgroups.IsNotFound(err) { - return err - } - if err := sys.Set(path, container.Cgroups); err != nil { return err } @@ -475,13 +487,17 @@ func getUnitName(c *configs.Cgroup) string { // because systemd will re-write the device settings if it needs to re-apply the cgroup context. // This happens at least for v208 when any sibling unit is started. func joinDevices(c *configs.Cgroup, pid int) error { - _, err := join(c, "devices", pid) + path, err := join(c, "devices", pid) // Even if it's `not found` error, we'll return err because devices cgroup // is hard requirement for container security. if err != nil { return err } - return nil + devices, err := subsystems.Get("devices") + if err != nil { + return err + } + return devices.Set(path, c) } func setKernelMemory(c *configs.Cgroup) error { @@ -494,14 +510,52 @@ func setKernelMemory(c *configs.Cgroup) error { return err } + if c.Resources.KernelMemory > 0 { + err = writeFile(path, "memory.kmem.limit_in_bytes", strconv.FormatInt(c.Resources.KernelMemory, 10)) + if err != nil { + return err + } + } + return nil } func joinMemory(c *configs.Cgroup, pid int) error { - _, err := getSubsystemPath(c, "memory") + path, err := getSubsystemPath(c, "memory") if err != nil && !cgroups.IsNotFound(err) { return err } + + // -1 disables memoryswap + if c.Resources.MemorySwap > 0 { + err = writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(c.Resources.MemorySwap, 10)) + if err != nil { + return err + } + } + if c.Resources.MemoryReservation > 0 { + err = writeFile(path, "memory.soft_limit_in_bytes", strconv.FormatInt(c.Resources.MemoryReservation, 10)) + if err != nil { + return err + } + } + if c.Resources.OomKillDisable { + if err := writeFile(path, "memory.oom_control", "1"); err != nil { + return err + } + } + + if c.Resources.MemorySwappiness >= 0 && c.Resources.MemorySwappiness <= 100 { + err = writeFile(path, "memory.swappiness", strconv.FormatInt(c.Resources.MemorySwappiness, 10)) + if err != nil { + return err + } + } else if c.Resources.MemorySwappiness == -1 { + return nil + } else { + return fmt.Errorf("invalid value:%d. valid memory swappiness range is 0-100", c.Resources.MemorySwappiness) + } + return nil } @@ -523,25 +577,68 @@ func joinCpuset(c *configs.Cgroup, pid int) error { // expects device path instead of major minor numbers, which is also confusing // for users. So we use fs work around for now. func joinBlkio(c *configs.Cgroup, pid int) error { - _, err := getSubsystemPath(c, "blkio") + path, err := getSubsystemPath(c, "blkio") if err != nil { return err } + // systemd doesn't directly support this in the dbus properties + if c.Resources.BlkioLeafWeight != 0 { + if err := writeFile(path, "blkio.leaf_weight", strconv.FormatUint(uint64(c.Resources.BlkioLeafWeight), 10)); err != nil { + return err + } + } + for _, wd := range c.Resources.BlkioWeightDevice { + if err := writeFile(path, "blkio.weight_device", wd.WeightString()); err != nil { + return err + } + if err := writeFile(path, "blkio.leaf_weight_device", wd.LeafWeightString()); err != nil { + return err + } + } + for _, td := range c.Resources.BlkioThrottleReadBpsDevice { + if err := writeFile(path, "blkio.throttle.read_bps_device", td.String()); err != nil { + return err + } + } + for _, td := range c.Resources.BlkioThrottleWriteBpsDevice { + if err := writeFile(path, "blkio.throttle.write_bps_device", td.String()); err != nil { + return err + } + } + for _, td := range c.Resources.BlkioThrottleReadIOPSDevice { + if err := writeFile(path, "blkio.throttle.read_iops_device", td.String()); err != nil { + return err + } + } + for _, td := range c.Resources.BlkioThrottleWriteIOPSDevice { + if err := writeFile(path, "blkio.throttle.write_iops_device", td.String()); err != nil { + return err + } + } + return nil } func joinHugetlb(c *configs.Cgroup, pid int) error { - _, err := join(c, "hugetlb", pid) + path, err := join(c, "hugetlb", pid) if err != nil && !cgroups.IsNotFound(err) { return err } - return nil + hugetlb, err := subsystems.Get("hugetlb") + if err != nil { + return err + } + return hugetlb.Set(path, c) } func joinPerfEvent(c *configs.Cgroup, pid int) error { - _, err := join(c, "perf_event", pid) + path, err := join(c, "perf_event", pid) if err != nil && !cgroups.IsNotFound(err) { return err } - return nil + perfEvent, err := subsystems.Get("perf_event") + if err != nil { + return err + } + return perfEvent.Set(path, c) } diff --git a/libcontainer/configs/cgroup_unix.go b/libcontainer/configs/cgroup_unix.go index 08b669a4..ef781324 100644 --- a/libcontainer/configs/cgroup_unix.go +++ b/libcontainer/configs/cgroup_unix.go @@ -64,9 +64,6 @@ type Resources struct { // MEM to use CpusetMems string `json:"cpuset_mems"` - // Process limit; set <= `0' to disable limit. - PidsLimit int64 `json:"pids_limit"` - // Specifies per cgroup weight, range is from 10 to 1000. BlkioWeight uint16 `json:"blkio_weight"` diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go index 54c13fa9..70513f7b 100644 --- a/libcontainer/factory_linux.go +++ b/libcontainer/factory_linux.go @@ -5,6 +5,7 @@ package libcontainer import ( "encoding/json" "fmt" + "io/ioutil" "os" "os/exec" "path/filepath" @@ -221,29 +222,21 @@ func (l *LinuxFactory) StartInitialization() (err error) { // clear the current process's environment to clean any libcontainer // specific env vars. os.Clearenv() - var i initer defer func() { // if we have an error during the initialization of the container's init then send it back to the // parent process in the form of an initError. if err != nil { - if _, ok := i.(*linuxStandardInit); ok { - // Synchronisation only necessary for standard init. - if err := json.NewEncoder(pipe).Encode(procError); err != nil { - panic(err) - } - } + // ensure that any data sent from the parent is consumed so it doesn't + // receive ECONNRESET when the child writes to the pipe. + ioutil.ReadAll(pipe) if err := json.NewEncoder(pipe).Encode(newSystemError(err)); err != nil { panic(err) } - } else { - if err := json.NewEncoder(pipe).Encode(procStart); err != nil { - panic(err) - } } // ensure that this pipe is always closed pipe.Close() }() - i, err = newContainerInit(it, pipe) + i, err := newContainerInit(it, pipe) if err != nil { return err } diff --git a/libcontainer/generic_error.go b/libcontainer/generic_error.go index e4872e2d..6fbc2d75 100644 --- a/libcontainer/generic_error.go +++ b/libcontainer/generic_error.go @@ -9,15 +9,6 @@ import ( "github.com/opencontainers/runc/libcontainer/stacktrace" ) -type syncType uint8 - -const ( - procReady syncType = iota - procError - procStart - procRun -) - var errorTemplate = template.Must(template.New("error").Parse(`Timestamp: {{.Timestamp}} Code: {{.ECode}} {{if .Message }} diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go index 504bf732..ddb11865 100644 --- a/libcontainer/init_linux.go +++ b/libcontainer/init_linux.go @@ -5,7 +5,6 @@ package libcontainer import ( "encoding/json" "fmt" - "io" "io/ioutil" "net" "os" @@ -74,7 +73,6 @@ func newContainerInit(t initType, pipe *os.File) (initer, error) { }, nil case initStandard: return &linuxStandardInit{ - pipe: pipe, parentPid: syscall.Getppid(), config: config, }, nil @@ -142,28 +140,6 @@ func finalizeNamespace(config *initConfig) error { return nil } -// syncParentReady sends to the given pipe a JSON payload which indicates that -// the init is ready to Exec the child process. It then waits for the parent to -// indicate that it is cleared to Exec. -func syncParentReady(pipe io.ReadWriter) error { - // Tell parent. - if err := json.NewEncoder(pipe).Encode(procReady); err != nil { - return err - } - - // Wait for parent to give the all-clear. - var procSync syncType - if err := json.NewDecoder(pipe).Decode(&procSync); err != nil { - if err == io.EOF { - return fmt.Errorf("parent closed synchronisation channel") - } - if procSync != procRun { - return fmt.Errorf("invalid synchronisation flag from parent") - } - } - return nil -} - // joinExistingNamespaces gets all the namespace paths specified for the container and // does a setns on the namespace fd so that the current process joins the namespace. func joinExistingNamespaces(namespaces []configs.Namespace) error { diff --git a/libcontainer/integration/exec_test.go b/libcontainer/integration/exec_test.go index 1995ff0e..59c8afd8 100644 --- a/libcontainer/integration/exec_test.go +++ b/libcontainer/integration/exec_test.go @@ -525,96 +525,6 @@ func testCpuShares(t *testing.T, systemd bool) { } } -func TestPids(t *testing.T) { - testPids(t, false) -} - -func TestPidsSystemd(t *testing.T) { - if !systemd.UseSystemd() { - t.Skip("Systemd is unsupported") - } - testPids(t, true) -} - -func testPids(t *testing.T, systemd bool) { - if testing.Short() { - return - } - - rootfs, err := newRootfs() - ok(t, err) - defer remove(rootfs) - - config := newTemplateConfig(rootfs) - if systemd { - config.Cgroups.Parent = "system.slice" - } - config.Cgroups.Resources.PidsLimit = -1 - - // Running multiple processes. - _, ret, err := runContainer(config, "", "/bin/sh", "-c", "/bin/true | /bin/true | /bin/true | /bin/true") - if err != nil && strings.Contains(err.Error(), "no such directory for pids.max") { - t.Skip("PIDs cgroup is unsupported") - } - ok(t, err) - - if ret != 0 { - t.Fatalf("expected fork() to succeed with no pids limit") - } - - // Enforce a permissive limit (shell + 4 * true). - config.Cgroups.Resources.PidsLimit = 5 - _, ret, err = runContainer(config, "", "/bin/sh", "-c", "/bin/true | /bin/true | /bin/true | /bin/true") - if err != nil && strings.Contains(err.Error(), "no such directory for pids.max") { - t.Skip("PIDs cgroup is unsupported") - } - ok(t, err) - - if ret != 0 { - t.Fatalf("expected fork() to succeed with permissive pids limit") - } - - // Enforce a restrictive limit limit (shell + 5 * true). - config.Cgroups.Resources.PidsLimit = 5 - out, ret, err := runContainer(config, "", "/bin/sh", "-c", "/bin/true | /bin/true | /bin/true | /bin/true | /bin/true") - if err != nil && strings.Contains(err.Error(), "no such directory for pids.max") { - t.Skip("PIDs cgroup is unsupported") - } - if err != nil && !strings.Contains(out.String(), "sh: can't fork") { - ok(t, err) - } - - if err == nil { - t.Fatalf("expected fork() to fail with restrictive pids limit") - } - - // Enforce minimal restrictions. - config.Cgroups.Resources.PidsLimit = 1 - out, ret, err = runContainer(config, "", "/bin/true") - if err != nil && strings.Contains(err.Error(), "no such directory for pids.max") { - t.Skip("PIDs cgroup is unsupported") - } - ok(t, err) - - if ret != 0 { - t.Fatalf("expected fork() to succeed for very small pids.max") - } - - // Enforce minimal restrictions. - config.Cgroups.Resources.PidsLimit = 1 - out, ret, err = runContainer(config, "", "/bin/sh", "-c", "/bin/true") - if err != nil && strings.Contains(err.Error(), "no such directory for pids.max") { - t.Skip("PIDs cgroup is unsupported") - } - if err != nil && !strings.Contains(out.String(), "sh: can't fork") { - ok(t, err) - } - - if err == nil { - t.Fatalf("expected fork() to fail for very small pids.max") - } -} - func TestRunWithKernelMemory(t *testing.T) { testRunWithKernelMemory(t, false) } diff --git a/libcontainer/process_linux.go b/libcontainer/process_linux.go index a358f651..114c71b3 100644 --- a/libcontainer/process_linux.go +++ b/libcontainer/process_linux.go @@ -5,7 +5,6 @@ package libcontainer import ( "encoding/json" "errors" - "fmt" "io" "os" "os/exec" @@ -87,7 +86,6 @@ func (p *setnsProcess) start() (err error) { if err := json.NewEncoder(p.parentPipe).Encode(p.config); err != nil { return newSystemError(err) } - if err := syscall.Shutdown(int(p.parentPipe.Fd()), syscall.SHUT_WR); err != nil { return newSystemError(err) } @@ -97,7 +95,6 @@ func (p *setnsProcess) start() (err error) { if err := json.NewDecoder(p.parentPipe).Decode(&ierr); err != nil && err != io.EOF { return newSystemError(err) } - // Must be done after Shutdown so the child will exit and we can wait for it. if ierr != nil { p.wait() return newSystemError(ierr) @@ -201,6 +198,7 @@ func (p *initProcess) start() (err error) { return newSystemError(err) } p.setExternalDescriptors(fds) + // Do this before syncing with child so that no children // can escape the cgroup if err := p.manager.Apply(p.pid()); err != nil { @@ -231,58 +229,13 @@ func (p *initProcess) start() (err error) { if err := p.sendConfig(); err != nil { return newSystemError(err) } - - var ( - procSync syncType - sentRun bool - ierr *genericError - ) - -loop: - for { - if err := json.NewDecoder(p.parentPipe).Decode(&procSync); err != nil { - if err == io.EOF { - break loop - } - return newSystemError(err) - } - - switch procSync { - case procStart: - break loop - case procReady: - if err := p.manager.Set(p.config.Config); err != nil { - return newSystemError(err) - } - // Sync with child. - if err := json.NewEncoder(p.parentPipe).Encode(procRun); err != nil { - return newSystemError(err) - } - sentRun = true - case procError: - // wait for the child process to fully complete and receive an error message - // if one was encoutered - if err := json.NewDecoder(p.parentPipe).Decode(&ierr); err != nil && err != io.EOF { - return newSystemError(err) - } - if ierr != nil { - break loop - } - // Programmer error. - panic("No error following JSON procError payload.") - default: - return newSystemError(fmt.Errorf("Invalid JSON synchronisation payload from child.")) - } - } - if !sentRun { - return newSystemError(fmt.Errorf("Couldn't synchronise with container process.")) - } - if err := syscall.Shutdown(int(p.parentPipe.Fd()), syscall.SHUT_WR); err != nil { + // wait for the child process to fully complete and receive an error message + // if one was encoutered + var ierr *genericError + if err := json.NewDecoder(p.parentPipe).Decode(&ierr); err != nil && err != io.EOF { return newSystemError(err) } - // Must be done after Shutdown so the child will exit and we can wait for it. if ierr != nil { - p.wait() return newSystemError(ierr) } return nil @@ -320,7 +273,8 @@ func (p *initProcess) sendConfig() error { if err := json.NewEncoder(p.parentPipe).Encode(p.config); err != nil { return err } - return nil + // shutdown writes for the parent side of the pipe + return syscall.Shutdown(int(p.parentPipe.Fd()), syscall.SHUT_WR) } func (p *initProcess) createNetworkInterfaces() error { diff --git a/libcontainer/standard_init_linux.go b/libcontainer/standard_init_linux.go index d3b50863..ec100578 100644 --- a/libcontainer/standard_init_linux.go +++ b/libcontainer/standard_init_linux.go @@ -3,7 +3,6 @@ package libcontainer import ( - "io" "os" "syscall" @@ -15,7 +14,6 @@ import ( ) type linuxStandardInit struct { - pipe io.ReadWriter parentPid int config *initConfig } @@ -52,6 +50,7 @@ func (l *linuxStandardInit) Init() error { if err := setOomScoreAdj(l.config.Config.OomScoreAdj); err != nil { return err } + label.Init() // InitializeMountNamespace() can be executed only for a new mount namespace if l.config.Config.Namespaces.Contains(configs.NEWNS) { @@ -76,6 +75,7 @@ func (l *linuxStandardInit) Init() error { return err } } + for _, path := range l.config.Config.ReadonlyPaths { if err := remountReadonly(path); err != nil { return err @@ -90,12 +90,6 @@ func (l *linuxStandardInit) Init() error { if err != nil { return err } - // Tell our parent that we're ready to Execv. This must be done before the - // Seccomp rules have been applied, because we need to be able to read and - // write to a socket. - if err := syncParentReady(l.pipe); err != nil { - return err - } if l.config.Config.Seccomp != nil { if err := seccomp.InitSeccomp(l.config.Config.Seccomp); err != nil { return err diff --git a/spec.go b/spec.go index 6c5e4790..fbe31bb2 100644 --- a/spec.go +++ b/spec.go @@ -456,7 +456,6 @@ func createCgroupConfig(name string, spec *specs.LinuxRuntimeSpec, devices []*co c.Resources.CpuRtPeriod = r.CPU.RealtimePeriod c.Resources.CpusetCpus = r.CPU.Cpus c.Resources.CpusetMems = r.CPU.Mems - c.Resources.PidsLimit = r.Pids.Limit c.Resources.BlkioWeight = r.BlockIO.Weight c.Resources.BlkioLeafWeight = r.BlockIO.LeafWeight for _, wd := range r.BlockIO.WeightDevice {