/****************************************************************************** * Icinga 2 * * Copyright (C) 2012 Icinga Development Team (http://www.icinga.org/) * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software Foundation * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ******************************************************************************/ #include "i2-base.h" #ifndef _MSC_VER # include "popen_noshell.h" #endif /* _MSC_VER */ using namespace icinga; bool Process::m_ThreadCreated = false; boost::mutex Process::m_Mutex; deque Process::m_Tasks; condition_variable Process::m_TasksCV; Process::Process(const String& command) : AsyncTask(), m_Command(command), m_UsePopen(false) { assert(Application::IsMainThread()); if (!m_ThreadCreated) { thread t(&Process::WorkerThreadProc); t.detach(); m_ThreadCreated = true; } } void Process::Run(void) { boost::mutex::scoped_lock lock(m_Mutex); m_Tasks.push_back(GetSelf()); m_TasksCV.notify_all(); } void Process::WorkerThreadProc(void) { boost::mutex::scoped_lock lock(m_Mutex); map tasks; for (;;) { while (m_Tasks.empty() || tasks.size() >= MaxTasksPerThread) { lock.unlock(); map::iterator it, prev; #ifndef _MSC_VER fd_set readfds; int nfds = 0; FD_ZERO(&readfds); int fd; BOOST_FOREACH(tie(fd, tuples::ignore), tasks) { if (fd > nfds) nfds = fd; FD_SET(fd, &readfds); } timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; select(nfds + 1, &readfds, NULL, NULL, &tv); #else /* _MSC_VER */ Utility::Sleep(1); #endif /* _MSC_VER */ for (it = tasks.begin(); it != tasks.end(); ) { int fd = it->first; Process::Ptr task = it->second; #ifndef _MSC_VER if (!FD_ISSET(fd, &readfds)) { it++; continue; } #endif /* _MSC_VER */ if (!task->RunTask()) { prev = it; it++; tasks.erase(prev); Event::Post(boost::bind(&Process::FinishResult, task, task->m_Result)); } else { it++; } } lock.lock(); } while (!m_Tasks.empty() && tasks.size() < MaxTasksPerThread) { Process::Ptr task = m_Tasks.front(); m_Tasks.pop_front(); lock.unlock(); try { task->InitTask(); int fd = task->GetFD(); if (fd >= 0) tasks[fd] = task; } catch (...) { Event::Post(boost::bind(&Process::FinishException, task, boost::current_exception())); } lock.lock(); } } } void Process::InitTask(void) { m_Result.ExecutionStart = Utility::GetTime(); #ifdef _MSC_VER m_FP = _popen(m_Command.CStr(), "r"); #else /* _MSC_VER */ if (!m_UsePopen) { m_PCloseArg = new popen_noshell_pass_to_pclose; m_FP = popen_noshell_compat(m_Command.CStr(), "r", (popen_noshell_pass_to_pclose *)m_PCloseArg); if (m_FP == NULL) m_UsePopen = true; } if (m_UsePopen) m_FP = popen(m_Command.CStr(), "r"); #endif /* _MSC_VER */ if (m_FP == NULL) throw_exception(runtime_error("Could not create process.")); } bool Process::RunTask(void) { char buffer[512]; size_t read = fread(buffer, 1, sizeof(buffer), m_FP); if (read > 0) m_OutputStream.write(buffer, read); if (!feof(m_FP)) return true; String output = m_OutputStream.str(); int status, exitcode; #ifdef _MSC_VER status = _pclose(m_FP); #else /* _MSC_VER */ if (m_UsePopen) { status = pclose(m_FP); } else { status = pclose_noshell((popen_noshell_pass_to_pclose *)m_PCloseArg); delete (popen_noshell_pass_to_pclose *)m_PCloseArg; } #endif /* _MSC_VER */ m_Result.ExecutionEnd = Utility::GetTime(); #ifndef _MSC_VER if (WIFEXITED(status)) { exitcode = WEXITSTATUS(status); #else /* _MSC_VER */ exitcode = status; /* cmd.exe returns error code 1 (warning) when the plugin * could not be executed - change the exit status to "unknown" * when we have no plugin output. */ if (output.IsEmpty()) exitcode = 128; #endif /* _MSC_VER */ #ifndef _MSC_VER } else if (WIFSIGNALED(status)) { stringstream outputbuf; outputbuf << "Process was terminated by signal " << WTERMSIG(status); output = outputbuf.str(); exitcode = 128; } else { exitcode = 128; } #endif /* _MSC_VER */ m_Result.ExitStatus = exitcode; m_Result.Output = output; return false; } /** * Retrives the stdout file descriptor for the child process. * * @returns The stdout file descriptor. */ int Process::GetFD(void) const { return fileno(m_FP); }