mirror of
https://github.com/Icinga/icinga2.git
synced 2025-08-23 10:38:16 +02:00
starting with the longest allowed suffix until it's small enough. This way no string gets discarted completely in favor of a longer one before it. Also, the computation happens only if necessary and in the already fork(2)ed child process. The pragmatic implementation doesn't even try to mimic kernels' limits perfectly.
1291 lines
31 KiB
C++
1291 lines
31 KiB
C++
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
|
|
|
#include "base/process.hpp"
|
|
#include "base/exception.hpp"
|
|
#include "base/convert.hpp"
|
|
#include "base/array.hpp"
|
|
#include "base/objectlock.hpp"
|
|
#include "base/utility.hpp"
|
|
#include "base/initialize.hpp"
|
|
#include "base/logger.hpp"
|
|
#include "base/utility.hpp"
|
|
#include "base/scriptglobal.hpp"
|
|
#include "base/json.hpp"
|
|
#include <cstddef>
|
|
#include <boost/algorithm/string/join.hpp>
|
|
#include <boost/thread/once.hpp>
|
|
#include <thread>
|
|
#include <iostream>
|
|
#include <utility>
|
|
|
|
#ifndef _WIN32
|
|
# include <errno.h>
|
|
# include <execvpe.h>
|
|
# include <poll.h>
|
|
# include <string.h>
|
|
|
|
# ifndef __APPLE__
|
|
extern char **environ;
|
|
# else /* __APPLE__ */
|
|
# include <crt_externs.h>
|
|
# define environ (*_NSGetEnviron())
|
|
# endif /* __APPLE__ */
|
|
#endif /* _WIN32 */
|
|
|
|
using namespace icinga;
|
|
|
|
#define IOTHREADS 4
|
|
|
|
static std::mutex l_ProcessMutex[IOTHREADS];
|
|
static std::map<Process::ProcessHandle, Process::Ptr> l_Processes[IOTHREADS];
|
|
#ifdef _WIN32
|
|
static HANDLE l_Events[IOTHREADS];
|
|
#else /* _WIN32 */
|
|
static int l_EventFDs[IOTHREADS][2];
|
|
static std::map<Process::ConsoleHandle, Process::ProcessHandle> l_FDs[IOTHREADS];
|
|
|
|
static std::mutex l_ProcessControlMutex;
|
|
static int l_ProcessControlFD = -1;
|
|
static pid_t l_ProcessControlPID;
|
|
#endif /* _WIN32 */
|
|
static boost::once_flag l_ProcessOnceFlag = BOOST_ONCE_INIT;
|
|
static boost::once_flag l_SpawnHelperOnceFlag = BOOST_ONCE_INIT;
|
|
|
|
Process::Process(Process::Arguments arguments, Dictionary::Ptr extraEnvironment, Array::Ptr safeToTruncate)
|
|
: m_Arguments(std::move(arguments)), m_ExtraEnvironment(std::move(extraEnvironment)),
|
|
m_SafeToTruncate(std::move(safeToTruncate)), m_Timeout(600)
|
|
#ifdef _WIN32
|
|
, m_ReadPending(false), m_ReadFailed(false), m_Overlapped()
|
|
#else /* _WIN32 */
|
|
, m_SentSigterm(false)
|
|
#endif /* _WIN32 */
|
|
, m_AdjustPriority(false), m_ResultAvailable(false)
|
|
{
|
|
#ifdef _WIN32
|
|
m_Overlapped.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
|
#endif /* _WIN32 */
|
|
}
|
|
|
|
Process::~Process()
|
|
{
|
|
#ifdef _WIN32
|
|
CloseHandle(m_Overlapped.hEvent);
|
|
#endif /* _WIN32 */
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
static Value ProcessSpawnImpl(struct msghdr *msgh, const Dictionary::Ptr& request)
|
|
{
|
|
struct cmsghdr *cmsg = CMSG_FIRSTHDR(msgh);
|
|
|
|
if (cmsg == nullptr || cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_len != CMSG_LEN(sizeof(int) * 3)) {
|
|
std::cerr << "Invalid 'spawn' request: FDs missing" << std::endl;
|
|
return Empty;
|
|
}
|
|
|
|
auto *fds = (int *)CMSG_DATA(cmsg);
|
|
|
|
Array::Ptr arguments = request->Get("arguments");
|
|
Dictionary::Ptr extraEnvironment = request->Get("extraEnvironment");
|
|
Array::Ptr safeToTruncate = request->Get("safeToTruncate");
|
|
bool adjustPriority = request->Get("adjustPriority");
|
|
|
|
// build argv
|
|
auto **argv = new char *[arguments->GetLength() + 1];
|
|
|
|
for (unsigned int i = 0; i < arguments->GetLength(); i++) {
|
|
String arg = arguments->Get(i);
|
|
argv[i] = strdup(arg.CStr());
|
|
}
|
|
|
|
argv[arguments->GetLength()] = nullptr;
|
|
|
|
// build envp
|
|
int envc = 0;
|
|
|
|
/* count existing environment variables */
|
|
while (environ[envc])
|
|
envc++;
|
|
|
|
auto **envp = new char *[envc + (extraEnvironment ? extraEnvironment->GetLength() : 0) + 2];
|
|
const char* lcnumeric = "LC_NUMERIC=";
|
|
const char* notifySocket = "NOTIFY_SOCKET=";
|
|
int j = 0;
|
|
|
|
for (int i = 0; i < envc; i++) {
|
|
if (strncmp(environ[i], lcnumeric, strlen(lcnumeric)) == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (strncmp(environ[i], notifySocket, strlen(notifySocket)) == 0) {
|
|
continue;
|
|
}
|
|
|
|
envp[j] = strdup(environ[i]);
|
|
++j;
|
|
}
|
|
|
|
if (extraEnvironment) {
|
|
ObjectLock olock(extraEnvironment);
|
|
|
|
for (const Dictionary::Pair& kv : extraEnvironment) {
|
|
String skv = kv.first + "=" + Convert::ToString(kv.second);
|
|
envp[j] = strdup(skv.CStr());
|
|
j++;
|
|
}
|
|
}
|
|
|
|
envp[j++] = strdup("LC_NUMERIC=C");
|
|
envp[j] = nullptr;
|
|
|
|
extraEnvironment.reset();
|
|
|
|
std::vector<std::pair<const char*, size_t>> safeToTruncateStrings;
|
|
std::vector<char*> safeToTruncateArgs;
|
|
|
|
if (safeToTruncate) {
|
|
// Lock here recursively, not in the fork(2)ed child
|
|
ObjectLock lock (safeToTruncate);
|
|
|
|
safeToTruncateStrings.reserve(safeToTruncate->GetLength());
|
|
|
|
for (auto& v : safeToTruncate) {
|
|
if (v.GetType() == ValueString) {
|
|
auto s (v.Get<String>().CStr());
|
|
auto len (strlen(s)); // exec(3) uses C strings, so we do
|
|
|
|
// The shorter the string, the more false positives are possible.
|
|
// E.g. "-" could appear literally everywhere, but it won't exceed any limit.
|
|
if (len >= 1024u) {
|
|
safeToTruncateStrings.emplace_back(s, len);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!safeToTruncateStrings.empty()) {
|
|
// malloc(3) here, not in the fork(2)ed child
|
|
safeToTruncateArgs.reserve(arguments->GetLength() + j);
|
|
}
|
|
|
|
pid_t pid = fork();
|
|
|
|
int errorCode = 0;
|
|
|
|
if (pid < 0)
|
|
errorCode = errno;
|
|
|
|
if (pid == 0) {
|
|
// child process
|
|
|
|
(void)close(l_ProcessControlFD);
|
|
|
|
if (setsid() < 0) {
|
|
perror("setsid() failed");
|
|
_exit(128);
|
|
}
|
|
|
|
if (dup2(fds[0], STDIN_FILENO) < 0 || dup2(fds[1], STDOUT_FILENO) < 0 || dup2(fds[2], STDERR_FILENO) < 0) {
|
|
perror("dup2() failed");
|
|
_exit(128);
|
|
}
|
|
|
|
(void)close(fds[0]);
|
|
(void)close(fds[1]);
|
|
(void)close(fds[2]);
|
|
|
|
#ifdef HAVE_NICE
|
|
if (adjustPriority) {
|
|
// Cheating the compiler on "warning: ignoring return value of 'int nice(int)', declared with attribute warn_unused_result [-Wunused-result]".
|
|
auto x (nice(5));
|
|
(void)x;
|
|
}
|
|
#endif /* HAVE_NICE */
|
|
|
|
sigset_t mask;
|
|
sigemptyset(&mask);
|
|
sigprocmask(SIG_SETMASK, &mask, nullptr);
|
|
|
|
for (;;) {
|
|
if (icinga2_execvpe(argv[0], argv, envp) < 0) {
|
|
if (errno == E2BIG && !safeToTruncateStrings.empty()) {
|
|
if (safeToTruncateArgs.empty()) {
|
|
// Initialize safeToTruncateArgs
|
|
for (char** strings : {argv, envp}) {
|
|
for (auto it (strings); *it; ++it) {
|
|
auto s (*it);
|
|
auto len (strlen(s));
|
|
|
|
for (auto [suffixCstr, suffixLen] : safeToTruncateStrings) {
|
|
if (suffixLen <= len) {
|
|
auto substr (s + len - suffixLen);
|
|
|
|
// Allow truncating an arg only if it ends with a safe to truncate string
|
|
if (!strcmp(substr, suffixCstr)) {
|
|
// Allow truncating only the safe part of a string,
|
|
// e.g. "-foo=bar" => "-foo=", but not "-foo=bar" => "-f"
|
|
safeToTruncateArgs.emplace_back(substr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
char* longest = nullptr;
|
|
size_t longestLen = 0;
|
|
|
|
for (auto s : safeToTruncateArgs) {
|
|
if (longest) {
|
|
auto len (strlen(s));
|
|
|
|
if (len > longestLen) {
|
|
longest = s;
|
|
longestLen = len;
|
|
}
|
|
} else {
|
|
longest = s;
|
|
longestLen = strlen(longest);
|
|
}
|
|
}
|
|
|
|
if (longest) {
|
|
longest[longestLen * 3u / 4u] = 0;
|
|
continue; // Re-try
|
|
}
|
|
}
|
|
|
|
char errmsg[512];
|
|
strcpy(errmsg, "execvpe(");
|
|
strncat(errmsg, argv[0], sizeof(errmsg) - strlen(errmsg) - 1);
|
|
strncat(errmsg, ") failed", sizeof(errmsg) - strlen(errmsg) - 1);
|
|
errmsg[sizeof(errmsg) - 1] = '\0';
|
|
perror(errmsg);
|
|
}
|
|
|
|
_exit(128);
|
|
}
|
|
}
|
|
|
|
(void)close(fds[0]);
|
|
(void)close(fds[1]);
|
|
(void)close(fds[2]);
|
|
|
|
// free arguments
|
|
for (int i = 0; argv[i]; i++)
|
|
free(argv[i]);
|
|
|
|
delete[] argv;
|
|
|
|
// free environment
|
|
for (int i = 0; envp[i]; i++)
|
|
free(envp[i]);
|
|
|
|
delete[] envp;
|
|
|
|
Dictionary::Ptr response = new Dictionary({
|
|
{ "rc", pid },
|
|
{ "errno", errorCode }
|
|
});
|
|
|
|
return response;
|
|
}
|
|
|
|
static Value ProcessKillImpl(struct msghdr *msgh, const Dictionary::Ptr& request)
|
|
{
|
|
pid_t pid = request->Get("pid");
|
|
int signum = request->Get("signum");
|
|
|
|
errno = 0;
|
|
kill(pid, signum);
|
|
int error = errno;
|
|
|
|
Dictionary::Ptr response = new Dictionary({
|
|
{ "errno", error }
|
|
});
|
|
|
|
return response;
|
|
}
|
|
|
|
static Value ProcessWaitPIDImpl(struct msghdr *msgh, const Dictionary::Ptr& request)
|
|
{
|
|
pid_t pid = request->Get("pid");
|
|
|
|
int status;
|
|
int rc = waitpid(pid, &status, 0);
|
|
|
|
Dictionary::Ptr response = new Dictionary({
|
|
{ "status", status },
|
|
{ "rc", rc }
|
|
});
|
|
|
|
return response;
|
|
}
|
|
|
|
static void ProcessHandler()
|
|
{
|
|
sigset_t mask;
|
|
sigfillset(&mask);
|
|
sigprocmask(SIG_SETMASK, &mask, nullptr);
|
|
|
|
Utility::CloseAllFDs({0, 1, 2, l_ProcessControlFD});
|
|
|
|
for (;;) {
|
|
size_t length;
|
|
|
|
struct msghdr msg;
|
|
memset(&msg, 0, sizeof(msg));
|
|
|
|
struct iovec io;
|
|
io.iov_base = &length;
|
|
io.iov_len = sizeof(length);
|
|
|
|
msg.msg_iov = &io;
|
|
msg.msg_iovlen = 1;
|
|
|
|
char cbuf[4096];
|
|
msg.msg_control = cbuf;
|
|
msg.msg_controllen = sizeof(cbuf);
|
|
|
|
int rc = recvmsg(l_ProcessControlFD, &msg, 0);
|
|
|
|
if (rc <= 0) {
|
|
if (rc < 0 && (errno == EINTR || errno == EAGAIN))
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
|
|
auto *mbuf = new char[length];
|
|
|
|
size_t count = 0;
|
|
while (count < length) {
|
|
rc = recv(l_ProcessControlFD, mbuf + count, length - count, 0);
|
|
|
|
if (rc <= 0) {
|
|
if (rc < 0 && (errno == EINTR || errno == EAGAIN))
|
|
continue;
|
|
|
|
delete [] mbuf;
|
|
|
|
_exit(0);
|
|
}
|
|
|
|
count += rc;
|
|
|
|
if (rc == 0)
|
|
break;
|
|
}
|
|
|
|
String jrequest = String(mbuf, mbuf + count);
|
|
|
|
delete [] mbuf;
|
|
|
|
Dictionary::Ptr request = JsonDecode(jrequest);
|
|
|
|
String command = request->Get("command");
|
|
|
|
Value response;
|
|
|
|
if (command == "spawn")
|
|
response = ProcessSpawnImpl(&msg, request);
|
|
else if (command == "waitpid")
|
|
response = ProcessWaitPIDImpl(&msg, request);
|
|
else if (command == "kill")
|
|
response = ProcessKillImpl(&msg, request);
|
|
else
|
|
response = Empty;
|
|
|
|
String jresponse = JsonEncode(response);
|
|
|
|
if (send(l_ProcessControlFD, jresponse.CStr(), jresponse.GetLength(), 0) < 0) {
|
|
BOOST_THROW_EXCEPTION(posix_error()
|
|
<< boost::errinfo_api_function("send")
|
|
<< boost::errinfo_errno(errno));
|
|
}
|
|
}
|
|
|
|
_exit(0);
|
|
}
|
|
|
|
static void StartSpawnProcessHelper()
|
|
{
|
|
if (l_ProcessControlFD != -1) {
|
|
(void)close(l_ProcessControlFD);
|
|
|
|
int status;
|
|
(void)waitpid(l_ProcessControlPID, &status, 0);
|
|
}
|
|
|
|
int controlFDs[2];
|
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, controlFDs) < 0) {
|
|
BOOST_THROW_EXCEPTION(posix_error()
|
|
<< boost::errinfo_api_function("socketpair")
|
|
<< boost::errinfo_errno(errno));
|
|
}
|
|
|
|
pid_t pid = fork();
|
|
|
|
if (pid < 0) {
|
|
BOOST_THROW_EXCEPTION(posix_error()
|
|
<< boost::errinfo_api_function("fork")
|
|
<< boost::errinfo_errno(errno));
|
|
}
|
|
|
|
if (pid == 0) {
|
|
(void)close(controlFDs[1]);
|
|
|
|
l_ProcessControlFD = controlFDs[0];
|
|
|
|
ProcessHandler();
|
|
|
|
_exit(1);
|
|
}
|
|
|
|
(void)close(controlFDs[0]);
|
|
|
|
l_ProcessControlFD = controlFDs[1];
|
|
l_ProcessControlPID = pid;
|
|
}
|
|
|
|
static pid_t ProcessSpawn(const std::vector<String>& arguments, const Dictionary::Ptr& extraEnvironment,
|
|
const Array::Ptr& safeToTruncate, bool adjustPriority, int fds[3])
|
|
{
|
|
Dictionary::Ptr request = new Dictionary({
|
|
{ "command", "spawn" },
|
|
{ "arguments", Array::FromVector(arguments) },
|
|
{ "extraEnvironment", extraEnvironment },
|
|
{ "safeToTruncate", safeToTruncate },
|
|
{ "adjustPriority", adjustPriority }
|
|
});
|
|
|
|
String jrequest = JsonEncode(request);
|
|
size_t length = jrequest.GetLength();
|
|
|
|
std::unique_lock<std::mutex> lock(l_ProcessControlMutex);
|
|
|
|
struct msghdr msg;
|
|
memset(&msg, 0, sizeof(msg));
|
|
|
|
struct iovec io;
|
|
io.iov_base = &length;
|
|
io.iov_len = sizeof(length);
|
|
|
|
msg.msg_iov = &io;
|
|
msg.msg_iovlen = 1;
|
|
|
|
char cbuf[CMSG_SPACE(sizeof(int) * 3)];
|
|
msg.msg_control = cbuf;
|
|
msg.msg_controllen = sizeof(cbuf);
|
|
|
|
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_RIGHTS;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(int) * 3);
|
|
|
|
memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * 3);
|
|
|
|
msg.msg_controllen = cmsg->cmsg_len;
|
|
|
|
do {
|
|
while (sendmsg(l_ProcessControlFD, &msg, 0) < 0) {
|
|
StartSpawnProcessHelper();
|
|
}
|
|
} while (send(l_ProcessControlFD, jrequest.CStr(), jrequest.GetLength(), 0) < 0);
|
|
|
|
char buf[4096];
|
|
|
|
ssize_t rc = recv(l_ProcessControlFD, buf, sizeof(buf), 0);
|
|
|
|
if (rc <= 0)
|
|
return -1;
|
|
|
|
String jresponse = String(buf, buf + rc);
|
|
|
|
Dictionary::Ptr response = JsonDecode(jresponse);
|
|
|
|
if (response->Get("rc") == -1)
|
|
errno = response->Get("errno");
|
|
|
|
return response->Get("rc");
|
|
}
|
|
|
|
static int ProcessKill(pid_t pid, int signum)
|
|
{
|
|
Dictionary::Ptr request = new Dictionary({
|
|
{ "command", "kill" },
|
|
{ "pid", pid },
|
|
{ "signum", signum }
|
|
});
|
|
|
|
String jrequest = JsonEncode(request);
|
|
size_t length = jrequest.GetLength();
|
|
|
|
std::unique_lock<std::mutex> lock(l_ProcessControlMutex);
|
|
|
|
do {
|
|
while (send(l_ProcessControlFD, &length, sizeof(length), 0) < 0) {
|
|
StartSpawnProcessHelper();
|
|
}
|
|
} while (send(l_ProcessControlFD, jrequest.CStr(), jrequest.GetLength(), 0) < 0);
|
|
|
|
char buf[4096];
|
|
|
|
ssize_t rc = recv(l_ProcessControlFD, buf, sizeof(buf), 0);
|
|
|
|
if (rc <= 0)
|
|
return -1;
|
|
|
|
String jresponse = String(buf, buf + rc);
|
|
|
|
Dictionary::Ptr response = JsonDecode(jresponse);
|
|
return response->Get("errno");
|
|
}
|
|
|
|
static int ProcessWaitPID(pid_t pid, int *status)
|
|
{
|
|
Dictionary::Ptr request = new Dictionary({
|
|
{ "command", "waitpid" },
|
|
{ "pid", pid }
|
|
});
|
|
|
|
String jrequest = JsonEncode(request);
|
|
size_t length = jrequest.GetLength();
|
|
|
|
std::unique_lock<std::mutex> lock(l_ProcessControlMutex);
|
|
|
|
do {
|
|
while (send(l_ProcessControlFD, &length, sizeof(length), 0) < 0) {
|
|
StartSpawnProcessHelper();
|
|
}
|
|
} while (send(l_ProcessControlFD, jrequest.CStr(), jrequest.GetLength(), 0) < 0);
|
|
|
|
char buf[4096];
|
|
|
|
ssize_t rc = recv(l_ProcessControlFD, buf, sizeof(buf), 0);
|
|
|
|
if (rc <= 0)
|
|
return -1;
|
|
|
|
String jresponse = String(buf, buf + rc);
|
|
|
|
Dictionary::Ptr response = JsonDecode(jresponse);
|
|
*status = response->Get("status");
|
|
return response->Get("rc");
|
|
}
|
|
|
|
void Process::InitializeSpawnHelper()
|
|
{
|
|
if (l_ProcessControlFD == -1)
|
|
StartSpawnProcessHelper();
|
|
}
|
|
#endif /* _WIN32 */
|
|
|
|
static void InitializeProcess()
|
|
{
|
|
#ifdef _WIN32
|
|
for (auto& event : l_Events) {
|
|
event = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
|
}
|
|
#else /* _WIN32 */
|
|
for (auto& eventFD : l_EventFDs) {
|
|
# ifdef HAVE_PIPE2
|
|
if (pipe2(eventFD, O_CLOEXEC) < 0) {
|
|
if (errno == ENOSYS) {
|
|
# endif /* HAVE_PIPE2 */
|
|
if (pipe(eventFD) < 0) {
|
|
BOOST_THROW_EXCEPTION(posix_error()
|
|
<< boost::errinfo_api_function("pipe")
|
|
<< boost::errinfo_errno(errno));
|
|
}
|
|
|
|
Utility::SetCloExec(eventFD[0]);
|
|
Utility::SetCloExec(eventFD[1]);
|
|
# ifdef HAVE_PIPE2
|
|
} else {
|
|
BOOST_THROW_EXCEPTION(posix_error()
|
|
<< boost::errinfo_api_function("pipe2")
|
|
<< boost::errinfo_errno(errno));
|
|
}
|
|
}
|
|
# endif /* HAVE_PIPE2 */
|
|
}
|
|
#endif /* _WIN32 */
|
|
}
|
|
|
|
INITIALIZE_ONCE(InitializeProcess);
|
|
|
|
void Process::ThreadInitialize()
|
|
{
|
|
/* Note to self: Make sure this runs _after_ we've daemonized. */
|
|
for (int tid = 0; tid < IOTHREADS; tid++) {
|
|
std::thread t([tid]() { IOThreadProc(tid); });
|
|
t.detach();
|
|
}
|
|
}
|
|
|
|
Process::Arguments Process::PrepareCommand(const Value& command)
|
|
{
|
|
#ifdef _WIN32
|
|
String args;
|
|
#else /* _WIN32 */
|
|
std::vector<String> args;
|
|
#endif /* _WIN32 */
|
|
|
|
if (command.IsObjectType<Array>()) {
|
|
Array::Ptr arguments = command;
|
|
|
|
ObjectLock olock(arguments);
|
|
for (const Value& argument : arguments) {
|
|
#ifdef _WIN32
|
|
if (args != "")
|
|
args += " ";
|
|
|
|
args += Utility::EscapeCreateProcessArg(argument);
|
|
#else /* _WIN32 */
|
|
args.push_back(argument);
|
|
#endif /* _WIN32 */
|
|
}
|
|
|
|
return args;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
return command;
|
|
#else /* _WIN32 */
|
|
return { "sh", "-c", command };
|
|
#endif
|
|
}
|
|
|
|
void Process::SetTimeout(double timeout)
|
|
{
|
|
m_Timeout = timeout;
|
|
}
|
|
|
|
double Process::GetTimeout() const
|
|
{
|
|
return m_Timeout;
|
|
}
|
|
|
|
void Process::SetAdjustPriority(bool adjust)
|
|
{
|
|
m_AdjustPriority = adjust;
|
|
}
|
|
|
|
bool Process::GetAdjustPriority() const
|
|
{
|
|
return m_AdjustPriority;
|
|
}
|
|
|
|
void Process::IOThreadProc(int tid)
|
|
{
|
|
#ifdef _WIN32
|
|
HANDLE *handles = nullptr;
|
|
HANDLE *fhandles = nullptr;
|
|
#else /* _WIN32 */
|
|
pollfd *pfds = nullptr;
|
|
#endif /* _WIN32 */
|
|
int count = 0;
|
|
double now;
|
|
|
|
Utility::SetThreadName("ProcessIO");
|
|
|
|
for (;;) {
|
|
double timeout = -1;
|
|
|
|
now = Utility::GetTime();
|
|
|
|
{
|
|
std::unique_lock<std::mutex> lock(l_ProcessMutex[tid]);
|
|
|
|
count = 1 + l_Processes[tid].size();
|
|
#ifdef _WIN32
|
|
handles = reinterpret_cast<HANDLE *>(realloc(handles, sizeof(HANDLE) * count));
|
|
fhandles = reinterpret_cast<HANDLE *>(realloc(fhandles, sizeof(HANDLE) * count));
|
|
|
|
fhandles[0] = l_Events[tid];
|
|
|
|
#else /* _WIN32 */
|
|
pfds = reinterpret_cast<pollfd *>(realloc(pfds, sizeof(pollfd) * count));
|
|
|
|
pfds[0].fd = l_EventFDs[tid][0];
|
|
pfds[0].events = POLLIN;
|
|
pfds[0].revents = 0;
|
|
#endif /* _WIN32 */
|
|
|
|
int i = 1;
|
|
typedef std::pair<ProcessHandle, Process::Ptr> kv_pair;
|
|
for (const kv_pair& kv : l_Processes[tid]) {
|
|
const Process::Ptr& process = kv.second;
|
|
#ifdef _WIN32
|
|
handles[i] = kv.first;
|
|
|
|
if (!process->m_ReadPending) {
|
|
process->m_ReadPending = true;
|
|
|
|
BOOL res = ReadFile(process->m_FD, process->m_ReadBuffer, sizeof(process->m_ReadBuffer), 0, &process->m_Overlapped);
|
|
if (res || GetLastError() != ERROR_IO_PENDING) {
|
|
process->m_ReadFailed = !res;
|
|
SetEvent(process->m_Overlapped.hEvent);
|
|
}
|
|
}
|
|
|
|
fhandles[i] = process->m_Overlapped.hEvent;
|
|
#else /* _WIN32 */
|
|
pfds[i].fd = process->m_FD;
|
|
pfds[i].events = POLLIN;
|
|
pfds[i].revents = 0;
|
|
#endif /* _WIN32 */
|
|
|
|
if (process->m_Timeout != 0) {
|
|
double delta = process->GetNextTimeout() - (now - process->m_Result.ExecutionStart);
|
|
|
|
if (timeout == -1 || delta < timeout)
|
|
timeout = delta;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if (timeout < 0.01)
|
|
timeout = 0.5;
|
|
|
|
timeout *= 1000;
|
|
|
|
#ifdef _WIN32
|
|
DWORD rc = WaitForMultipleObjects(count, fhandles, FALSE, timeout == -1 ? INFINITE : static_cast<DWORD>(timeout));
|
|
#else /* _WIN32 */
|
|
int rc = poll(pfds, count, timeout);
|
|
|
|
if (rc < 0)
|
|
continue;
|
|
#endif /* _WIN32 */
|
|
|
|
now = Utility::GetTime();
|
|
|
|
{
|
|
std::unique_lock<std::mutex> lock(l_ProcessMutex[tid]);
|
|
|
|
#ifdef _WIN32
|
|
if (rc == WAIT_OBJECT_0)
|
|
ResetEvent(l_Events[tid]);
|
|
#else /* _WIN32 */
|
|
if (pfds[0].revents & (POLLIN | POLLHUP | POLLERR)) {
|
|
char buffer[512];
|
|
if (read(l_EventFDs[tid][0], buffer, sizeof(buffer)) < 0)
|
|
Log(LogCritical, "base", "Read from event FD failed.");
|
|
}
|
|
#endif /* _WIN32 */
|
|
|
|
for (int i = 1; i < count; i++) {
|
|
#ifdef _WIN32
|
|
auto it = l_Processes[tid].find(handles[i]);
|
|
#else /* _WIN32 */
|
|
auto it2 = l_FDs[tid].find(pfds[i].fd);
|
|
|
|
if (it2 == l_FDs[tid].end())
|
|
continue; /* This should never happen. */
|
|
|
|
auto it = l_Processes[tid].find(it2->second);
|
|
#endif /* _WIN32 */
|
|
|
|
if (it == l_Processes[tid].end())
|
|
continue; /* This should never happen. */
|
|
|
|
bool is_timeout = false;
|
|
|
|
if (it->second->m_Timeout != 0) {
|
|
double timeout = it->second->m_Result.ExecutionStart + it->second->GetNextTimeout();
|
|
|
|
if (timeout < now)
|
|
is_timeout = true;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
if (rc == WAIT_OBJECT_0 + i || is_timeout) {
|
|
#else /* _WIN32 */
|
|
if (pfds[i].revents & (POLLIN | POLLHUP | POLLERR) || is_timeout) {
|
|
#endif /* _WIN32 */
|
|
if (!it->second->DoEvents()) {
|
|
#ifdef _WIN32
|
|
CloseHandle(it->first);
|
|
CloseHandle(it->second->m_FD);
|
|
#else /* _WIN32 */
|
|
l_FDs[tid].erase(it->second->m_FD);
|
|
(void)close(it->second->m_FD);
|
|
#endif /* _WIN32 */
|
|
l_Processes[tid].erase(it);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
String Process::PrettyPrintArguments(const Process::Arguments& arguments)
|
|
{
|
|
#ifdef _WIN32
|
|
return "'" + arguments + "'";
|
|
#else /* _WIN32 */
|
|
return "'" + boost::algorithm::join(arguments, "' '") + "'";
|
|
#endif /* _WIN32 */
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
static BOOL CreatePipeOverlapped(HANDLE *outReadPipe, HANDLE *outWritePipe,
|
|
SECURITY_ATTRIBUTES *securityAttributes, DWORD size, DWORD readMode, DWORD writeMode)
|
|
{
|
|
static LONG pipeIndex = 0;
|
|
|
|
if (size == 0)
|
|
size = 8192;
|
|
|
|
LONG currentIndex = InterlockedIncrement(&pipeIndex);
|
|
|
|
char pipeName[128];
|
|
sprintf(pipeName, "\\\\.\\Pipe\\OverlappedPipe.%d.%d", (int)GetCurrentProcessId(), (int)currentIndex);
|
|
|
|
*outReadPipe = CreateNamedPipe(pipeName, PIPE_ACCESS_INBOUND | readMode,
|
|
PIPE_TYPE_BYTE | PIPE_WAIT, 1, size, size, 60 * 1000, securityAttributes);
|
|
|
|
if (*outReadPipe == INVALID_HANDLE_VALUE)
|
|
return FALSE;
|
|
|
|
*outWritePipe = CreateFile(pipeName, GENERIC_WRITE, 0, securityAttributes, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | writeMode, nullptr);
|
|
|
|
if (*outWritePipe == INVALID_HANDLE_VALUE) {
|
|
DWORD error = GetLastError();
|
|
CloseHandle(*outReadPipe);
|
|
SetLastError(error);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
#endif /* _WIN32 */
|
|
|
|
void Process::Run(const std::function<void(const ProcessResult&)>& callback)
|
|
{
|
|
#ifndef _WIN32
|
|
boost::call_once(l_SpawnHelperOnceFlag, &Process::InitializeSpawnHelper);
|
|
#endif /* _WIN32 */
|
|
boost::call_once(l_ProcessOnceFlag, &Process::ThreadInitialize);
|
|
|
|
m_Result.ExecutionStart = Utility::GetTime();
|
|
|
|
#ifdef _WIN32
|
|
SECURITY_ATTRIBUTES sa = {};
|
|
sa.nLength = sizeof(sa);
|
|
sa.bInheritHandle = TRUE;
|
|
|
|
HANDLE outReadPipe, outWritePipe;
|
|
if (!CreatePipeOverlapped(&outReadPipe, &outWritePipe, &sa, 0, FILE_FLAG_OVERLAPPED, 0))
|
|
BOOST_THROW_EXCEPTION(win32_error()
|
|
<< boost::errinfo_api_function("CreatePipe")
|
|
<< errinfo_win32_error(GetLastError()));
|
|
|
|
if (!SetHandleInformation(outReadPipe, HANDLE_FLAG_INHERIT, 0))
|
|
BOOST_THROW_EXCEPTION(win32_error()
|
|
<< boost::errinfo_api_function("SetHandleInformation")
|
|
<< errinfo_win32_error(GetLastError()));
|
|
|
|
HANDLE outWritePipeDup;
|
|
if (!DuplicateHandle(GetCurrentProcess(), outWritePipe, GetCurrentProcess(),
|
|
&outWritePipeDup, 0, TRUE, DUPLICATE_SAME_ACCESS))
|
|
BOOST_THROW_EXCEPTION(win32_error()
|
|
<< boost::errinfo_api_function("DuplicateHandle")
|
|
<< errinfo_win32_error(GetLastError()));
|
|
|
|
/* LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
|
|
SIZE_T cbSize;
|
|
|
|
if (!InitializeProcThreadAttributeList(nullptr, 1, 0, &cbSize) && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
|
BOOST_THROW_EXCEPTION(win32_error()
|
|
<< boost::errinfo_api_function("InitializeProcThreadAttributeList")
|
|
<< errinfo_win32_error(GetLastError()));
|
|
|
|
lpAttributeList = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(new char[cbSize]);
|
|
|
|
if (!InitializeProcThreadAttributeList(lpAttributeList, 1, 0, &cbSize))
|
|
BOOST_THROW_EXCEPTION(win32_error()
|
|
<< boost::errinfo_api_function("InitializeProcThreadAttributeList")
|
|
<< errinfo_win32_error(GetLastError()));
|
|
|
|
HANDLE rgHandles[3];
|
|
rgHandles[0] = outWritePipe;
|
|
rgHandles[1] = outWritePipeDup;
|
|
rgHandles[2] = GetStdHandle(STD_INPUT_HANDLE);
|
|
|
|
if (!UpdateProcThreadAttribute(lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
|
|
rgHandles, sizeof(rgHandles), nullptr, nullptr))
|
|
BOOST_THROW_EXCEPTION(win32_error()
|
|
<< boost::errinfo_api_function("UpdateProcThreadAttribute")
|
|
<< errinfo_win32_error(GetLastError()));
|
|
*/
|
|
|
|
STARTUPINFOEX si = {};
|
|
si.StartupInfo.cb = sizeof(si);
|
|
si.StartupInfo.hStdError = outWritePipe;
|
|
si.StartupInfo.hStdOutput = outWritePipeDup;
|
|
si.StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
|
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
|
|
// si.lpAttributeList = lpAttributeList;
|
|
|
|
PROCESS_INFORMATION pi;
|
|
|
|
char *args = new char[m_Arguments.GetLength() + 1];
|
|
strncpy(args, m_Arguments.CStr(), m_Arguments.GetLength() + 1);
|
|
args[m_Arguments.GetLength()] = '\0';
|
|
|
|
LPCH pEnvironment = GetEnvironmentStrings();
|
|
size_t ioffset = 0, offset = 0;
|
|
|
|
char *envp = nullptr;
|
|
|
|
for (;;) {
|
|
size_t len = strlen(pEnvironment + ioffset);
|
|
|
|
if (len == 0)
|
|
break;
|
|
|
|
char *eqp = strchr(pEnvironment + ioffset, '=');
|
|
if (eqp && m_ExtraEnvironment && m_ExtraEnvironment->Contains(String(pEnvironment + ioffset, eqp))) {
|
|
ioffset += len + 1;
|
|
continue;
|
|
}
|
|
|
|
envp = static_cast<char *>(realloc(envp, offset + len + 1));
|
|
|
|
if (!envp)
|
|
BOOST_THROW_EXCEPTION(std::bad_alloc());
|
|
|
|
strcpy(envp + offset, pEnvironment + ioffset);
|
|
offset += len + 1;
|
|
ioffset += len + 1;
|
|
}
|
|
|
|
FreeEnvironmentStrings(pEnvironment);
|
|
|
|
if (m_ExtraEnvironment) {
|
|
ObjectLock olock(m_ExtraEnvironment);
|
|
|
|
for (const Dictionary::Pair& kv : m_ExtraEnvironment) {
|
|
String skv = kv.first + "=" + Convert::ToString(kv.second);
|
|
|
|
envp = static_cast<char *>(realloc(envp, offset + skv.GetLength() + 1));
|
|
|
|
if (!envp)
|
|
BOOST_THROW_EXCEPTION(std::bad_alloc());
|
|
|
|
strcpy(envp + offset, skv.CStr());
|
|
offset += skv.GetLength() + 1;
|
|
}
|
|
}
|
|
|
|
envp = static_cast<char *>(realloc(envp, offset + 1));
|
|
|
|
if (!envp)
|
|
BOOST_THROW_EXCEPTION(std::bad_alloc());
|
|
|
|
envp[offset] = '\0';
|
|
|
|
if (!CreateProcess(nullptr, args, nullptr, nullptr, TRUE,
|
|
0 /*EXTENDED_STARTUPINFO_PRESENT*/, envp, nullptr, &si.StartupInfo, &pi)) {
|
|
DWORD error = GetLastError();
|
|
CloseHandle(outWritePipe);
|
|
CloseHandle(outWritePipeDup);
|
|
free(envp);
|
|
/* DeleteProcThreadAttributeList(lpAttributeList);
|
|
delete [] reinterpret_cast<char *>(lpAttributeList); */
|
|
|
|
m_Result.PID = 0;
|
|
m_Result.ExecutionEnd = Utility::GetTime();
|
|
m_Result.ExitStatus = 127;
|
|
m_Result.Output = "Command " + String(args) + " failed to execute: " + Utility::FormatErrorNumber(error);
|
|
|
|
delete [] args;
|
|
|
|
if (callback) {
|
|
/*
|
|
* Explicitly use Process::Ptr to keep the reference counted while the
|
|
* callback is active and making it crash safe
|
|
*/
|
|
Process::Ptr process(this);
|
|
Utility::QueueAsyncCallback([this, process, callback]() { callback(m_Result); });
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
delete [] args;
|
|
free(envp);
|
|
/* DeleteProcThreadAttributeList(lpAttributeList);
|
|
delete [] reinterpret_cast<char *>(lpAttributeList); */
|
|
|
|
CloseHandle(outWritePipe);
|
|
CloseHandle(outWritePipeDup);
|
|
CloseHandle(pi.hThread);
|
|
|
|
m_Process = pi.hProcess;
|
|
m_FD = outReadPipe;
|
|
m_PID = pi.dwProcessId;
|
|
|
|
Log(LogNotice, "Process")
|
|
<< "Running command " << PrettyPrintArguments(m_Arguments) << ": PID " << m_PID;
|
|
|
|
#else /* _WIN32 */
|
|
int outfds[2];
|
|
|
|
#ifdef HAVE_PIPE2
|
|
if (pipe2(outfds, O_CLOEXEC) < 0) {
|
|
if (errno == ENOSYS) {
|
|
#endif /* HAVE_PIPE2 */
|
|
if (pipe(outfds) < 0) {
|
|
BOOST_THROW_EXCEPTION(posix_error()
|
|
<< boost::errinfo_api_function("pipe")
|
|
<< boost::errinfo_errno(errno));
|
|
}
|
|
|
|
Utility::SetCloExec(outfds[0]);
|
|
Utility::SetCloExec(outfds[1]);
|
|
#ifdef HAVE_PIPE2
|
|
} else {
|
|
BOOST_THROW_EXCEPTION(posix_error()
|
|
<< boost::errinfo_api_function("pipe2")
|
|
<< boost::errinfo_errno(errno));
|
|
}
|
|
}
|
|
#endif /* HAVE_PIPE2 */
|
|
|
|
int fds[3];
|
|
fds[0] = STDIN_FILENO;
|
|
fds[1] = outfds[1];
|
|
fds[2] = outfds[1];
|
|
|
|
m_Process = ProcessSpawn(m_Arguments, m_ExtraEnvironment, m_SafeToTruncate, m_AdjustPriority, fds);
|
|
m_PID = m_Process;
|
|
|
|
if (m_PID == -1) {
|
|
m_OutputStream << "Fork failed with error code " << errno << " (" << Utility::FormatErrorNumber(errno) << ")";
|
|
Log(LogCritical, "Process", m_OutputStream.str());
|
|
}
|
|
|
|
Log(LogNotice, "Process")
|
|
<< "Running command " << PrettyPrintArguments(m_Arguments) << ": PID " << m_PID;
|
|
|
|
(void)close(outfds[1]);
|
|
|
|
Utility::SetNonBlocking(outfds[0]);
|
|
|
|
m_FD = outfds[0];
|
|
#endif /* _WIN32 */
|
|
|
|
m_Callback = callback;
|
|
|
|
int tid = GetTID();
|
|
|
|
{
|
|
std::unique_lock<std::mutex> lock(l_ProcessMutex[tid]);
|
|
l_Processes[tid][m_Process] = this;
|
|
#ifndef _WIN32
|
|
l_FDs[tid][m_FD] = m_Process;
|
|
#endif /* _WIN32 */
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
SetEvent(l_Events[tid]);
|
|
#else /* _WIN32 */
|
|
if (write(l_EventFDs[tid][1], "T", 1) < 0 && errno != EINTR && errno != EAGAIN)
|
|
Log(LogCritical, "base", "Write to event FD failed.");
|
|
#endif /* _WIN32 */
|
|
}
|
|
|
|
const ProcessResult& Process::WaitForResult() {
|
|
std::unique_lock<std::mutex> lock(m_ResultMutex);
|
|
m_ResultCondition.wait(lock, [this]{ return m_ResultAvailable; });
|
|
return m_Result;
|
|
}
|
|
|
|
bool Process::DoEvents()
|
|
{
|
|
bool is_timeout = false;
|
|
#ifndef _WIN32
|
|
bool could_not_kill = false;
|
|
#endif /* _WIN32 */
|
|
|
|
if (m_Timeout != 0) {
|
|
auto now (Utility::GetTime());
|
|
|
|
#ifndef _WIN32
|
|
{
|
|
auto timeout (GetNextTimeout());
|
|
auto deadline (m_Result.ExecutionStart + timeout);
|
|
|
|
if (deadline < now && !m_SentSigterm) {
|
|
Log(LogWarning, "Process")
|
|
<< "Terminating process " << m_PID << " (" << PrettyPrintArguments(m_Arguments)
|
|
<< ") after timeout of " << timeout << " seconds";
|
|
|
|
m_OutputStream << "<Timeout exceeded.>";
|
|
|
|
int error = ProcessKill(m_Process, SIGTERM);
|
|
if (error) {
|
|
Log(LogWarning, "Process")
|
|
<< "Couldn't terminate the process " << m_PID << " (" << PrettyPrintArguments(m_Arguments)
|
|
<< "): [errno " << error << "] " << strerror(error);
|
|
}
|
|
|
|
m_SentSigterm = true;
|
|
}
|
|
}
|
|
#endif /* _WIN32 */
|
|
|
|
auto timeout (GetNextTimeout());
|
|
auto deadline (m_Result.ExecutionStart + timeout);
|
|
|
|
if (deadline < now) {
|
|
Log(LogWarning, "Process")
|
|
<< "Killing process group " << m_PID << " (" << PrettyPrintArguments(m_Arguments)
|
|
<< ") after timeout of " << timeout << " seconds";
|
|
|
|
#ifdef _WIN32
|
|
m_OutputStream << "<Timeout exceeded.>";
|
|
TerminateProcess(m_Process, 3);
|
|
#else /* _WIN32 */
|
|
int error = ProcessKill(-m_Process, SIGKILL);
|
|
if (error) {
|
|
Log(LogWarning, "Process")
|
|
<< "Couldn't kill the process group " << m_PID << " (" << PrettyPrintArguments(m_Arguments)
|
|
<< "): [errno " << error << "] " << strerror(error);
|
|
could_not_kill = true;
|
|
}
|
|
#endif /* _WIN32 */
|
|
|
|
is_timeout = true;
|
|
}
|
|
}
|
|
|
|
if (!is_timeout) {
|
|
#ifdef _WIN32
|
|
m_ReadPending = false;
|
|
|
|
DWORD rc;
|
|
if (!m_ReadFailed && GetOverlappedResult(m_FD, &m_Overlapped, &rc, TRUE) && rc > 0) {
|
|
m_OutputStream.write(m_ReadBuffer, rc);
|
|
return true;
|
|
}
|
|
#else /* _WIN32 */
|
|
char buffer[512];
|
|
for (;;) {
|
|
int rc = read(m_FD, buffer, sizeof(buffer));
|
|
|
|
if (rc < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
|
|
return true;
|
|
|
|
if (rc > 0) {
|
|
m_OutputStream.write(buffer, rc);
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
#endif /* _WIN32 */
|
|
}
|
|
|
|
String output = m_OutputStream.str();
|
|
|
|
#ifdef _WIN32
|
|
WaitForSingleObject(m_Process, INFINITE);
|
|
|
|
DWORD exitcode;
|
|
GetExitCodeProcess(m_Process, &exitcode);
|
|
|
|
Log(LogNotice, "Process")
|
|
<< "PID " << m_PID << " (" << PrettyPrintArguments(m_Arguments) << ") terminated with exit code " << exitcode;
|
|
#else /* _WIN32 */
|
|
int status, exitcode;
|
|
if (could_not_kill || m_PID == -1) {
|
|
exitcode = 128;
|
|
} else if (ProcessWaitPID(m_Process, &status) != m_Process) {
|
|
exitcode = 128;
|
|
|
|
Log(LogWarning, "Process")
|
|
<< "PID " << m_PID << " (" << PrettyPrintArguments(m_Arguments) << ") died mysteriously: waitpid failed";
|
|
} else if (WIFEXITED(status)) {
|
|
exitcode = WEXITSTATUS(status);
|
|
|
|
Log msg(LogNotice, "Process");
|
|
msg << "PID " << m_PID << " (" << PrettyPrintArguments(m_Arguments)
|
|
<< ") terminated with exit code " << exitcode;
|
|
|
|
if (m_SentSigterm) {
|
|
exitcode = 128;
|
|
msg << " after sending SIGTERM";
|
|
}
|
|
} else if (WIFSIGNALED(status)) {
|
|
int signum = WTERMSIG(status);
|
|
const char *zsigname = strsignal(signum);
|
|
|
|
String signame = Convert::ToString(signum);
|
|
|
|
if (zsigname) {
|
|
signame += " (";
|
|
signame += zsigname;
|
|
signame += ")";
|
|
}
|
|
|
|
Log(LogWarning, "Process")
|
|
<< "PID " << m_PID << " was terminated by signal " << signame;
|
|
|
|
std::ostringstream outputbuf;
|
|
outputbuf << "<Terminated by signal " << signame << ".>";
|
|
output = output + outputbuf.str();
|
|
exitcode = 128;
|
|
} else {
|
|
exitcode = 128;
|
|
}
|
|
#endif /* _WIN32 */
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_ResultMutex);
|
|
m_Result.PID = m_PID;
|
|
m_Result.ExecutionEnd = Utility::GetTime();
|
|
m_Result.ExitStatus = exitcode;
|
|
m_Result.Output = output;
|
|
m_ResultAvailable = true;
|
|
}
|
|
m_ResultCondition.notify_all();
|
|
|
|
if (m_Callback) {
|
|
/*
|
|
* Explicitly use Process::Ptr to keep the reference counted while the
|
|
* callback is active and making it crash safe
|
|
*/
|
|
Process::Ptr process(this);
|
|
Utility::QueueAsyncCallback([this, process]() { m_Callback(m_Result); });
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
pid_t Process::GetPID() const
|
|
{
|
|
return m_PID;
|
|
}
|
|
|
|
|
|
int Process::GetTID() const
|
|
{
|
|
return (reinterpret_cast<uintptr_t>(this) / sizeof(void *)) % IOTHREADS;
|
|
}
|
|
|
|
double Process::GetNextTimeout() const
|
|
{
|
|
#ifdef _WIN32
|
|
return m_Timeout;
|
|
#else /* _WIN32 */
|
|
return m_SentSigterm ? m_Timeout * 1.1 : m_Timeout;
|
|
#endif /* _WIN32 */
|
|
}
|