blob: 1d6772499942b229e819c33989e5ce2b40120eae [file] [log] [blame]
// Copyright 2010 The Goma 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 "subprocess.h"
#include <fcntl.h>
#include <stdio.h>
#ifndef _WIN32
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#include <algorithm>
#include <deque>
#include <iostream>
#include <memory>
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "compiler_flags.h"
#include "compiler_specific.h"
#include "env_flags.h"
#include "file_stat.h"
#include "glog/logging.h"
#include "ioutil.h"
#include "path.h"
#include "scoped_fd.h"
#ifndef _WIN32
#include "spawner_posix.h"
#else
#include "spawner_win.h"
#endif
using std::string;
namespace {
#ifdef _WIN32
string GetPathExt(const std::vector<string>& envs) {
return devtools_goma::GetEnvFromEnvIter(envs.begin(), envs.end(), "PATHEXT");
}
#else
string GetPathExt(const std::vector<string>& envs ALLOW_UNUSED) {
return "";
}
#endif
bool GetRealPrognameAndEnvs(const devtools_goma::FileStat* gomacc_filestat,
const string& prog,
const std::vector<string>& args,
std::vector<string>* envs,
string* real_progname) {
static const char kPath[] = "PATH";
*real_progname = prog;
if (gomacc_filestat != nullptr) {
// We should set ReadCommand to avoid gomacc in GetRealExecutablePath.
#ifndef _WIN32
InstallReadCommandOutputFunc(devtools_goma::ReadCommandOutputByPopen);
#else
InstallReadCommandOutputFunc(devtools_goma::ReadCommandOutputByRedirector);
#endif
}
string no_goma_env_path;
if (!GetRealExecutablePath(
gomacc_filestat, prog, ".",
devtools_goma::GetEnvFromEnvIter(envs->begin(), envs->end(), kPath),
GetPathExt(*envs), real_progname, &no_goma_env_path, nullptr)) {
LOG(ERROR) << "failed to get executable path."
<< " prog=" << prog
<< " path=" << devtools_goma::GetEnvFromEnvIter(
envs->begin(), envs->end(), kPath)
<< " pathext=" << GetPathExt(*envs);
return false;
}
if (!devtools_goma::ReplaceEnvInEnvIter(envs->begin(), envs->end(),
kPath, no_goma_env_path)) {
LOG(ERROR) << "failed to replace path env."
<< " kPath=" << kPath
<< " path=" << devtools_goma::GetEnvFromEnvIter(
envs->begin(), envs->end(), kPath)
<< " no_goma_env_path=" << no_goma_env_path;
return false;
}
return true;
}
} // namespace
namespace devtools_goma {
#ifdef _WIN32
int SpawnAndWait(const string& prog, const std::vector<string>& args,
const std::vector<string>& envs) {
return SpawnAndWaitNonGomacc(nullptr, prog, args, envs);
}
int SpawnAndWaitNonGomacc(const FileStat* gomacc_filestat,
const string& prog,
const std::vector<string>& args,
std::vector<string> envs) {
string real_progname;
GetRealPrognameAndEnvs(gomacc_filestat, prog, args, &envs, &real_progname);
std::unique_ptr<SpawnerWin> spawner(new SpawnerWin);
int status = spawner->Run(
real_progname, args, envs, GetCurrentDirNameOrDie());
if (status == Spawner::kInvalidPid) {
return -1;
}
while (spawner->IsChildRunning())
spawner->Wait(Spawner::WAIT_INFINITE);
return spawner->ChildStatus();
}
#else
int Execvpe(const string& prog, const std::vector<string>& args,
const std::vector<string>& envs) {
return ExecvpeNonGomacc(nullptr, prog, args, envs);
}
int ExecvpeNonGomacc(const FileStat* gomacc_filestat,
const string& prog,
const std::vector<string>& args,
std::vector<string> envs) {
string real_progname;
GetRealPrognameAndEnvs(gomacc_filestat, prog, args, &envs, &real_progname);
std::vector<const char*> argvp;
std::vector<const char*> envp;
for (const auto& arg : args) {
argvp.push_back(arg.c_str());
}
argvp.push_back(nullptr);
for (const auto& env : envs) {
envp.push_back(env.c_str());
}
envp.push_back(nullptr);
return execve(real_progname.c_str(), const_cast<char**>(&argvp[0]),
const_cast<char**>(&envp[0]));
}
#endif
#ifndef _WIN32
string ReadCommandOutputByPopen(
const string& prog, const std::vector<string>& argv,
const std::vector<string>& envs,
const string& cwd, CommandOutputOption option, int32_t* status) {
string commandline;
if (!cwd.empty()) {
commandline = "sh -c 'cd " + cwd + " && ";
}
for (const auto& env : envs)
commandline += env + " ";
for (const auto& arg : argv) {
// Escaping only <, >, ( and ) is OK for now.
if (arg.find_first_of(" <>();&'#") == string::npos) {
CHECK(arg.find_first_of("\\\"") == string::npos) << arg;
commandline += arg + " ";
} else {
commandline += "\"" + arg + "\" ";
}
}
if (!cwd.empty()) {
commandline += "'";
}
if (option == MERGE_STDOUT_STDERR)
commandline += " 2>&1";
FILE* p = popen(commandline.c_str(), "r");
CHECK(p) << "popen for " << prog << " (" << commandline << ") failed";
std::ostringstream strbuf;
while (true) {
const size_t kBufSize = 64 * 1024;
char buf[kBufSize];
size_t len = fread(buf, 1, kBufSize, p);
if (len == 0) {
if (errno == EINTR)
continue;
CHECK(feof(p)) << "could not read output for: " << commandline;
break;
}
strbuf.write(buf, len);
}
int exit_status = pclose(p);
if (status) {
*status = exit_status;
} else {
LOG_IF(FATAL, exit_status != 0)
<< "If the caller expects the non-zero exit status, "
<< "the caller must set non-nullptr status in the argument."
<< " prog=" << prog
<< " args=" << absl::StrJoin(argv, " ")
<< " cwd=" << cwd
<< " exit_status=" << exit_status
<< " output=" << strbuf.str();
}
return strbuf.str();
}
void Daemonize(const string& stderr_filename, int pid_record_fd,
const std::set<int>& preserve_fds) {
PCHECK(setsid() >= 0);
PCHECK(chdir("/") == 0);
umask(0);
// Fork again, so we'll never get tty.
pid_t pid;
if ((pid = fork())) {
PCHECK(pid > 0);
exit(0);
}
pid = Getpid();
if (pid_record_fd >= 0) {
PCHECK(write(pid_record_fd, &pid, sizeof(pid)) == sizeof(pid));
} else {
std::cout << pid << std::endl;
}
int devnullfd = ScopedFd::OpenNull();
CHECK_GE(devnullfd, 0);
PCHECK(dup2(devnullfd, STDIN_FILENO) >= 0);
PCHECK(dup2(devnullfd, STDOUT_FILENO) >= 0);
int stderrfd = -1;
if (!stderr_filename.empty())
stderrfd = open(stderr_filename.c_str(), O_WRONLY|O_CREAT, 0660);
if (stderrfd >= 0) {
PCHECK(dup2(stderrfd, STDERR_FILENO) >= 0);
} else {
PCHECK(dup2(devnullfd, STDERR_FILENO) >= 0);
}
// Close all file descriptors except stdin/stdout/stderr and in preserve_fds.
int maxfd = sysconf(_SC_OPEN_MAX);
for (int fd = STDERR_FILENO + 1; fd < maxfd; ++fd) {
if (preserve_fds.count(fd) == 0)
close(fd);
}
}
#else
string ReadCommandOutputByRedirector(const string& prog,
const std::vector<string>& argv, const std::vector<string>& env,
const string& cwd, CommandOutputOption option, int32_t* status) {
SpawnerWin spawner;
Spawner::ConsoleOutputOption output_option =
Spawner::MERGE_STDOUT_STDERR;
if (option == STDOUT_ONLY)
output_option = Spawner::STDOUT_ONLY;
string output;
spawner.SetConsoleOutputBuffer(&output, output_option);
spawner.Run(prog, argv, env, cwd);
while (spawner.IsChildRunning())
spawner.Wait(Spawner::WAIT_INFINITE);
int exit_status = spawner.ChildStatus();
if (status) {
*status = exit_status;
} else {
LOG_IF(FATAL, exit_status != 0)
<< "If the caller expects the non-zero exit status, "
<< "the caller must set non-nullptr status in the argument."
<< " prog=" << prog
<< " cwd=" << cwd
<< " exit_status=" << exit_status;
}
return output;
}
#endif
} // namespace devtools_goma