| // 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 "login_manager/container_manager_impl.h" |
| |
| #include <errno.h> |
| #include <stdint.h> |
| #include <sys/mount.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_util.h> |
| #include <base/posix/safe_strerror.h> |
| #include <base/strings/stringprintf.h> |
| |
| #include <libcontainer/libcontainer.h> |
| |
| #include "login_manager/container_config_parser.h" |
| #include "login_manager/system_utils.h" |
| |
| namespace login_manager { |
| |
| namespace { |
| |
| const char kContainerRunPath[] = "/run/containers"; |
| |
| std::string libcontainer_strerror(int err) { |
| if (err < 0) { |
| // Negative values come from -errno. Change the sign back for |
| // safe_strerror(). |
| return base::safe_strerror(-err); |
| } else { |
| // Otherwise, it might have been a libminijail error code. |
| return base::StringPrintf("libminijail error code %d", err); |
| } |
| } |
| |
| ContainerManagerImpl::ContainerPtr CreateContainer(const std::string& name) { |
| return ContainerManagerImpl::ContainerPtr( |
| container_new(name.c_str(), kContainerRunPath), &container_destroy); |
| } |
| |
| } // anonymous namespace |
| |
| ContainerManagerImpl::ContainerManagerImpl( |
| SystemUtils* system_utils, |
| const base::FilePath& containers_directory, |
| const std::string& name) |
| : system_utils_(system_utils), |
| container_directory_(containers_directory.Append(name)), |
| name_(name), |
| container_(nullptr, &container_destroy), |
| clean_exit_(false) { |
| DCHECK(system_utils_); |
| } |
| |
| ContainerManagerImpl::~ContainerManagerImpl() {} |
| |
| bool ContainerManagerImpl::IsManagedJob(pid_t pid) { |
| pid_t container_pid; |
| return GetContainerPID(&container_pid) && container_pid == pid; |
| } |
| |
| void ContainerManagerImpl::HandleExit(const siginfo_t& status) { |
| pid_t pid; |
| if (!GetContainerPID(&pid)) { |
| LOG(ERROR) << "Container " << name_ << " unexpected exit."; |
| return; |
| } |
| |
| CleanUpContainer(pid); |
| } |
| |
| void ContainerManagerImpl::RequestJobExit() { |
| pid_t pid; |
| if (!GetContainerPID(&pid)) |
| return; |
| |
| // If HandleExit() is called after this point, it is considered clean. |
| clean_exit_ = true; |
| |
| if (!RequestTermination()) { |
| LOG(INFO) << "Killing off container " << name_; |
| int rc = container_kill(container_.get()); |
| if (rc != 0) { |
| LOG(ERROR) << "Failed to kill container " << name_ << ": " |
| << libcontainer_strerror(rc); |
| } |
| } |
| } |
| |
| void ContainerManagerImpl::EnsureJobExit(base::TimeDelta timeout) { |
| if (!container_) |
| return; |
| |
| pid_t pid; |
| if (!GetContainerPID(&pid)) |
| return; |
| |
| if (!system_utils_->ProcessGroupIsGone(pid, timeout)) { |
| LOG(INFO) << "Killing off container " << name_; |
| int rc = container_kill(container_.get()); |
| if (rc != 0) { |
| LOG(ERROR) << "Failed to kill container " << name_ << ": " |
| << libcontainer_strerror(rc); |
| } |
| } |
| CleanUpContainer(pid); |
| } |
| |
| bool ContainerManagerImpl::StartContainer(const ExitCallback& exit_callback) { |
| // TODO(lhchavez): Make logging less verbose once we're comfortable that |
| // everything works correctly. See b/29266253. |
| LOG(INFO) << "Starting container " << name_; |
| if (container_) { |
| LOG(ERROR) << "Container " << name_ << " already running"; |
| return false; |
| } |
| |
| std::string config_json_data; |
| if (!base::ReadFileToString(container_directory_.Append("config.json"), |
| &config_json_data)) { |
| PLOG(ERROR) << "Fail to read config for " << name_; |
| return false; |
| } |
| |
| std::string runtime_json_data; |
| if (!base::ReadFileToString(container_directory_.Append("runtime.json"), |
| &runtime_json_data)) { |
| LOG(ERROR) << "Fail to read runtime config for " << name_; |
| return false; |
| } |
| |
| ContainerConfigPtr config(container_config_create(), |
| &container_config_destroy); |
| if (!ParseContainerConfig(config_json_data, runtime_json_data, name_, |
| container_directory_, &config)) { |
| LOG(ERROR) << "Failed to parse container configuration for " << name_; |
| return false; |
| } |
| |
| ContainerPtr new_container = CreateContainer(name_); |
| if (!new_container) { |
| LOG(ERROR) << "Failed to create the new container named " << name_; |
| return false; |
| } |
| |
| int rc = container_start(new_container.get(), config.get()); |
| if (rc != 0) { |
| LOG(ERROR) << "Failed to start container " << name_ << ": " |
| << libcontainer_strerror(rc); |
| return false; |
| } |
| |
| container_ = std::move(new_container); |
| exit_callback_ = exit_callback; |
| clean_exit_ = false; |
| |
| return true; |
| } |
| |
| bool ContainerManagerImpl::GetRootFsPath(base::FilePath* path_out) const { |
| if (!container_) |
| return false; |
| *path_out = base::FilePath(container_root(container_.get())); |
| return true; |
| } |
| |
| bool ContainerManagerImpl::GetContainerPID(pid_t* pid_out) const { |
| if (!container_) |
| return false; |
| *pid_out = container_pid(container_.get()); |
| return true; |
| } |
| |
| bool ContainerManagerImpl::RequestTermination() { |
| return false; |
| } |
| |
| void ContainerManagerImpl::OnContainerStopped(bool clean) {} |
| |
| void ContainerManagerImpl::CleanUpContainer(pid_t pid) { |
| if (!container_) |
| return; |
| |
| LOG(INFO) << "Cleaning up container " << name_; |
| int rc = container_wait(container_.get()); |
| if (rc != 0) { |
| LOG(ERROR) << "Failed to clean up container " << name_ << ": " |
| << libcontainer_strerror(rc); |
| } |
| |
| // Save callbacks until after everything has been cleaned up. |
| ExitCallback exit_callback; |
| std::swap(exit_callback_, exit_callback); |
| |
| container_.reset(); |
| exit_callback_.Reset(); |
| |
| OnContainerStopped(clean_exit_); |
| |
| if (!exit_callback.is_null()) |
| exit_callback.Run(pid, clean_exit_); |
| } |
| |
| } // namespace login_manager |