blob: 6a52e8e6573f02d769178568f15540061f7aaaa5 [file] [log] [blame]
// 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 "diagnostics/cros_healthd/process/process_with_output.h"
#include <signal.h>
#include <base/files/file_util.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <brillo/errors/error_codes.h>
#include "diagnostics/cros_healthd/utils/helper_tool_utils.h"
namespace diagnostics {
namespace {
const char kDBusErrorString[] = "org.chromium.health.error.RunProcess";
const char kInitErrorString[] = "Process initialization failure.";
const char kStartErrorString[] = "Process start failure.";
const char kInputErrorString[] = "Process input write failure.";
} // namespace
ProcessWithOutput::ProcessWithOutput() : separate_stderr_(false) {}
ProcessWithOutput::~ProcessWithOutput() {
outfile_.reset();
errfile_.reset();
if (!outfile_path_.empty())
base::DeleteFile(outfile_path_); // not recursive
if (!errfile_path_.empty())
base::DeleteFile(errfile_path_);
}
bool ProcessWithOutput::Init() {
return Init({});
}
bool ProcessWithOutput::Init(
const std::vector<std::string>& minijail_extra_args) {
if (!SandboxedProcess::Init(minijail_extra_args)) {
return false;
}
outfile_ = base::CreateAndOpenTemporaryStream(&outfile_path_);
if (!outfile_.get()) {
return false;
}
if (separate_stderr_) {
errfile_ = base::CreateAndOpenTemporaryStream(&errfile_path_);
if (!errfile_.get()) {
return false;
}
}
// We can't just RedirectOutput to the file we just created, since
// RedirectOutput uses O_CREAT | O_EXCL to open the target file (i.e., it'll
// fail if the file already exists). We can't CreateTemporaryFile() and then
// use that filename, since we'd have to remove it before using
// RedirectOutput, which exposes us to a /tmp race. Instead, bind outfile_'s
// fd to the subprocess's stdout and stderr.
BindFd(fileno(outfile_.get()), STDOUT_FILENO);
BindFd(fileno(separate_stderr_ ? errfile_.get() : outfile_.get()),
STDERR_FILENO);
return true;
}
bool ProcessWithOutput::GetOutputLines(std::vector<std::string>* output) const {
std::string contents;
if (!GetOutput(&contents))
return false;
// If the file contains "a\nb\n", base::SplitString() will return a vector
// {"a", "b", ""} because it treats "\n" as a delimiter, not an EOL
// character. Removing the final "\n" fixes this.
if (base::EndsWith(contents, "\n", base::CompareCase::SENSITIVE)) {
contents.pop_back();
}
*output = base::SplitString(contents, "\n", base::KEEP_WHITESPACE,
base::SPLIT_WANT_ALL);
return true;
}
bool ProcessWithOutput::GetOutput(std::string* output) const {
return base::ReadFileToString(outfile_path_, output);
}
bool ProcessWithOutput::GetError(std::string* error) {
return base::ReadFileToString(errfile_path_, error);
}
int ProcessWithOutput::RunProcess(const std::string& command,
const ArgList& arguments,
bool requires_root,
const std::string* stdin,
std::string* stdout,
std::string* stderr,
brillo::ErrorPtr* error) {
ProcessWithOutput process;
if (requires_root) {
process.SandboxAs("root", "root");
}
return DoRunProcess(command, arguments, stdin, stdout, stderr, error,
&process);
}
int ProcessWithOutput::DoRunProcess(const std::string& command,
const ArgList& arguments,
const std::string* stdin,
std::string* stdout,
std::string* stderr,
brillo::ErrorPtr* error,
ProcessWithOutput* process) {
process->set_separate_stderr(true);
if (!process->Init()) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kDBusErrorString, kInitErrorString);
return kRunError;
}
process->AddArg(command);
for (const auto& argument : arguments) {
process->AddArg(argument);
}
int result = kRunError;
if (stdin) {
process->RedirectUsingPipe(STDIN_FILENO, true);
if (process->Start()) {
int stdin_fd = process->GetPipe(STDIN_FILENO);
// Kill the process if writing to or closing the pipe fails.
if (!base::WriteFileDescriptor(stdin_fd, stdin->c_str(),
stdin->length()) ||
IGNORE_EINTR(close(stdin_fd)) < 0) {
process->Kill(SIGKILL, 0);
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kDBusErrorString, kInputErrorString);
}
result = process->Wait();
} else {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kDBusErrorString, kStartErrorString);
}
} else {
result = process->Run();
}
if (stdout)
process->GetOutput(stdout);
if (stderr)
process->GetError(stderr);
return result;
}
} // namespace diagnostics