From e3a5da8ce62826f56c0b531590bb472ea717eeac Mon Sep 17 00:00:00 2001 From: Rahat Mahmood Date: Wed, 21 Apr 2021 13:34:51 -0700 Subject: Stub the custom "job" controller required by some workloads. PiperOrigin-RevId: 369724358 --- pkg/sentry/fsimpl/cgroupfs/BUILD | 1 + pkg/sentry/fsimpl/cgroupfs/base.go | 28 +++++++++++++++ pkg/sentry/fsimpl/cgroupfs/cgroupfs.go | 21 ++++++++--- pkg/sentry/fsimpl/cgroupfs/job.go | 64 ++++++++++++++++++++++++++++++++++ test/syscalls/linux/cgroup.cc | 35 +++++++++++++++++-- test/util/cgroup_util.cc | 13 +++++++ test/util/cgroup_util.h | 8 +++++ 7 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 pkg/sentry/fsimpl/cgroupfs/job.go diff --git a/pkg/sentry/fsimpl/cgroupfs/BUILD b/pkg/sentry/fsimpl/cgroupfs/BUILD index 48913068a..37efb641a 100644 --- a/pkg/sentry/fsimpl/cgroupfs/BUILD +++ b/pkg/sentry/fsimpl/cgroupfs/BUILD @@ -23,6 +23,7 @@ go_library( "cpuacct.go", "cpuset.go", "dir_refs.go", + "job.go", "memory.go", ], visibility = ["//pkg/sentry:internal"], diff --git a/pkg/sentry/fsimpl/cgroupfs/base.go b/pkg/sentry/fsimpl/cgroupfs/base.go index 39c1013e1..0f54888d8 100644 --- a/pkg/sentry/fsimpl/cgroupfs/base.go +++ b/pkg/sentry/fsimpl/cgroupfs/base.go @@ -18,6 +18,7 @@ import ( "bytes" "fmt" "sort" + "strconv" "sync/atomic" "gvisor.dev/gvisor/pkg/abi/linux" @@ -26,6 +27,7 @@ import ( "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/vfs" + "gvisor.dev/gvisor/pkg/syserror" "gvisor.dev/gvisor/pkg/usermem" ) @@ -231,3 +233,29 @@ func (d *tasksData) Write(ctx context.Context, src usermem.IOSequence, offset in // TODO(b/183137098): Payload is the pid for a process to add to this cgroup. return src.NumBytes(), nil } + +// parseInt64FromString interprets src as string encoding a int64 value, and +// returns the parsed value. +func parseInt64FromString(ctx context.Context, src usermem.IOSequence, offset int64) (val, len int64, err error) { + const maxInt64StrLen = 20 // i.e. len(fmt.Sprintf("%d", math.MinInt64)) == 20 + + t := kernel.TaskFromContext(ctx) + src = src.DropFirst64(offset) + + buf := t.CopyScratchBuffer(maxInt64StrLen) + n, err := src.CopyIn(ctx, buf) + if err != nil { + return 0, int64(n), err + } + buf = buf[:n] + + val, err = strconv.ParseInt(string(buf), 10, 64) + if err != nil { + // Note: This also handles zero-len writes if offset is beyond the end + // of src, or src is empty. + ctx.Warningf("cgroupfs.parseInt64FromString: failed to parse %q: %v", string(buf), err) + return 0, int64(n), syserror.EINVAL + } + + return val, int64(n), nil +} diff --git a/pkg/sentry/fsimpl/cgroupfs/cgroupfs.go b/pkg/sentry/fsimpl/cgroupfs/cgroupfs.go index ca8caee5f..bd3e69757 100644 --- a/pkg/sentry/fsimpl/cgroupfs/cgroupfs.go +++ b/pkg/sentry/fsimpl/cgroupfs/cgroupfs.go @@ -81,13 +81,20 @@ const ( controllerCPU = kernel.CgroupControllerType("cpu") controllerCPUAcct = kernel.CgroupControllerType("cpuacct") controllerCPUSet = kernel.CgroupControllerType("cpuset") + controllerJob = kernel.CgroupControllerType("job") controllerMemory = kernel.CgroupControllerType("memory") ) -var allControllers = []kernel.CgroupControllerType{controllerCPU, controllerCPUAcct, controllerCPUSet, controllerMemory} +var allControllers = []kernel.CgroupControllerType{ + controllerCPU, + controllerCPUAcct, + controllerCPUSet, + controllerJob, + controllerMemory, +} // SupportedMountOptions is the set of supported mount options for cgroupfs. -var SupportedMountOptions = []string{"all", "cpu", "cpuacct", "cpuset", "memory"} +var SupportedMountOptions = []string{"all", "cpu", "cpuacct", "cpuset", "job", "memory"} // FilesystemType implements vfs.FilesystemType. // @@ -171,6 +178,10 @@ func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt delete(mopts, "cpuset") wantControllers = append(wantControllers, controllerCPUSet) } + if _, ok := mopts["job"]; ok { + delete(mopts, "job") + wantControllers = append(wantControllers, controllerJob) + } if _, ok := mopts["memory"]; ok { delete(mopts, "memory") wantControllers = append(wantControllers, controllerMemory) @@ -235,14 +246,16 @@ func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt for _, ty := range wantControllers { var c controller switch ty { - case controllerMemory: - c = newMemoryController(fs, defaults) case controllerCPU: c = newCPUController(fs, defaults) case controllerCPUAcct: c = newCPUAcctController(fs) case controllerCPUSet: c = newCPUSetController(fs) + case controllerJob: + c = newJobController(fs) + case controllerMemory: + c = newMemoryController(fs, defaults) default: panic(fmt.Sprintf("Unreachable: unknown cgroup controller %q", ty)) } diff --git a/pkg/sentry/fsimpl/cgroupfs/job.go b/pkg/sentry/fsimpl/cgroupfs/job.go new file mode 100644 index 000000000..48919c338 --- /dev/null +++ b/pkg/sentry/fsimpl/cgroupfs/job.go @@ -0,0 +1,64 @@ +// Copyright 2021 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cgroupfs + +import ( + "bytes" + "fmt" + + "gvisor.dev/gvisor/pkg/context" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs" + "gvisor.dev/gvisor/pkg/sentry/kernel/auth" + "gvisor.dev/gvisor/pkg/usermem" +) + +// +stateify savable +type jobController struct { + controllerCommon + id int64 +} + +var _ controller = (*jobController)(nil) + +func newJobController(fs *filesystem) *jobController { + c := &jobController{} + c.controllerCommon.init(controllerJob, fs) + return c +} + +func (c *jobController) AddControlFiles(ctx context.Context, creds *auth.Credentials, _ *cgroupInode, contents map[string]kernfs.Inode) { + contents["job.id"] = c.fs.newControllerWritableFile(ctx, creds, &jobIDData{c: c}) +} + +// +stateify savable +type jobIDData struct { + c *jobController +} + +// Generate implements vfs.DynamicBytesSource.Generate. +func (d *jobIDData) Generate(ctx context.Context, buf *bytes.Buffer) error { + fmt.Fprintf(buf, "%d\n", d.c.id) + return nil +} + +// Write implements vfs.WritableDynamicBytesSource.Write. +func (d *jobIDData) Write(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) { + val, n, err := parseInt64FromString(ctx, src, offset) + if err != nil { + return n, err + } + d.c.id = val + return n, nil +} diff --git a/test/syscalls/linux/cgroup.cc b/test/syscalls/linux/cgroup.cc index a1006a978..862328f5b 100644 --- a/test/syscalls/linux/cgroup.cc +++ b/test/syscalls/linux/cgroup.cc @@ -15,6 +15,7 @@ // All tests in this file rely on being about to mount and unmount cgroupfs, // which isn't expected to work, or be safe on a general linux system. +#include #include #include @@ -35,8 +36,9 @@ using ::testing::_; using ::testing::Ge; using ::testing::Gt; -std::vector known_controllers = {"cpu", "cpuset", "cpuacct", - "memory"}; +std::vector known_controllers = { + "cpu", "cpuset", "cpuacct", "job", "memory", +}; bool CgroupsAvailable() { return IsRunningOnGvisor() && !IsRunningWithVFS1() && @@ -257,6 +259,35 @@ TEST(CPUAcctCgroup, CPUAcctStat) { EXPECT_THAT(Atoi(sys_tokens[1]), IsPosixErrorOkAndHolds(Ge(0))); } +// WriteAndVerifyControlValue attempts to write val to a cgroup file at path, +// and verify the value by reading it afterwards. +PosixError WriteAndVerifyControlValue(const Cgroup& c, std::string_view path, + int64_t val) { + RETURN_IF_ERRNO(c.WriteIntegerControlFile(path, val)); + ASSIGN_OR_RETURN_ERRNO(int64_t newval, c.ReadIntegerControlFile(path)); + if (newval != val) { + return PosixError( + EINVAL, + absl::StrFormat( + "Unexpected value for control file '%s': expected %d, got %d", path, + val, newval)); + } + return NoError(); +} + +TEST(JobCgroup, ReadWriteRead) { + SKIP_IF(!CgroupsAvailable()); + + Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); + Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("job")); + + EXPECT_THAT(c.ReadIntegerControlFile("job.id"), IsPosixErrorOkAndHolds(0)); + EXPECT_NO_ERRNO(WriteAndVerifyControlValue(c, "job.id", 1234)); + EXPECT_NO_ERRNO(WriteAndVerifyControlValue(c, "job.id", -1)); + EXPECT_NO_ERRNO(WriteAndVerifyControlValue(c, "job.id", LLONG_MIN)); + EXPECT_NO_ERRNO(WriteAndVerifyControlValue(c, "job.id", LLONG_MAX)); +} + TEST(ProcCgroups, Empty) { SKIP_IF(!CgroupsAvailable()); diff --git a/test/util/cgroup_util.cc b/test/util/cgroup_util.cc index 65d9c4986..d8d3fe471 100644 --- a/test/util/cgroup_util.cc +++ b/test/util/cgroup_util.cc @@ -17,6 +17,7 @@ #include #include +#include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" #include "test/util/fs_util.h" #include "test/util/mount_util.h" @@ -50,6 +51,18 @@ PosixErrorOr Cgroup::ReadIntegerControlFile( return val; } +PosixError Cgroup::WriteControlFile(absl::string_view name, + const std::string& value) const { + ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, Open(Relpath(name), O_WRONLY)); + RETURN_ERROR_IF_SYSCALL_FAIL(WriteFd(fd.get(), value.c_str(), value.size())); + return NoError(); +} + +PosixError Cgroup::WriteIntegerControlFile(absl::string_view name, + int64_t value) const { + return WriteControlFile(name, absl::StrCat(value)); +} + PosixErrorOr> Cgroup::Procs() const { ASSIGN_OR_RETURN_ERRNO(std::string buf, ReadControlFile("cgroup.procs")); return ParsePIDList(buf); diff --git a/test/util/cgroup_util.h b/test/util/cgroup_util.h index b049559df..c6e4303e1 100644 --- a/test/util/cgroup_util.h +++ b/test/util/cgroup_util.h @@ -45,6 +45,14 @@ class Cgroup { // to parse it as an integer. PosixErrorOr ReadIntegerControlFile(absl::string_view name) const; + // Writes a string to a cgroup control file. + PosixError WriteControlFile(absl::string_view name, + const std::string& value) const; + + // Writes an integer value to a cgroup control file. + PosixError WriteIntegerControlFile(absl::string_view name, + int64_t value) const; + // Returns the thread ids of the leaders of thread groups managed by this // cgroup. PosixErrorOr> Procs() const; -- cgit v1.2.3