diff --git a/lib/base/Makefile.am b/lib/base/Makefile.am index 537884c17..eb6b036ef 100644 --- a/lib/base/Makefile.am +++ b/lib/base/Makefile.am @@ -34,6 +34,8 @@ libbase_la_SOURCES = \ object.cpp \ object.h \ process.cpp \ + process-unix.cpp \ + process-windows.cpp \ process.h \ qstring.cpp \ qstring.h \ diff --git a/lib/base/base.vcxproj b/lib/base/base.vcxproj index 754254405..7f136821c 100644 --- a/lib/base/base.vcxproj +++ b/lib/base/base.vcxproj @@ -39,6 +39,8 @@ + + @@ -249,4 +251,4 @@ - \ No newline at end of file + diff --git a/lib/base/process-unix.cpp b/lib/base/process-unix.cpp new file mode 100644 index 000000000..17eb7e8f8 --- /dev/null +++ b/lib/base/process-unix.cpp @@ -0,0 +1,323 @@ +/****************************************************************************** + * 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. * + ******************************************************************************/ + +#ifndef _WIN32 +#include "i2-base.h" + +#ifndef _MSC_VER +#include +#endif /* _MSC_VER */ + +using namespace icinga; + +int Process::m_TaskFd; +extern char **environ; + +void Process::CreateWorkers(void) +{ + int fds[2]; + + if (pipe(fds) < 0) + BOOST_THROW_EXCEPTION(PosixException("pipe() failed.", errno)); + + m_TaskFd = fds[1]; + + for (int i = 0; i < thread::hardware_concurrency(); i++) { + int childTaskFd; + + childTaskFd = dup(fds[0]); + + if (childTaskFd < 0) + BOOST_THROW_EXCEPTION(PosixException("dup() failed.", errno)); + + int flags; + flags = fcntl(childTaskFd, F_GETFL, 0); + if (flags < 0) + BOOST_THROW_EXCEPTION(PosixException("fcntl failed", errno)); + + if (fcntl(childTaskFd, F_SETFL, flags | O_NONBLOCK | O_CLOEXEC) < 0) + BOOST_THROW_EXCEPTION(PosixException("fcntl failed", errno)); + + thread t(&Process::WorkerThreadProc, childTaskFd); + t.detach(); + } +} + +void Process::WorkerThreadProc(int taskFd) +{ + map tasks; + pollfd *pfds = NULL; + + for (;;) { + map::iterator it, prev; + + pfds = (pollfd *)realloc(pfds, (1 + tasks.size()) * sizeof(pollfd)); + + if (pfds == NULL) + BOOST_THROW_EXCEPTION(PosixException("realloc() failed.", errno)); + + int idx = 0; + + int fd; + BOOST_FOREACH(tie(fd, tuples::ignore), tasks) { + pfds[idx].fd = fd; + pfds[idx].events = POLLIN | POLLHUP; + idx++; + } + + if (tasks.size() < MaxTasksPerThread) { + pfds[idx].fd = taskFd; + pfds[idx].events = POLLIN; + idx++; + } + + int rc = poll(pfds, idx, -1); + + if (rc < 0 && errno != EINTR) + BOOST_THROW_EXCEPTION(PosixException("poll() failed.", errno)); + + if (rc == 0) + continue; + + + for (int i = 0; i < idx; i++) { + if ((pfds[i].revents & (POLLIN|POLLHUP)) == 0) + continue; + + while (pfds[i].fd == taskFd && tasks.size() < MaxTasksPerThread) { + Process::Ptr task; + + { + boost::mutex::scoped_lock lock(m_Mutex); + + /* Read one byte for every task we take from the pending tasks list. */ + char buffer; + int rc = read(taskFd, &buffer, sizeof(buffer)); + + if (rc < 0) { + if (errno == EAGAIN) + break; /* Someone else was faster and took our task. */ + + BOOST_THROW_EXCEPTION(PosixException("read() failed.", errno)); + } + + assert(!m_Tasks.empty()); + + task = m_Tasks.front(); + m_Tasks.pop_front(); + } + + try { + task->InitTask(); + + int fd = task->m_FD; + + if (fd >= 0) + tasks[fd] = task; + } catch (...) { + Event::Post(boost::bind(&Process::FinishException, task, boost::current_exception())); + } + } + + it = tasks.find(pfds[i].fd); + + if (it == tasks.end()) + continue; + + Process::Ptr task = it->second; + + if (!task->RunTask()) { + prev = it; + tasks.erase(prev); + + Event::Post(boost::bind(&Process::FinishResult, task, task->m_Result)); + } + } + } +} + +void Process::NotifyWorker(void) +{ + /** + * This little gem which is commonly known as the "self-pipe trick" + * takes care of waking up the select() call in the worker thread. + */ + if (write(m_TaskFd, "T", 1) < 0) + BOOST_THROW_EXCEPTION(PosixException("write() failed.", errno)); +} + +void Process::InitTask(void) +{ + m_Result.ExecutionStart = Utility::GetTime(); + + assert(m_FD == -1); + + int fds[2]; + +#ifdef HAVE_PIPE2 + if (pipe2(fds, O_NONBLOCK | O_CLOEXEC) < 0) +#else /* HAVE_PIPE2 */ + if (pipe(fds) < 0) +#endif /* HAVE_PIPE2 */ + BOOST_THROW_EXCEPTION(PosixException("pipe() failed.", errno)); + +#ifndef HAVE_PIPE2 + int flags; + flags = fcntl(fds[0], F_GETFL, 0); + if (flags < 0) + BOOST_THROW_EXCEPTION(PosixException("fcntl failed", errno)); + + if (fcntl(fds[0], F_SETFL, flags | O_NONBLOCK | O_CLOEXEC) < 0) + BOOST_THROW_EXCEPTION(PosixException("fcntl failed", errno)); + + flags = fcntl(fds[1], F_GETFL, 0); + if (flags < 0) + BOOST_THROW_EXCEPTION(PosixException("fcntl failed", errno)); + + if (fcntl(fds[1], F_SETFL, flags | O_NONBLOCK | O_CLOEXEC) < 0) + BOOST_THROW_EXCEPTION(PosixException("fcntl failed", errno)); +#endif /* HAVE_PIPE2 */ + + // build argv + char **argv = new char *[m_Arguments.size() + 1]; + + for (int i = 0; i < m_Arguments.size(); i++) + argv[i] = strdup(m_Arguments[i].CStr()); + + argv[m_Arguments.size()] = NULL; + + m_Arguments.clear(); + + // build envp + int envc = 0; + + /* count existing environment variables */ + while (environ[envc] != NULL) + envc++; + + char **envp = new char *[envc + (m_ExtraEnvironment ? m_ExtraEnvironment->GetLength() : 0) + 1]; + + for (int i = 0; i < envc; i++) + envp[i] = strdup(environ[i]); + + if (m_ExtraEnvironment) { + String key; + Value value; + int index = envc; + BOOST_FOREACH(tie(key, value), m_ExtraEnvironment) { + String kv = key + "=" + Convert::ToString(value); + envp[index] = strdup(kv.CStr()); + index++; + } + } + + envp[envc + (m_ExtraEnvironment ? m_ExtraEnvironment->GetLength() : 0)] = NULL; + + m_ExtraEnvironment.reset(); + +#ifdef HAVE_VFORK + m_Pid = vfork(); +#else /* HAVE_VFORK */ + m_Pid = fork(); +#endif /* HAVE_VFORK */ + + if (m_Pid < 0) + BOOST_THROW_EXCEPTION(PosixException("fork() failed.", errno)); + + if (m_Pid == 0) { + // child process + + if (dup2(fds[1], STDOUT_FILENO) < 0 || dup2(fds[1], STDERR_FILENO) < 0) { + perror("dup2() failed."); + _exit(128); + } + + (void) close(fds[0]); + (void) close(fds[1]); + + if (execvpe(argv[0], argv, envp) < 0) { + perror("execvpe() failed."); + _exit(128); + } + + _exit(128); + } + + // parent process + + // free arguments + for (int i = 0; argv[i] != NULL; i++) + free(argv[i]); + + delete [] argv; + + // free environment + for (int i = 0; envp[i] != NULL; i++) + free(envp[i]); + + delete [] envp; + + m_FD = fds[0]; + (void) close(fds[1]); +} + +bool Process::RunTask(void) +{ + char buffer[512]; + int rc; + + do { + rc = read(m_FD, buffer, sizeof(buffer)); + + if (rc > 0) { + m_OutputStream.write(buffer, rc); + } + } while (rc > 0); + + if (rc < 0 && errno == EAGAIN) + return true; + + String output = m_OutputStream.str(); + + int status, exitcode; + + (void) close(m_FD); + + if (waitpid(m_Pid, &status, 0) != m_Pid) + BOOST_THROW_EXCEPTION(PosixException("waitpid() failed.", errno)); + + if (WIFEXITED(status)) { + exitcode = WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + stringstream outputbuf; + outputbuf << "Process was terminated by signal " << WTERMSIG(status); + output = outputbuf.str(); + exitcode = 128; + } else { + exitcode = 128; + } + + m_Result.ExecutionEnd = Utility::GetTime(); + m_Result.ExitStatus = exitcode; + m_Result.Output = output; + + return false; +} + +#endif /* _WIN32 */ diff --git a/lib/base/process-windows.cpp b/lib/base/process-windows.cpp new file mode 100644 index 000000000..954644b71 --- /dev/null +++ b/lib/base/process-windows.cpp @@ -0,0 +1,54 @@ +/****************************************************************************** + * 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. * + ******************************************************************************/ + +#ifdef _WIN32 +#include "i2-base.h" + +#ifndef _MSC_VER +#include +#endif /* _MSC_VER */ + +using namespace icinga; + +void Process::CreateWorkers(void) +{ + // TODO: implement +} + +void Process::WorkerThreadProc(void) +{ + // TODO: implement +} + +void Process::NotifyWorker(void) +{ + // TODO: implement +} + +void Process::InitTask(void) +{ + // TODO: implement +} + +bool Process::RunTask(void) +{ + // TODO: implement +} + +#endif /* _WIN32 */ diff --git a/lib/base/process.cpp b/lib/base/process.cpp index 8c1aa0239..3ac71fb53 100644 --- a/lib/base/process.cpp +++ b/lib/base/process.cpp @@ -25,102 +25,33 @@ using namespace icinga; -bool Process::m_ThreadCreated = false; +bool Process::m_WorkersCreated = false; boost::mutex Process::m_Mutex; deque Process::m_Tasks; -#ifndef _MSC_VER -int Process::m_TaskFd; -extern char **environ; -#endif /* _MSC_VER */ Process::Process(const vector& arguments, const Dictionary::Ptr& extraEnvironment) - : AsyncTask(), -#ifndef _MSC_VER - m_FD(-1) -#else /* _MSC_VER */ - m_FP(NULL) -#endif /* _MSC_VER */ + : AsyncTask(), m_Arguments(arguments), m_ExtraEnvironment(extraEnvironment) { assert(Application::IsMainThread()); - if (!m_ThreadCreated) { -#ifndef _MSC_VER - int fds[2]; + if (!m_WorkersCreated) { + CreateWorkers(); - if (pipe(fds) < 0) - BOOST_THROW_EXCEPTION(PosixException("pipe() failed.", errno)); - - m_TaskFd = fds[1]; -#endif /* _MSC_VER */ - - for (int i = 0; i < thread::hardware_concurrency(); i++) { - int childTaskFd; - -#ifdef _MSC_VER - childTaskFd = 0; -#else /* _MSC_VER */ - childTaskFd = dup(fds[0]); - - if (childTaskFd < 0) - BOOST_THROW_EXCEPTION(PosixException("dup() failed.", errno)); - - int flags; - flags = fcntl(childTaskFd, F_GETFL, 0); - if (flags < 0) - BOOST_THROW_EXCEPTION(PosixException("fcntl failed", errno)); - - if (fcntl(childTaskFd, F_SETFL, flags | O_NONBLOCK | O_CLOEXEC) < 0) - BOOST_THROW_EXCEPTION(PosixException("fcntl failed", errno)); -#endif /* _MSC_VER */ - - thread t(&Process::WorkerThreadProc, childTaskFd); - t.detach(); - } - - m_ThreadCreated = true; + m_WorkersCreated = true; } - // build argv - m_Arguments = new char *[arguments.size() + 1]; - - for (int i = 0; i < arguments.size(); i++) - m_Arguments[i] = strdup(arguments[i].CStr()); - - m_Arguments[arguments.size()] = NULL; - - // build envp - int envc = 0; - - /* count existing environment variables */ - while (environ[envc] != NULL) - envc++; - - m_Environment = new char *[envc + (extraEnvironment ? extraEnvironment->GetLength() : 0) + 1]; - - for (int i = 0; i < envc; i++) - m_Environment[i] = strdup(environ[i]); - - if (extraEnvironment) { - String key; - Value value; - int index = envc; - BOOST_FOREACH(tie(key, value), extraEnvironment) { - String kv = key + "=" + Convert::ToString(value); - m_Environment[index] = strdup(kv.CStr()); - index++; - } - } - - m_Environment[envc + (extraEnvironment ? extraEnvironment->GetLength() : 0)] = NULL; +#ifndef _WIN32 + m_FD = -1; +#endif /* _MSC_VER */ } vector Process::ParseCommand(const String& command) { // TODO: implement vector args; -#ifdef _MSC_VER +#ifdef _WIN32 args.push_back(command); -#else /* MSC_VER */ +#else /* _WIN32 */ args.push_back("sh"); args.push_back("-c"); args.push_back(command); @@ -135,320 +66,5 @@ void Process::Run(void) m_Tasks.push_back(GetSelf()); } -#ifndef _MSC_VER - /** - * This little gem which is commonly known as the "self-pipe trick" - * takes care of waking up the select() call in the worker thread. - */ - if (write(m_TaskFd, "T", 1) < 0) - BOOST_THROW_EXCEPTION(PosixException("write() failed.", errno)); -#endif /* _MSC_VER */ -} - -void Process::WorkerThreadProc(int taskFd) -{ - map tasks; - pollfd *pfds = NULL; - - for (;;) { - map::iterator it, prev; - -#ifndef _MSC_VER - pfds = (pollfd *)realloc(pfds, (1 + tasks.size()) * sizeof(pollfd)); - - if (pfds == NULL) - BOOST_THROW_EXCEPTION(PosixException("realloc() failed.", errno)); - - int idx = 0; - - int fd; - BOOST_FOREACH(tie(fd, tuples::ignore), tasks) { - pfds[idx].fd = fd; - pfds[idx].events = POLLIN | POLLHUP; - idx++; - } - - if (tasks.size() < MaxTasksPerThread) { - pfds[idx].fd = taskFd; - pfds[idx].events = POLLIN; - idx++; - } - - int rc = poll(pfds, idx, -1); - - if (rc < 0 && errno != EINTR) - BOOST_THROW_EXCEPTION(PosixException("poll() failed.", errno)); - - if (rc == 0) - continue; - -#else /* _MSC_VER */ - Utility::Sleep(1); -#endif /* _MSC_VER */ - -#ifndef _MSC_VER - for (int i = 0; i < idx; i++) { - if ((pfds[i].revents & (POLLIN|POLLHUP)) == 0) - continue; - - if (pfds[i].fd == taskFd) { -#endif /* _MSC_VER */ - - while (tasks.size() < MaxTasksPerThread) { - Process::Ptr task; - - { - boost::mutex::scoped_lock lock(m_Mutex); - -#ifndef _MSC_VER - /* Read one byte for every task we take from the pending tasks list. */ - char buffer; - int rc = read(taskFd, &buffer, sizeof(buffer)); - - if (rc < 0) { - if (errno == EAGAIN) - break; /* Someone else was faster and took our task. */ - - BOOST_THROW_EXCEPTION(PosixException("read() failed.", errno)); - } - - assert(!m_Tasks.empty()); -#else /* _MSC_VER */ - if (m_Tasks.empty()) - break; -#endif /* _MSC_VER */ - - task = m_Tasks.front(); - m_Tasks.pop_front(); - } - - try { - task->InitTask(); - -#ifdef _MSC_VER - int fd = fileno(task->m_FP); -#else /* _MSC_VER */ - int fd = task->m_FD; -#endif /* _MSC_VER */ - - if (fd >= 0) - tasks[fd] = task; - } catch (...) { - Event::Post(boost::bind(&Process::FinishException, task, boost::current_exception())); - } - } -#ifndef _MSC_VER - - continue; - } - - it = tasks.find(pfds[i].fd); - - if (it == tasks.end()) - continue; -#else /* _MSC_VER */ - for (it = tasks.begin(); it != tasks.end(); ) { - int fd = it->first; -#endif /* _MSC_VER */ - Process::Ptr task = it->second; - - if (!task->RunTask()) { - prev = it; -#ifdef _MSC_VER - it++; -#endif /* _MSC_VER */ - tasks.erase(prev); - - Event::Post(boost::bind(&Process::FinishResult, task, task->m_Result)); -#ifdef _MSC_VER - } else { - it++; -#endif /* _MSC_VER */ - } -#ifdef _MSC_VER - } -#else /* _MSC_VER */ - } -#endif /* _MSC_VER */ - } -} - -void Process::InitTask(void) -{ - m_Result.ExecutionStart = Utility::GetTime(); - -#ifdef _MSC_VER - assert(m_FP == NULL); -#else /* _MSC_VER */ - assert(m_FD == -1); -#endif /* _MSC_VER */ - -#ifdef _MSC_VER - String cmdLine; - - // This is almost certainly wrong, but will have to do for now. (#3684) - for (int i = 0; m_Arguments[i] != NULL ; i++) { - cmdLine += "\""; - cmdLine += m_Arguments[i]; - cmdLine += "\" "; - } - - // free arguments - for (int i = 0; m_Arguments[i] != NULL; i++) - free(m_Arguments[i]); - - delete [] m_Arguments; - - // free environment - for (int i = 0; m_Environment[i] != NULL; i++) - free(m_Environment[i]); - - delete [] m_Environment; - - m_FP = _popen(cmdLine.CStr(), "r"); -#else /* _MSC_VER */ - int fds[2]; - -#ifdef HAVE_PIPE2 - if (pipe2(fds, O_NONBLOCK | O_CLOEXEC) < 0) -#else /* HAVE_PIPE2 */ - if (pipe(fds) < 0) -#endif /* HAVE_PIPE2 */ - BOOST_THROW_EXCEPTION(PosixException("pipe() failed.", errno)); - -#ifndef HAVE_PIPE2 - int flags; - flags = fcntl(fds[0], F_GETFL, 0); - if (flags < 0) - BOOST_THROW_EXCEPTION(PosixException("fcntl failed", errno)); - - if (fcntl(fds[0], F_SETFL, flags | O_NONBLOCK | O_CLOEXEC) < 0) - BOOST_THROW_EXCEPTION(PosixException("fcntl failed", errno)); - - flags = fcntl(fds[1], F_GETFL, 0); - if (flags < 0) - BOOST_THROW_EXCEPTION(PosixException("fcntl failed", errno)); - - if (fcntl(fds[1], F_SETFL, flags | O_NONBLOCK | O_CLOEXEC) < 0) - BOOST_THROW_EXCEPTION(PosixException("fcntl failed", errno)); -#endif /* HAVE_PIPE2 */ - -#ifdef HAVE_VFORK - m_Pid = vfork(); -#else /* HAVE_VFORK */ - m_Pid = fork(); -#endif /* HAVE_VFORK */ - - if (m_Pid < 0) - BOOST_THROW_EXCEPTION(PosixException("fork() failed.", errno)); - - if (m_Pid == 0) { - // child process - - if (dup2(fds[1], STDOUT_FILENO) < 0 || dup2(fds[1], STDERR_FILENO) < 0) { - perror("dup2() failed."); - _exit(128); - } - - (void) close(fds[0]); - (void) close(fds[1]); - - if (execvpe(m_Arguments[0], m_Arguments, m_Environment) < 0) { - perror("execvpe() failed."); - _exit(128); - } - - _exit(128); - } - - // parent process - - // free arguments - for (int i = 0; m_Arguments[i] != NULL; i++) - free(m_Arguments[i]); - - delete [] m_Arguments; - - // free environment - for (int i = 0; m_Environment[i] != NULL; i++) - free(m_Environment[i]); - - delete [] m_Environment; - - (void) close(fds[1]); - - m_FD = fds[0]; -#endif /* _MSC_VER */ -} - -bool Process::RunTask(void) -{ - char buffer[512]; - int rc; - -#ifndef _MSC_VER - do { - rc = read(m_FD, buffer, sizeof(buffer)); -#else /* _MSC_VER */ - if (!feof(m_FP)) - rc = fread(buffer, 1, sizeof(buffer), m_FP); - else - rc = 0; -#endif /* _MSC_VER */ - - if (rc > 0) { - m_OutputStream.write(buffer, rc); -#ifdef _MSC_VER - return true; -#endif /* _MSC_VER */ - } -#ifndef _MSC_VER - } while (rc > 0); - - if (rc < 0 && errno == EAGAIN) - return true; -#endif /* _MSC_VER */ - - String output = m_OutputStream.str(); - - int status, exitcode; - -#ifdef _MSC_VER - status = _pclose(m_FP); -#else /* _MSC_VER */ - (void) close(m_FD); - - if (waitpid(m_Pid, &status, 0) != m_Pid) - BOOST_THROW_EXCEPTION(PosixException("waitpid() failed.", errno)); -#endif /* _MSC_VER */ - -#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.ExecutionEnd = Utility::GetTime(); - m_Result.ExitStatus = exitcode; - m_Result.Output = output; - - return false; + NotifyWorker(); } diff --git a/lib/base/process.h b/lib/base/process.h index c3c661401..f2805bce0 100644 --- a/lib/base/process.h +++ b/lib/base/process.h @@ -54,17 +54,15 @@ public: static vector ParseCommand(const String& command); private: - static bool m_ThreadCreated; + static bool m_WorkersCreated; - char **m_Arguments; - char **m_Environment; + vector m_Arguments; + Dictionary::Ptr m_ExtraEnvironment; -#ifndef _MSC_VER +#ifndef _WIN32 pid_t m_Pid; int m_FD; -#else /* _MSC_VER */ - FILE *m_FP; -#endif /* _MSC_VER */ +#endif /* _WIN32 */ stringstream m_OutputStream; @@ -74,11 +72,20 @@ private: static boost::mutex m_Mutex; static deque m_Tasks; -#ifndef _MSC_VER +#ifndef _WIN32 static int m_TaskFd; -#endif /* _MSC_VER */ +#endif /* _WIN32 */ + static void CreateWorkers(void); + static void NotifyWorker(void); + + void SpawnTask(void); + +#ifdef _WIN32 + static void WorkerThreadProc(void); +#else /* _WIN32 */ static void WorkerThreadProc(int taskFd); +#endif /* _WIN32 */ void InitTask(void); bool RunTask(void);