text: // Copyright 2016 The Chromium OS Authors. All rights reserved.
  // 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 <base/bind.h>
  #include <base/callback.h>
  #include <base/callback_helpers.h>
  #include <base/files/file_util.h>
  #include <base/files/scoped_file.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", "schedtune", "memory"};
  
  base::ScopedFD OpenCgroupFile(const base::FilePath& cgroup_path,
                                base::StringPiece 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,
                       base::StringPiece name,
                       base::StringPiece str) {
    base::ScopedFD fd = OpenCgroupFile(cgroup_path, name, true);
    if (!fd.is_valid())
      return false;
    if (!base::WriteFileDescriptor(fd.get(), str.data(), str.size()))
      return false;
    return true;
  }
  
  bool WriteCgroupFileInt(const base::FilePath& cgroup_path,
                          base::StringPiece name,
                          const int value) {
    return WriteCgroupFile(cgroup_path, name, base::NumberToString(value));
  }
  
  bool CopyCgroupParent(const base::FilePath& cgroup_path,
                        base::StringPiece 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;
  
    char buffer[256];
    ssize_t bytes_read;
    while ((bytes_read = read(source.get(), buffer, sizeof(buffer))) > 0) {
      if (!base::WriteFileDescriptor(dest.get(), buffer, bytes_read))
        return false;
    }
    if (bytes_read < 0)
      return false;
  
    return true;
  }
  
  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::Bind(
          [](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,
                            base::StringPiece 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(base::StringPiece 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_ = name.as_string();
  
    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
  