| // Copyright 2016 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "libcontainer/cgroup.h" | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <stdbool.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #include <unistd.h> | |
| #include <utility> | |
| #include <base/files/file_util.h> | |
| #include <base/files/scoped_file.h> | |
| #include <base/functional/bind.h> | |
| #include <base/functional/callback.h> | |
| #include <base/functional/callback_helpers.h> | |
| #include <base/logging.h> | |
| #include <base/strings/string_number_conversions.h> | |
| #include <base/strings/stringprintf.h> | |
| namespace libcontainer { | |
| namespace { | |
| Cgroup::CgroupFactory g_cgroup_factory = nullptr; | |
| constexpr const char* kCgroupNames[Cgroup::Type::NUM_TYPES] = { | |
| "cpu", "cpuacct", "cpuset", "devices", "freezer", "memory"}; | |
| base::ScopedFD OpenCgroupFile(const base::FilePath& cgroup_path, | |
| std::string_view name, | |
| bool write) { | |
| base::FilePath path = cgroup_path.Append(name); | |
| int flags = write ? O_WRONLY | O_CREAT | O_TRUNC : O_RDONLY; | |
| // Adding O_NONBLOCK to avoid blocking in case we were tricked into opening a | |
| // blocking file (e.g. an unopened FIFO or a device). | |
| flags |= O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC; | |
| base::ScopedFD fd(HANDLE_EINTR(open(path.value().c_str(), flags, 0664))); | |
| if (!fd.is_valid()) | |
| return base::ScopedFD(); | |
| // Ensure the opened file is a regular file. | |
| struct stat st; | |
| if (fstat(fd.get(), &st) < 0) { | |
| PLOG(ERROR) << "Failed to fstat " << path.value(); | |
| return base::ScopedFD(); | |
| } | |
| if (!S_ISREG(st.st_mode)) { | |
| LOG(ERROR) << path.value() << " is not a regular file"; | |
| return base::ScopedFD(); | |
| } | |
| // Remove O_NONBLOCK before returning |fd|. | |
| flags = fcntl(fd.get(), F_GETFL); | |
| if (flags == -1) { | |
| PLOG(ERROR) << "Failed to get flags for " << path.value(); | |
| return base::ScopedFD(); | |
| } | |
| if (fcntl(fd.get(), F_SETFL, flags & ~O_NONBLOCK) < 0) { | |
| PLOG(ERROR) << "Failed to remove O_NONBLOCK for " << path.value(); | |
| return base::ScopedFD(); | |
| } | |
| return fd; | |
| } | |
| bool WriteCgroupFile(const base::FilePath& cgroup_path, | |
| std::string_view name, | |
| std::string_view str) { | |
| base::ScopedFD fd = OpenCgroupFile(cgroup_path, name, true); | |
| if (!fd.is_valid()) | |
| return false; | |
| if (!base::WriteFileDescriptor(fd.get(), str)) | |
| return false; | |
| return true; | |
| } | |
| bool WriteCgroupFileInt(const base::FilePath& cgroup_path, | |
| std::string_view name, | |
| const int value) { | |
| return WriteCgroupFile(cgroup_path, name, base::NumberToString(value)); | |
| } | |
| bool CopyCgroupParent(const base::FilePath& cgroup_path, | |
| std::string_view name) { | |
| base::ScopedFD dest = OpenCgroupFile(cgroup_path, name, true); | |
| if (!dest.is_valid()) | |
| return false; | |
| base::ScopedFD source = OpenCgroupFile(cgroup_path.DirName(), name, false); | |
| if (!source.is_valid()) | |
| return false; | |
| base::File infile(std::move(source)); | |
| base::File outfile(std::move(dest)); | |
| return base::CopyFileContents(infile, outfile); | |
| } | |
| std::string GetDeviceString(const int major, const int minor) { | |
| if (major >= 0 && minor >= 0) | |
| return base::StringPrintf("%d:%d", major, minor); | |
| else if (major >= 0) | |
| return base::StringPrintf("%d:*", major); | |
| else if (minor >= 0) | |
| return base::StringPrintf("*:%d", minor); | |
| else | |
| return base::StringPrintf("*:*"); | |
| } | |
| bool CreateCgroupAsOwner(const base::FilePath& cgroup_path, | |
| uid_t cgroup_owner, | |
| gid_t cgroup_group) { | |
| base::ScopedClosureRunner runner; | |
| // If running as root and the cgroup owner is a user, create the cgroup | |
| // as that user. | |
| if (getuid() == 0 && (cgroup_owner != 0 || cgroup_group != 0)) { | |
| // Ensure that we reset the euid and egid no matter what. | |
| // TODO(ejcaruso, hidehiko): change to OnceClosure when libchrome is | |
| // upreved again. | |
| runner.ReplaceClosure(base::BindOnce( | |
| [](uid_t euid, gid_t egid) { | |
| if (seteuid(euid) != 0) | |
| PLOG(ERROR) << "Failed to reset euid"; | |
| if (setegid(egid) != 0) | |
| PLOG(ERROR) << "Failed to reset egid"; | |
| }, | |
| geteuid(), getegid())); | |
| if (setegid(cgroup_group) != 0 || seteuid(cgroup_owner) != 0) { | |
| PLOG(ERROR) << "Failed to set cgroup owner"; | |
| return false; | |
| } | |
| } | |
| if (mkdir(cgroup_path.value().c_str(), S_IRWXU | S_IRWXG) < 0 && | |
| errno != EEXIST) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool CopyCpusetParent(const base::FilePath& cgroup_path) { | |
| // cpuset is special: we need to copy parent's cpus or mems, | |
| // otherwise we'll start with "empty" cpuset and nothing can | |
| // run in it/be moved into it. | |
| // | |
| // Chromium OS and Android use the legacy noprefix mount option (i.e. | |
| // no "cpuset." in front of cpus/mems), but we also check for the | |
| // prefixed files. | |
| if (!CopyCgroupParent(cgroup_path, "cpus") && | |
| !CopyCgroupParent(cgroup_path, "cpuset.cpus")) { | |
| PLOG(ERROR) << "Failed to copy " << cgroup_path.value() | |
| << "/cpus from parent"; | |
| return false; | |
| } | |
| if (!CopyCgroupParent(cgroup_path, "mems") && | |
| !CopyCgroupParent(cgroup_path, "cpuset.mems")) { | |
| PLOG(ERROR) << "Failed to copy " << cgroup_path.value() | |
| << "/mems from parent"; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool CheckCgroupAvailable(const base::FilePath& cgroup_root, | |
| std::string_view cgroup_name) { | |
| base::FilePath path = cgroup_root.Append(cgroup_name); | |
| return access(path.value().c_str(), F_OK) == 0; | |
| } | |
| } // namespace | |
| bool Cgroup::Freeze() { | |
| return WriteCgroupFile(cgroup_paths_[Type::FREEZER], "freezer.state", | |
| "FROZEN\n"); | |
| } | |
| bool Cgroup::Thaw() { | |
| return WriteCgroupFile(cgroup_paths_[Type::FREEZER], "freezer.state", | |
| "THAWED\n"); | |
| } | |
| bool Cgroup::DenyAllDevices() { | |
| return WriteCgroupFile(cgroup_paths_[Type::DEVICES], "devices.deny", "a\n"); | |
| } | |
| bool Cgroup::AddDevice(bool allow, | |
| int major, | |
| int minor, | |
| bool read, | |
| bool write, | |
| bool modify, | |
| char type) { | |
| if (type != 'b' && type != 'c' && type != 'a') { | |
| LOG(ERROR) << "Invalid cgroup type '" << type << "'"; | |
| return false; | |
| } | |
| if (!read && !write && !modify) { | |
| LOG(ERROR) << "Invalid cgroup permissions: at least one of read, write, " | |
| "modify should be true"; | |
| return false; | |
| } | |
| std::string device_string = GetDeviceString(major, minor); | |
| // The device file format is: | |
| // <type, c, b, or a> major:minor rmw | |
| std::string perm_string = | |
| base::StringPrintf("%c %s %s%s%s\n", type, device_string.c_str(), | |
| read ? "r" : "", write ? "w" : "", modify ? "m" : ""); | |
| return WriteCgroupFile(cgroup_paths_[Type::DEVICES], | |
| allow ? "devices.allow" : "devices.deny", perm_string); | |
| } | |
| bool Cgroup::SetCpuShares(int shares) { | |
| return WriteCgroupFileInt(cgroup_paths_[Type::CPU], "cpu.shares", shares); | |
| } | |
| bool Cgroup::SetCpuQuota(int quota) { | |
| return WriteCgroupFileInt(cgroup_paths_[Type::CPU], "cpu.cfs_quota_us", | |
| quota); | |
| } | |
| bool Cgroup::SetCpuPeriod(int period) { | |
| return WriteCgroupFileInt(cgroup_paths_[Type::CPU], "cpu.cfs_period_us", | |
| period); | |
| } | |
| bool Cgroup::SetCpuRtRuntime(int rt_runtime) { | |
| return WriteCgroupFileInt(cgroup_paths_[Type::CPU], "cpu.rt_runtime_us", | |
| rt_runtime); | |
| } | |
| bool Cgroup::SetCpuRtPeriod(int rt_period) { | |
| return WriteCgroupFileInt(cgroup_paths_[Type::CPU], "cpu.rt_period_us", | |
| rt_period); | |
| } | |
| // static | |
| void Cgroup::SetCgroupFactoryForTesting(CgroupFactory factory) { | |
| g_cgroup_factory = factory; | |
| } | |
| // static | |
| std::unique_ptr<Cgroup> Cgroup::Create(std::string_view name, | |
| const base::FilePath& cgroup_root, | |
| const base::FilePath& cgroup_parent, | |
| uid_t cgroup_owner, | |
| gid_t cgroup_group) { | |
| if (g_cgroup_factory) { | |
| return g_cgroup_factory(name, cgroup_root, cgroup_parent, cgroup_owner, | |
| cgroup_group); | |
| } | |
| std::unique_ptr<Cgroup> cg(new Cgroup()); | |
| for (int32_t i = 0; i < Type::NUM_TYPES; ++i) { | |
| if (!CheckCgroupAvailable(cgroup_root, kCgroupNames[i])) { | |
| if (errno == ENOENT) | |
| continue; | |
| PLOG(ERROR) << "Cgroup " << kCgroupNames[i] << " not available"; | |
| return nullptr; | |
| } | |
| if (!cgroup_parent.empty()) { | |
| const base::FilePath parent_path = | |
| cgroup_root.Append(kCgroupNames[i]).Append(cgroup_parent); | |
| cg->cgroup_paths_[i] = parent_path.Append(name); | |
| // Try to create the parent cgroup only if it doesn't exist. | |
| if (!base::DirectoryExists(parent_path)) { | |
| if (!CreateCgroupAsOwner(parent_path, cgroup_owner, cgroup_group)) { | |
| PLOG(ERROR) << "Failed to create parent cgroup " | |
| << parent_path.value() << " as owner"; | |
| return nullptr; | |
| } | |
| if (i == Type::CPUSET && !CopyCpusetParent(parent_path)) | |
| return nullptr; | |
| } | |
| } else { | |
| cg->cgroup_paths_[i] = cgroup_root.Append(kCgroupNames[i]).Append(name); | |
| } | |
| if (!CreateCgroupAsOwner(cg->cgroup_paths_[i], cgroup_owner, | |
| cgroup_group)) { | |
| PLOG(ERROR) << "Failed to create cgroup " << cg->cgroup_paths_[i].value() | |
| << " as owner"; | |
| return nullptr; | |
| } | |
| cg->cgroup_tasks_paths_[i] = cg->cgroup_paths_[i].Append("tasks"); | |
| if (i == Type::CPUSET && !CopyCpusetParent(cg->cgroup_paths_[i])) | |
| return nullptr; | |
| } | |
| cg->name_ = std::string(name); | |
| return cg; | |
| } | |
| Cgroup::Cgroup() = default; | |
| Cgroup::~Cgroup() { | |
| for (int32_t i = 0; i < Type::NUM_TYPES; ++i) { | |
| if (cgroup_paths_[i].empty()) | |
| continue; | |
| rmdir(cgroup_paths_[i].value().c_str()); | |
| } | |
| } | |
| } // namespace libcontainer |