cgroupfs: Return the correct cgroup path for tasks from procfs.

Previously we were returning the path to the control file used to
migrate a task, rather than the path to the cgroup directory.

PiperOrigin-RevId: 438676422
This commit is contained in:
Rahat Mahmood
2022-03-31 15:33:04 -07:00
committed by gVisor bot
parent 17d7454f86
commit 8a8349e1bc
5 changed files with 102 additions and 28 deletions
+10 -4
View File
@@ -283,9 +283,15 @@ func (c *cgroupInode) AbortMigrate(t *kernel.Task, src *kernel.Cgroup) {
}
}
func (c *cgroupInode) Cgroup(fd *vfs.FileDescription) kernel.Cgroup {
func (c *cgroupInode) CgroupFromControlFileFD(fd *vfs.FileDescription) kernel.Cgroup {
controlFileDentry := fd.Dentry().Impl().(*kernfs.Dentry)
// The returned parent dentry remains valid without holding locks because in
// cgroupfs, the parent directory relationship of a control file is
// effectively immutable. Control files cannot be unlinked, renamed or
// destroyed independently from their parent directory.
parentD := controlFileDentry.Parent()
return kernel.Cgroup{
Dentry: fd.Dentry().Impl().(*kernfs.Dentry),
Dentry: parentD,
CgroupImpl: c,
}
}
@@ -340,7 +346,7 @@ func (d *cgroupProcsData) Write(ctx context.Context, fd *vfs.FileDescription, sr
if targetTG == nil {
return 0, linuxerr.EINVAL
}
return n, targetTG.MigrateCgroup(d.Cgroup(fd))
return n, targetTG.MigrateCgroup(d.CgroupFromControlFileFD(fd))
}
// +stateify savable
@@ -382,7 +388,7 @@ func (d *tasksData) Write(ctx context.Context, fd *vfs.FileDescription, src user
if targetTask == nil {
return 0, linuxerr.EINVAL
}
return n, targetTask.MigrateCgroup(d.Cgroup(fd))
return n, targetTask.MigrateCgroup(d.CgroupFromControlFileFD(fd))
}
// parseInt64FromString interprets src as string encoding a int64 value, and
+7
View File
@@ -600,6 +600,13 @@ func (d *Dentry) WalkDentryTree(ctx context.Context, vfsObj *vfs.VirtualFilesyst
return target, nil
}
// Parent returns the parent of this Dentry. This is not safe in general, the
// filesystem may concurrently move d elsewhere. The caller is responsible for
// ensuring the returned result remains valid while it is used.
func (d *Dentry) Parent() *Dentry {
return d.parent
}
// The Inode interface maps filesystem-level operations that operate on paths to
// equivalent operations on specific filesystem nodes.
//
+62 -1
View File
@@ -258,7 +258,7 @@ TEST(Cgroup, MountRace) {
}
});
Cgroup c = Cgroup(mountpoint.path());
Cgroup c = Cgroup::RootCgroup(mountpoint.path());
// c should be a valid cgroup.
EXPECT_NO_ERRNO(c.ContainsCallingProcess());
}
@@ -960,6 +960,67 @@ TEST(ProcCgroup, ProcfsReportsCgroupfsMountOptions) {
}
}
TEST(ProcCgroups, ProcfsRreportsHierarchyID) {
SKIP_IF(!CgroupsAvailable());
Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
Cgroup h1 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory,cpuacct"));
Cgroup h2 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpu"));
absl::flat_hash_map<std::string, CgroupsEntry> entries =
ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries());
EXPECT_EQ(entries["memory"].hierarchy, entries["cpuacct"].hierarchy);
// Hierarhcy IDs are allocated sequentially, starting at 1.
EXPECT_EQ(entries["memory"].hierarchy, 1);
EXPECT_EQ(entries["cpu"].hierarchy, 2);
}
TEST(ProcCgroups, ProcfsReportsTasksCgroup) {
SKIP_IF(!CgroupsAvailable());
Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
Cgroup h1 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory"));
Cgroup h2 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpu,cpuacct"));
Cgroup h1c1 = ASSERT_NO_ERRNO_AND_VALUE(h1.CreateChild("memory_child1"));
Cgroup h1c2 = ASSERT_NO_ERRNO_AND_VALUE(h1.CreateChild("memory_child2"));
Cgroup h2c1 = ASSERT_NO_ERRNO_AND_VALUE(h2.CreateChild("cpu_child1"));
Cgroup h2c2 = ASSERT_NO_ERRNO_AND_VALUE(h2.CreateChild("cpu_child2"));
// Test should initially be in the hierarchy roots.
auto entries = ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid()));
EXPECT_EQ(h1.CanonicalPath(), entries["memory"].path);
EXPECT_EQ(h2.CanonicalPath(), entries["cpu,cpuacct"].path);
// Move to child for hierarchy #1 and check paths. Note that we haven't moved
// in hierarchy #2.
ASSERT_NO_ERRNO(h1c1.Enter(getpid()));
entries = ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid()));
EXPECT_EQ(h1c1.CanonicalPath(), entries["memory"].path);
EXPECT_EQ(h2.CanonicalPath(), entries["cpu,cpuacct"].path);
// Move h2; h1 should remain unchanged.
ASSERT_NO_ERRNO(h2c1.Enter(getpid()));
entries = ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid()));
EXPECT_EQ(h1c1.CanonicalPath(), entries["memory"].path);
EXPECT_EQ(h2c1.CanonicalPath(), entries["cpu,cpuacct"].path);
// Move the thread rather than process group.
const pid_t tid = syscall(SYS_gettid);
ASSERT_NO_ERRNO(h1c2.EnterThread(tid));
entries = ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(tid));
EXPECT_EQ(h1c2.CanonicalPath(), entries["memory"].path);
EXPECT_EQ(h2c1.CanonicalPath(), entries["cpu,cpuacct"].path);
ASSERT_NO_ERRNO(h2c2.EnterThread(tid));
entries = ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(tid));
EXPECT_EQ(h1c2.CanonicalPath(), entries["memory"].path);
EXPECT_EQ(h2c2.CanonicalPath(), entries["cpu,cpuacct"].path);
}
} // namespace
} // namespace testing
} // namespace gvisor
+6 -13
View File
@@ -25,26 +25,19 @@
namespace gvisor {
namespace testing {
Cgroup::Cgroup(absl::string_view path) : cgroup_path_(path) {
Cgroup::Cgroup(absl::string_view path, absl::string_view mountpoint)
: cgroup_path_(path), mountpoint_(mountpoint) {
id_ = ++Cgroup::next_id_;
std::cerr << absl::StreamFormat("[cg#%d] <= %s", id_, cgroup_path_)
<< std::endl;
}
PosixErrorOr<Cgroup> Cgroup::RecursivelyCreate(absl::string_view path) {
RETURN_IF_ERRNO(RecursivelyCreateDir(path));
return Cgroup(path);
}
PosixErrorOr<Cgroup> Cgroup::Create(absl::string_view path) {
RETURN_IF_ERRNO(Mkdir(path));
return Cgroup(path);
}
PosixError Cgroup::Delete() { return Rmdir(cgroup_path_); }
PosixErrorOr<Cgroup> Cgroup::CreateChild(absl::string_view name) const {
return Cgroup::Create(JoinPath(Path(), name));
std::string path = JoinPath(Path(), name);
RETURN_IF_ERRNO(Mkdir(path));
return Cgroup(path, mountpoint_);
}
PosixErrorOr<std::string> Cgroup::ReadControlFile(
@@ -183,7 +176,7 @@ PosixErrorOr<Cgroup> Mounter::MountCgroupfs(std::string mopts) {
"Mount(\"none\", \"%s\", \"cgroup\", 0, \"%s\", 0) => OK",
mountpath, mopts)
<< std::endl;
Cgroup cg = Cgroup(mountpath);
Cgroup cg = Cgroup::RootCgroup(mountpath);
mountpoints_[cg.id()] = std::move(mountpoint);
mounts_[cg.id()] = std::move(mount);
return cg;
+17 -10
View File
@@ -30,24 +30,28 @@ namespace testing {
// Cgroup represents a cgroup directory on a mounted cgroupfs.
class Cgroup {
public:
Cgroup(std::string_view path);
static Cgroup RootCgroup(absl::string_view path) {
return Cgroup(path, path);
}
uint64_t id() const { return id_; }
// RecursivelyCreate creates cgroup specified by path, including all
// components leading up to path. Path should end inside a cgroupfs mount. If
// path already exists, RecursivelyCreate does nothing and silently succeeds.
static PosixErrorOr<Cgroup> RecursivelyCreate(std::string_view path);
// Creates a new cgroup at path. The parent directory must exist and be a
// cgroupfs directory.
static PosixErrorOr<Cgroup> Create(std::string_view path);
// Deletes the current cgroup represented by this object.
PosixError Delete();
const std::string& Path() const { return cgroup_path_; }
// Returns the canonical path for this cgroup, which is the absolute path
// starting at the hierarchy root.
const std::string CanonicalPath() const {
std::string relpath =
GetRelativePath(mountpoint_, cgroup_path_).ValueOrDie();
if (relpath == ".") {
return "/";
}
return "/" + relpath;
}
// Creates a child cgroup under this cgroup with the given name.
PosixErrorOr<Cgroup> CreateChild(std::string_view name) const;
@@ -95,12 +99,15 @@ class Cgroup {
PosixError EnterThread(pid_t pid) const;
private:
Cgroup(std::string_view path, std::string_view mountpoint);
PosixErrorOr<absl::flat_hash_set<pid_t>> ParsePIDList(
absl::string_view data) const;
static int64_t next_id_;
int64_t id_;
const std::string cgroup_path_;
const std::string mountpoint_;
};
// Mounter is a utility for creating cgroupfs mounts. It automatically manages