| // Copyright (c) 2012 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 "chromeos/process.h" |
| |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <stdint.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include <map> |
| |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| #include <base/time/time.h> |
| |
| namespace chromeos { |
| |
| bool ReturnTrue() { |
| return true; |
| } |
| |
| Process::Process() { |
| } |
| |
| Process::~Process() { |
| } |
| |
| bool Process::ProcessExists(pid_t pid) { |
| return base::DirectoryExists( |
| base::FilePath(base::StringPrintf("/proc/%d", pid))); |
| } |
| |
| ProcessImpl::ProcessImpl() |
| : pid_(0), |
| uid_(-1), |
| gid_(-1), |
| pre_exec_(base::Bind(&ReturnTrue)), |
| search_path_(false), |
| inherit_parent_signal_mask_(false) { |
| } |
| |
| ProcessImpl::~ProcessImpl() { |
| Reset(0); |
| } |
| |
| void ProcessImpl::AddArg(const std::string& arg) { |
| arguments_.push_back(arg); |
| } |
| |
| void ProcessImpl::RedirectOutput(const std::string& output_file) { |
| output_file_ = output_file; |
| } |
| |
| void ProcessImpl::RedirectUsingPipe(int child_fd, bool is_input) { |
| PipeInfo info; |
| info.is_input_ = is_input; |
| info.is_bound_ = false; |
| pipe_map_[child_fd] = info; |
| } |
| |
| void ProcessImpl::BindFd(int parent_fd, int child_fd) { |
| PipeInfo info; |
| info.is_bound_ = true; |
| |
| // info.child_fd_ is the 'child half' of the pipe, which gets dup2()ed into |
| // place over child_fd. Since we already have the child we want to dup2() into |
| // place, we can set info.child_fd_ to parent_fd and leave info.parent_fd_ |
| // invalid. |
| info.child_fd_ = parent_fd; |
| info.parent_fd_ = -1; |
| pipe_map_[child_fd] = info; |
| } |
| |
| void ProcessImpl::SetUid(uid_t uid) { |
| uid_ = uid; |
| } |
| |
| void ProcessImpl::SetGid(gid_t gid) { |
| gid_ = gid; |
| } |
| |
| void ProcessImpl::SetInheritParentSignalMask(bool inherit) { |
| inherit_parent_signal_mask_ = inherit; |
| } |
| |
| void ProcessImpl::SetPreExecCallback(const PreExecCallback& cb) { |
| pre_exec_ = cb; |
| } |
| |
| void ProcessImpl::SetSearchPath(bool search_path) { |
| search_path_ = search_path; |
| } |
| |
| int ProcessImpl::GetPipe(int child_fd) { |
| PipeMap::iterator i = pipe_map_.find(child_fd); |
| if (i == pipe_map_.end()) |
| return -1; |
| else |
| return i->second.parent_fd_; |
| } |
| |
| bool ProcessImpl::PopulatePipeMap() { |
| // Verify all target fds are already open. With this assumption we |
| // can be sure that the pipe fds created below do not overlap with |
| // any of the target fds which simplifies how we dup2 to them. Note |
| // that multi-threaded code could close i->first between this loop |
| // and the next. |
| for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) { |
| struct stat stat_buffer; |
| if (fstat(i->first, &stat_buffer) < 0) { |
| int saved_errno = errno; |
| LOG(ERROR) << "Unable to fstat fd " << i->first << ": " << saved_errno; |
| return false; |
| } |
| } |
| |
| for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) { |
| if (i->second.is_bound_) { |
| // already have a parent fd, and the child fd gets dup()ed later. |
| continue; |
| } |
| int pipefds[2]; |
| if (pipe(pipefds) < 0) { |
| int saved_errno = errno; |
| LOG(ERROR) << "pipe call failed with: " << saved_errno; |
| return false; |
| } |
| if (i->second.is_input_) { |
| // pipe is an input from the prospective of the child. |
| i->second.parent_fd_ = pipefds[1]; |
| i->second.child_fd_ = pipefds[0]; |
| } else { |
| i->second.parent_fd_ = pipefds[0]; |
| i->second.child_fd_ = pipefds[1]; |
| } |
| } |
| return true; |
| } |
| |
| bool ProcessImpl::Start() { |
| scoped_ptr<char*[]> argv(new char*[arguments_.size() + 1]); |
| |
| for (size_t i = 0; i < arguments_.size(); ++i) |
| argv[i] = const_cast<char*>(arguments_[i].c_str()); |
| |
| argv[arguments_.size()] = nullptr; |
| |
| if (!PopulatePipeMap()) { |
| LOG(ERROR) << "Failing to start because pipe creation failed"; |
| return false; |
| } |
| |
| pid_t pid = fork(); |
| int saved_errno = errno; |
| if (pid < 0) { |
| LOG(ERROR) << "Fork failed: " << saved_errno; |
| Reset(0); |
| return false; |
| } |
| |
| if (pid == 0) { |
| // Executing inside the child process. |
| // Close parent's side of the child pipes. dup2 ours into place and |
| // then close our ends. |
| for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) { |
| IGNORE_EINTR(close(i->second.parent_fd_)); |
| HANDLE_EINTR(dup2(i->second.child_fd_, i->first)); |
| } |
| // Defer the actual close() of the child fd until afterward; this lets the |
| // same child fd be bound to multiple fds using BindFd |
| for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) { |
| IGNORE_EINTR(close(i->second.child_fd_)); |
| } |
| if (!output_file_.empty()) { |
| int output_handle = HANDLE_EINTR(open( |
| output_file_.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW, |
| 0666)); |
| if (output_handle < 0) { |
| PLOG(ERROR) << "Could not create " << output_file_; |
| // Avoid exit() to avoid atexit handlers from parent. |
| _exit(kErrorExitStatus); |
| } |
| HANDLE_EINTR(dup2(output_handle, STDOUT_FILENO)); |
| HANDLE_EINTR(dup2(output_handle, STDERR_FILENO)); |
| // Only close output_handle if it does not happen to be one of |
| // the two standard file descriptors we are trying to redirect. |
| if (output_handle != STDOUT_FILENO && output_handle != STDERR_FILENO) { |
| IGNORE_EINTR(close(output_handle)); |
| } |
| } |
| if (gid_ != static_cast<gid_t>(-1) && setresgid(gid_, gid_, gid_) < 0) { |
| int saved_errno = errno; |
| LOG(ERROR) << "Unable to set GID to " << gid_ << ": " << saved_errno; |
| _exit(kErrorExitStatus); |
| } |
| if (uid_ != static_cast<uid_t>(-1) && setresuid(uid_, uid_, uid_) < 0) { |
| int saved_errno = errno; |
| LOG(ERROR) << "Unable to set UID to " << uid_ << ": " << saved_errno; |
| _exit(kErrorExitStatus); |
| } |
| if (!pre_exec_.Run()) { |
| LOG(ERROR) << "Pre-exec callback failed"; |
| _exit(kErrorExitStatus); |
| } |
| // Reset signal mask for the child process if not inheriting signal mask |
| // from the parent process. |
| if (!inherit_parent_signal_mask_) { |
| sigset_t signal_mask; |
| CHECK_EQ(0, sigemptyset(&signal_mask)); |
| CHECK_EQ(0, sigprocmask(SIG_SETMASK, &signal_mask, nullptr)); |
| } |
| if (search_path_) { |
| execvp(argv[0], &argv[0]); |
| } else { |
| execv(argv[0], &argv[0]); |
| } |
| saved_errno = errno; |
| LOG(ERROR) << "Exec of " << argv[0] << " failed: " << saved_errno; |
| _exit(kErrorExitStatus); |
| } else { |
| // Still executing inside the parent process with known child pid. |
| arguments_.clear(); |
| UpdatePid(pid); |
| // Close our copy of child side pipes. |
| for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) { |
| IGNORE_EINTR(close(i->second.child_fd_)); |
| } |
| } |
| return true; |
| } |
| |
| int ProcessImpl::Wait() { |
| int status = 0; |
| if (pid_ == 0) { |
| LOG(ERROR) << "Process not running"; |
| return -1; |
| } |
| if (HANDLE_EINTR(waitpid(pid_, &status, 0)) < 0) { |
| int saved_errno = errno; |
| LOG(ERROR) << "Problem waiting for pid " << pid_ << ": " << saved_errno; |
| return -1; |
| } |
| pid_t old_pid = pid_; |
| // Update the pid to 0 - do not Reset as we do not want to try to |
| // kill the process that has just exited. |
| UpdatePid(0); |
| if (!WIFEXITED(status)) { |
| DCHECK(WIFSIGNALED(status)) << old_pid |
| << " neither exited, nor died on a signal?"; |
| LOG(ERROR) << "Process " << old_pid |
| << " did not exit normally: " << WTERMSIG(status); |
| return -1; |
| } |
| return WEXITSTATUS(status); |
| } |
| |
| int ProcessImpl::Run() { |
| if (!Start()) { |
| return -1; |
| } |
| return Wait(); |
| } |
| |
| pid_t ProcessImpl::pid() { |
| return pid_; |
| } |
| |
| bool ProcessImpl::Kill(int signal, int timeout) { |
| if (pid_ == 0) { |
| // Passing pid == 0 to kill is committing suicide. Check specifically. |
| LOG(ERROR) << "Process not running"; |
| return false; |
| } |
| if (kill(pid_, signal) < 0) { |
| int saved_errno = errno; |
| LOG(ERROR) << "Unable to send signal to " << pid_ << " error " |
| << saved_errno; |
| return false; |
| } |
| base::TimeTicks start_signal = base::TimeTicks::Now(); |
| do { |
| int status = 0; |
| pid_t w = waitpid(pid_, &status, WNOHANG); |
| int saved_errno = errno; |
| if (w < 0) { |
| if (saved_errno == ECHILD) |
| return true; |
| LOG(ERROR) << "Waitpid returned " << w << ", errno " << saved_errno; |
| return false; |
| } |
| if (w > 0) { |
| Reset(0); |
| return true; |
| } |
| usleep(100); |
| } while ((base::TimeTicks::Now() - start_signal).InSecondsF() <= timeout); |
| LOG(INFO) << "process " << pid_ << " did not exit from signal " << signal |
| << " in " << timeout << " seconds"; |
| return false; |
| } |
| |
| void ProcessImpl::UpdatePid(pid_t new_pid) { |
| pid_ = new_pid; |
| } |
| |
| void ProcessImpl::Reset(pid_t new_pid) { |
| arguments_.clear(); |
| // Close our side of all pipes to this child giving the child to |
| // handle sigpipes and shutdown nicely, though likely it won't |
| // have time. |
| for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) |
| IGNORE_EINTR(close(i->second.parent_fd_)); |
| pipe_map_.clear(); |
| if (pid_) |
| Kill(SIGKILL, 0); |
| UpdatePid(new_pid); |
| } |
| |
| bool ProcessImpl::ResetPidByFile(const std::string& pid_file) { |
| std::string contents; |
| if (!base::ReadFileToString(base::FilePath(pid_file), &contents)) { |
| LOG(ERROR) << "Could not read pid file" << pid_file; |
| return false; |
| } |
| base::TrimWhitespaceASCII(contents, base::TRIM_TRAILING, &contents); |
| int64_t pid_int64 = 0; |
| if (!base::StringToInt64(contents, &pid_int64)) { |
| LOG(ERROR) << "Unexpected pid file contents"; |
| return false; |
| } |
| Reset(pid_int64); |
| return true; |
| } |
| |
| pid_t ProcessImpl::Release() { |
| pid_t old_pid = pid_; |
| pid_ = 0; |
| return old_pid; |
| } |
| |
| } // namespace chromeos |