/* 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 #include #include #include #ifndef _WIN32 # include # include # include # ifndef __APPLE__ extern char **environ; # else /* __APPLE__ */ # include # define environ (*_NSGetEnviron()) # endif /* __APPLE__ */ #endif /* _WIN32 */ using namespace icinga; #define IOTHREADS 4 static std::mutex l_ProcessMutex[IOTHREADS]; static std::map l_Processes[IOTHREADS]; #ifdef _WIN32 static HANDLE l_Events[IOTHREADS]; #else /* _WIN32 */ static int l_EventFDs[IOTHREADS][2]; static std::map 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) : m_Arguments(std::move(arguments)), m_ExtraEnvironment(std::move(extraEnvironment)), 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"); 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 + 1] = nullptr; extraEnvironment.reset(); 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); if (icinga2_execvpe(argv[0], argv, envp) < 0) { 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& arguments, const Dictionary::Ptr& extraEnvironment, bool adjustPriority, int fds[3]) { Dictionary::Ptr request = new Dictionary({ { "command", "spawn" }, { "arguments", Array::FromVector(arguments) }, { "extraEnvironment", extraEnvironment }, { "adjustPriority", adjustPriority } }); String jrequest = JsonEncode(request); size_t length = jrequest.GetLength(); std::unique_lock 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 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 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 args; #endif /* _WIN32 */ if (command.IsObjectType()) { 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 lock(l_ProcessMutex[tid]); count = 1 + l_Processes[tid].size(); #ifdef _WIN32 handles = reinterpret_cast(realloc(handles, sizeof(HANDLE) * count)); fhandles = reinterpret_cast(realloc(fhandles, sizeof(HANDLE) * count)); fhandles[0] = l_Events[tid]; #else /* _WIN32 */ pfds = reinterpret_cast(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 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(timeout)); #else /* _WIN32 */ int rc = poll(pfds, count, timeout); if (rc < 0) continue; #endif /* _WIN32 */ now = Utility::GetTime(); { std::unique_lock 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& 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(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(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(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(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(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(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_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 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 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 << ""; 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 << ""; 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 << ""; output = output + outputbuf.str(); exitcode = 128; } else { exitcode = 128; } #endif /* _WIN32 */ { std::lock_guard 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(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 */ }