// // VMime library (http://www.vmime.org) // Copyright (C) 2002 Vincent Richard // // 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 3 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. // // Linking this library statically or dynamically with other modules is making // a combined work based on this library. Thus, the terms and conditions of // the GNU General Public License cover the whole combination. // #include "vmime/config.hpp" #if VMIME_PLATFORM_IS_POSIX && VMIME_HAVE_FILESYSTEM_FEATURES #include "vmime/platforms/posix/posixChildProcess.hpp" #include "vmime/platforms/posix/posixFile.hpp" #include "vmime/exception.hpp" #include #include #include #include #include #include namespace vmime { namespace platforms { namespace posix { // posixChildProcessFactory shared_ptr posixChildProcessFactory::create(const utility::file::path& path) const { return make_shared (path); } #ifndef VMIME_BUILDING_DOC // getPosixSignalMessage // Returns the name of a POSIX signal. static const string getPosixSignalMessage(const int num) { switch (num) { case SIGHUP: return "SIGHUP"; case SIGINT: return "SIGINT"; case SIGQUIT: return "SIGQUIT"; case SIGILL: return "SIGILL"; case SIGABRT: return "SIGABRT"; case SIGFPE: return "SIGFPE"; case SIGKILL: return "SIGKILL"; case SIGSEGV: return "SIGSEGV"; case SIGPIPE: return "SIGPIPE"; case SIGALRM: return "SIGALRM"; case SIGTERM: return "SIGTERM"; case SIGUSR1: return "SIGUSR1"; case SIGUSR2: return "SIGUSR2"; case SIGCHLD: return "SIGCHLD"; case SIGCONT: return "SIGCONT"; case SIGSTOP: return "SIGSTOP"; case SIGTSTP: return "SIGTSTP"; case SIGTTIN: return "SIGTTIN"; case SIGTTOU: return "SIGTTOU"; } return "(unknown)"; } // getPosixErrorMessage // Returns a message corresponding to an error code. static const string getPosixErrorMessage(const int num) { #ifdef strerror_r char res[256]; res[0] = '\0'; strerror_r(num, res, sizeof(res)); return string(res); #else return string(strerror(num)); #endif } // Output stream adapter for POSIX pipe class outputStreamPosixPipeAdapter : public utility::outputStream { public: outputStreamPosixPipeAdapter(const int desc) : m_desc(desc) { } void flush() { ::fsync(m_desc); } protected: void writeImpl(const byte_t* const data, const size_t count) { if (::write(m_desc, data, count) == -1) { const string errorMsg = getPosixErrorMessage(errno); throw exceptions::system_error(errorMsg); } } private: const int m_desc; }; // Input stream adapter for POSIX pipe class inputStreamPosixPipeAdapter : public utility::inputStream { public: inputStreamPosixPipeAdapter(const int desc) : m_desc(desc) { } bool eof() const { return m_eof; } void reset() { // Do nothing: unsupported } size_t skip(const size_t count) { // TODO: not tested byte_t buffer[4096]; ssize_t bytesSkipped = 0; ssize_t bytesRead = 0; while ((bytesRead = ::read(m_desc, buffer, std::min(sizeof(buffer), count - bytesSkipped))) != 0) { if (bytesRead == -1) { const string errorMsg = getPosixErrorMessage(errno); throw exceptions::system_error(errorMsg); } bytesSkipped += bytesRead; } return static_cast (bytesSkipped); } size_t read(byte_t* const data, const size_t count) { ssize_t bytesRead = 0; if ((bytesRead = ::read(m_desc, data, count)) == -1) { const string errorMsg = getPosixErrorMessage(errno); throw exceptions::system_error(errorMsg); } m_eof = (bytesRead == 0); return static_cast (bytesRead); } private: const int m_desc; bool m_eof; }; #endif // VMIME_BUILDING_DOC // posixChildProcess posixChildProcess::posixChildProcess(const utility::file::path& path) : m_processPath(path), m_started(false), m_stdIn(null), m_stdOut(null), m_pid(0), m_argArray(NULL) { m_pipe[0] = 0; m_pipe[1] = 0; sigemptyset(&m_oldProcMask); } posixChildProcess::~posixChildProcess() { if (m_started) { sigprocmask(SIG_SETMASK, &m_oldProcMask, NULL); } if (m_pipe[0] != 0) { close(m_pipe[0]); } if (m_pipe[1] != 0) { close(m_pipe[1]); } delete [] m_argArray; } // The following code is highly inspired and adapted from the 'sendmail' // provider module in Evolution data server code. // // Original authors: Dan Winship // Copyright 2000 Ximian, Inc. (www.ximian.com) void posixChildProcess::start(const std::vector & args, const int flags) { if (m_started) { return; } // Construct C-style argument array const char** argv = new const char*[args.size() + 2]; m_argVector = args; // for c_str() pointer to remain valid m_argArray = argv; // to free later argv[0] = m_processPath.getLastComponent().getBuffer().c_str(); argv[args.size() + 1] = NULL; for (unsigned int i = 0 ; i < m_argVector.size() ; ++i) { argv[i + 1] = m_argVector[i].c_str(); } // Create a pipe to communicate with the child process int fd[2]; if (pipe(fd) == -1) { throw exceptions::system_error(getPosixErrorMessage(errno)); } m_pipe[0] = fd[0]; m_pipe[1] = fd[1]; // Block SIGCHLD so the calling application doesn't notice // process exiting before we do sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGCHLD); sigprocmask(SIG_BLOCK, &mask, &m_oldProcMask); // Spawn process const pid_t pid = fork(); if (pid == -1) { // error const string errorMsg = getPosixErrorMessage(errno); sigprocmask(SIG_SETMASK, &m_oldProcMask, NULL); close(fd[0]); close(fd[1]); throw exceptions::system_error(errorMsg); } else if (pid == 0) { // child process if (flags & FLAG_REDIRECT_STDIN) { dup2(fd[0], STDIN_FILENO); } else { close(fd[0]); } if (flags & FLAG_REDIRECT_STDOUT) { dup2(fd[1], STDOUT_FILENO); } else { close(fd[1]); } posixFileSystemFactory* pfsf = new posixFileSystemFactory(); const string path = pfsf->pathToString(m_processPath); delete pfsf; execv(path.c_str(), const_cast (argv)); _exit(255); } if (flags & FLAG_REDIRECT_STDIN) { m_stdIn = make_shared (m_pipe[1]); } else { close(m_pipe[1]); m_pipe[1] = 0; } if (flags & FLAG_REDIRECT_STDOUT) { m_stdOut = make_shared (m_pipe[0]); } else { close(m_pipe[0]); m_pipe[0] = 0; } m_pid = pid; m_started = true; } shared_ptr posixChildProcess::getStdIn() { return m_stdIn; } shared_ptr posixChildProcess::getStdOut() { return m_stdOut; } void posixChildProcess::waitForFinish() { // Close stdin pipe if (m_pipe[1] != 0) { close(m_pipe[1]); m_pipe[1] = 0; } int wstat; while (waitpid(m_pid, &wstat, 0) == -1 && errno == EINTR) { ; } if (!WIFEXITED(wstat)) { throw exceptions::system_error("Process exited with signal " + getPosixSignalMessage(WTERMSIG(wstat))); } else if (WEXITSTATUS(wstat) != 0) { if (WEXITSTATUS(wstat) == 255) { scoped_ptr pfsf(new posixFileSystemFactory()); throw exceptions::system_error("Could not execute '" + pfsf->pathToString(m_processPath) + "'"); } else { std::ostringstream oss; oss.imbue(std::locale::classic()); oss << "Process exited with status " << WEXITSTATUS(wstat); throw exceptions::system_error(oss.str()); } } } } // posix } // platforms } // vmime #endif // VMIME_PLATFORM_IS_POSIX && VMIME_HAVE_FILESYSTEM_FEATURES