From ae4240026e91a27fb5938811f437cc5495d27572 Mon Sep 17 00:00:00 2001 From: Vincent Richard Date: Sat, 30 Apr 2005 08:19:56 +0000 Subject: [PATCH] Added 'childProcess'. --- ChangeLog | 5 + SConstruct | 2 + src/messaging/sendmail/sendmailTransport.cpp | 211 +--------- src/platforms/posix/posixChildProcess.cpp | 383 +++++++++++++++++++ src/platforms/posix/posixHandler.cpp | 8 + src/platforms/windows/windowsHandler.cpp | 7 + vmime/platformDependant.hpp | 13 + vmime/platforms/posix/posixChildProcess.hpp | 74 ++++ vmime/platforms/posix/posixHandler.hpp | 4 + vmime/platforms/windows/windowsHandler.hpp | 4 +- 10 files changed, 515 insertions(+), 196 deletions(-) create mode 100644 src/platforms/posix/posixChildProcess.cpp create mode 100644 vmime/platforms/posix/posixChildProcess.hpp diff --git a/ChangeLog b/ChangeLog index 7e7e8aac..9b582a50 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,11 @@ VERSION 0.7.1cvs ================ +2005-04-30 Vincent Richard + + * utility/childProcess.{hpp|cpp}: added a 'childProcess' class to help + with spawning child processes (used in 'sendmail' implementation). + 2005-04-28 Stefan Uhrig * README.msvc: added guide describing how to compile VMime using diff --git a/SConstruct b/SConstruct index b47344b3..0c0f7725 100644 --- a/SConstruct +++ b/SConstruct @@ -146,6 +146,7 @@ libvmime_sources = [ 'types.hpp', 'word.cpp', 'word.hpp', 'vmime.hpp', + 'utility/childProcess.hpp', 'utility/file.hpp', 'utility/datetimeUtils.cpp', 'utility/datetimeUtils.hpp', 'utility/filteredStream.cpp', 'utility/filteredStream.hpp', @@ -245,6 +246,7 @@ libvmime_messaging_proto_sources = [ libvmime_platforms_sources = { 'posix': [ + 'platforms/posix/posixChildProcess.cpp', 'platforms/posix/posixChildProcess.hpp', 'platforms/posix/posixFile.cpp', 'platforms/posix/posixFile.hpp', 'platforms/posix/posixHandler.cpp', 'platforms/posix/posixHandler.hpp', 'platforms/posix/posixSocket.cpp', 'platforms/posix/posixSocket.hpp' diff --git a/src/messaging/sendmail/sendmailTransport.cpp b/src/messaging/sendmail/sendmailTransport.cpp index 87d1637f..1d4d2361 100644 --- a/src/messaging/sendmail/sendmailTransport.cpp +++ b/src/messaging/sendmail/sendmailTransport.cpp @@ -24,22 +24,14 @@ #include "vmime/message.hpp" #include "vmime/mailboxList.hpp" -#include "vmime/messaging/authHelper.hpp" - #include "vmime/utility/filteredStream.hpp" +#include "vmime/utility/childProcess.hpp" +#include "vmime/utility/smartPtr.hpp" #if VMIME_BUILTIN_PLATFORM_POSIX -#include -#include -#include -#include -#include -#include - - namespace vmime { namespace messaging { namespace sendmail { @@ -138,204 +130,33 @@ void sendmailTransport::send } -static const string getSignalMessage(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)"; -} - - -static const string getErrorMessage(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 -} - - - -#ifndef VMIME_BUILDING_DOC - -// Output stream adapter for UNIX pipe - -class outputStreamPipeAdapter : public utility::outputStream -{ -public: - - outputStreamPipeAdapter(const int desc) - : m_desc(desc) - { - } - - void write(const value_type* const data, const size_type count) - { - if (::write(m_desc, data, count) == -1) - { - const string errorMsg = getErrorMessage(errno); - throw exceptions::system_error(errorMsg); - } - } - -private: - - int m_desc; -}; - -#endif // VMIME_BUILDING_DOC - - - -// 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 sendmailTransport::internalSend (const std::vector args, utility::inputStream& is, const utility::stream::size_type size, utility::progressionListener* progress) { - // Construct C-style argument array - const char** argv = new const char*[args.size() + 2]; + const utility::file::path path = vmime::platformDependant::getHandler()-> + getFileSystemFactory()->stringToPath(m_sendmailPath); - argv[0] = "sendmail"; - argv[args.size()] = NULL; + utility::auto_ptr proc = + vmime::platformDependant::getHandler()-> + getChildProcessFactory()->create(path); - for (unsigned int i = 0 ; i < args.size() ; ++i) - argv[i + 1] = args[i].c_str(); - - // Create a pipe to communicate with sendmail - int fd[2]; - - if (pipe(fd) == -1) - { - throw exceptions::system_error(getErrorMessage(errno)); - } - - // Block SIGCHLD so the calling application doesn't notice - // sendmail exiting before we do - sigset_t mask, oldMask; - - sigemptyset(&mask); - sigaddset(&mask, SIGCHLD); - sigprocmask(SIG_BLOCK, &mask, &oldMask); - - // Spawn 'sendmail' process - pid_t pid = fork(); - - if (pid == -1) // error - { - const string errorMsg = getErrorMessage(errno); - - sigprocmask(SIG_SETMASK, &oldMask, NULL); - - close(fd[0]); - close(fd[1]); - - throw exceptions::system_error(errorMsg); - } - else if (pid == 0) // child process - { - dup2(fd[0], STDIN_FILENO); - close(fd[1]); - - execv(m_sendmailPath.c_str(), const_cast (argv)); - _exit(255); - } - - close(fd[0]); + proc->start(args, utility::childProcess::FLAG_REDIRECT_STDIN); // Copy message data from input stream to output pipe - try - { - outputStreamPipeAdapter pos(fd[1]); + utility::outputStream& os = *(proc->getStdIn()); - // Workaround for lame sendmail implementations that - // can't handle CRLF eoln sequences: we transform CRLF - // sequences into LF characters. - utility::CRLFToLFFilteredOutputStream fos(pos); + // Workaround for lame sendmail implementations that + // can't handle CRLF eoln sequences: we transform CRLF + // sequences into LF characters. + utility::CRLFToLFFilteredOutputStream fos(os); - // TODO: remove 'Bcc:' field from message header + // TODO: remove 'Bcc:' field from message header - utility::bufferedStreamCopy(is, fos, size, progress); - } - catch (exception& e) - { - close(fd[1]); - - int wstat; - - while (waitpid(pid, &wstat, 0) == -1 && errno == EINTR) - ; - - sigprocmask(SIG_SETMASK, &oldMask, NULL); - - throw; - } - - close(fd[1]); + utility::bufferedStreamCopy(is, fos, size, progress); // Wait for sendmail to exit - int wstat; - - while (waitpid(pid, &wstat, 0) == -1 && errno == EINTR) - ; - - sigprocmask(SIG_SETMASK, &oldMask, NULL); - - if (!WIFEXITED(wstat)) - { - throw exceptions::system_error("sendmail exited with signal " - + getSignalMessage(WTERMSIG(wstat)) + ", mail not sent"); - } - else if (WEXITSTATUS(wstat) != 0) - { - if (WEXITSTATUS(wstat) == 255) - { - std::ostringstream oss; - oss << "Could not execute '" << m_sendmailPath; - oss << "', mail not sent"; - - throw exceptions::system_error(oss.str()); - } - else - { - std::ostringstream oss; - oss << "sendmail exited with status " << WEXITSTATUS(wstat); - oss << ", mail not sent"; - - throw exceptions::system_error(oss.str()); - } - } + proc->waitForFinish(); } diff --git a/src/platforms/posix/posixChildProcess.cpp b/src/platforms/posix/posixChildProcess.cpp new file mode 100644 index 00000000..c44e1549 --- /dev/null +++ b/src/platforms/posix/posixChildProcess.cpp @@ -0,0 +1,383 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 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 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/platforms/posix/posixChildProcess.hpp" +#include "vmime/platforms/posix/posixFile.hpp" + +#include "vmime/exception.hpp" + +#include "vmime/utility/smartPtr.hpp" + +#include +#include +#include +#include +#include +#include + + +namespace vmime { +namespace platforms { +namespace posix { + + +// posixChildProcessFactory + +utility::childProcess* posixChildProcessFactory::create(const utility::file::path& path) const +{ + return new posixChildProcess(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 write(const value_type* const data, const size_type 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) + { + } + + const bool eof() const + { + return (m_eof); + } + + void reset() + { + // Do nothing: unsupported + } + + const size_type skip(const size_type count) + { + // TODO: not tested + value_type buffer[4096]; + + int bytesSkipped = 0; + int 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); + } + + const size_type read(value_type* const data, const size_type count) + { + int 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_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_stdIn); + delete (m_stdOut); +} + + +// 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]; + + argv[0] = m_processPath.getLastComponent().getBuffer().c_str(); + argv[args.size()] = NULL; + + for (unsigned int i = 0 ; i < args.size() ; ++i) + argv[i + 1] = args[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 = new outputStreamPosixPipeAdapter(m_pipe[1]); + } + else + { + close(m_pipe[1]); + m_pipe[1] = 0; + } + + if (flags & FLAG_REDIRECT_STDOUT) + { + m_stdOut = new inputStreamPosixPipeAdapter(m_pipe[0]); + } + else + { + close(m_pipe[0]); + m_pipe[0] = 0; + } + + m_pid = pid; + m_started = true; +} + + +utility::outputStream* posixChildProcess::getStdIn() +{ + return (m_stdIn); +} + + +utility::inputStream* 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) + { + vmime::utility::auto_ptr pfsf + = new posixFileSystemFactory(); + + throw exceptions::system_error("Could not execute '" + + pfsf->pathToString(m_processPath) + "'"); + } + else + { + std::ostringstream oss; + oss << "Process exited with status " << WEXITSTATUS(wstat); + + throw exceptions::system_error(oss.str()); + } + } +} + + +} // posix +} // platforms +} // vmime + diff --git a/src/platforms/posix/posixHandler.cpp b/src/platforms/posix/posixHandler.cpp index c95d6f0e..cd7f35fa 100644 --- a/src/platforms/posix/posixHandler.cpp +++ b/src/platforms/posix/posixHandler.cpp @@ -46,6 +46,7 @@ posixHandler::posixHandler() #endif #if VMIME_HAVE_FILESYSTEM_FEATURES m_fileSysFactory = new posixFileSystemFactory(); + m_childProcFactory = new posixChildProcessFactory(); #endif } @@ -57,6 +58,7 @@ posixHandler::~posixHandler() #endif #if VMIME_HAVE_FILESYSTEM_FEATURES delete (m_fileSysFactory); + delete (m_childProcFactory); #endif } @@ -188,6 +190,12 @@ vmime::utility::fileSystemFactory* posixHandler::getFileSystemFactory() const return (m_fileSysFactory); } + +vmime::utility::childProcessFactory* posixHandler::getChildProcessFactory() const +{ + return (m_childProcFactory); +} + #endif diff --git a/src/platforms/windows/windowsHandler.cpp b/src/platforms/windows/windowsHandler.cpp index bbdcc1b6..36741d25 100644 --- a/src/platforms/windows/windowsHandler.cpp +++ b/src/platforms/windows/windowsHandler.cpp @@ -257,6 +257,13 @@ vmime::utility::fileSystemFactory* windowsHandler::getFileSystemFactory() const return (m_fileSysFactory); } + +vmime::utility::childProcessFactory* windowsHandler::getChildProcessFactory() const +{ + // TODO: Not implemented + return (NULL); +} + #endif diff --git a/vmime/platformDependant.hpp b/vmime/platformDependant.hpp index 673ff5ef..b6de8e76 100644 --- a/vmime/platformDependant.hpp +++ b/vmime/platformDependant.hpp @@ -33,6 +33,7 @@ #if VMIME_HAVE_FILESYSTEM_FEATURES #include "vmime/utility/file.hpp" + #include "vmime/utility/childProcess.hpp" #endif @@ -126,8 +127,20 @@ public: virtual messaging::timeoutHandlerFactory* getTimeoutHandlerFactory(const string& name = "default") const = 0; #endif #if VMIME_HAVE_FILESYSTEM_FEATURES + /** Return a pointer to a factory that creates file-system objects. + * + * @return file-system factory + */ virtual utility::fileSystemFactory* getFileSystemFactory() const = 0; + + /** Return a pointer to a factory that creates child process objects, + * which are used to spawn processes (run executable files). + * + * @return child process factory + */ + virtual utility::childProcessFactory* getChildProcessFactory() const = 0; #endif + }; diff --git a/vmime/platforms/posix/posixChildProcess.hpp b/vmime/platforms/posix/posixChildProcess.hpp new file mode 100644 index 00000000..96d50d5a --- /dev/null +++ b/vmime/platforms/posix/posixChildProcess.hpp @@ -0,0 +1,74 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 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 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#ifndef VMIME_PLATFORMS_POSIX_POSIXCHILDPROCESS_HPP_INCLUDED +#define VMIME_PLATFORMS_POSIX_POSIXCHILDPROCESS_HPP_INCLUDED + + +#include "vmime/utility/childProcess.hpp" + + +namespace vmime { +namespace platforms { +namespace posix { + + +class posixChildProcess : public utility::childProcess +{ +public: + + posixChildProcess(const utility::file::path& path); + ~posixChildProcess(); + + void start(const std::vector args, const int flags = 0); + + utility::outputStream* getStdIn(); + utility::inputStream* getStdOut(); + + void waitForFinish(); + +private: + + utility::file::path m_processPath; + bool m_started; + + utility::outputStream* m_stdIn; + utility::inputStream* m_stdOut; + + sigset_t m_oldProcMask; + pid_t m_pid; + int m_pipe[2]; +}; + + +class posixChildProcessFactory : public utility::childProcessFactory +{ +public: + + utility::childProcess* create(const utility::file::path& path) const; +}; + + +} // posix +} // platforms +} // vmime + + +#endif // VMIME_PLATFORMS_POSIX_POSIXCHILDPROCESS_HPP_INCLUDED + diff --git a/vmime/platforms/posix/posixHandler.hpp b/vmime/platforms/posix/posixHandler.hpp index ff4b7e97..62500d9a 100644 --- a/vmime/platforms/posix/posixHandler.hpp +++ b/vmime/platforms/posix/posixHandler.hpp @@ -30,6 +30,7 @@ #if VMIME_HAVE_FILESYSTEM_FEATURES #include "vmime/platforms/posix/posixFile.hpp" + #include "vmime/platforms/posix/posixChildProcess.hpp" #endif @@ -63,6 +64,8 @@ public: #if VMIME_HAVE_FILESYSTEM_FEATURES vmime::utility::fileSystemFactory* getFileSystemFactory() const; + + vmime::utility::childProcessFactory* getChildProcessFactory() const; #endif void wait() const; @@ -75,6 +78,7 @@ private: #if VMIME_HAVE_FILESYSTEM_FEATURES posixFileSystemFactory* m_fileSysFactory; + posixChildProcessFactory* m_childProcFactory; #endif }; diff --git a/vmime/platforms/windows/windowsHandler.hpp b/vmime/platforms/windows/windowsHandler.hpp index 7db854b7..0f2c5b88 100644 --- a/vmime/platforms/windows/windowsHandler.hpp +++ b/vmime/platforms/windows/windowsHandler.hpp @@ -29,7 +29,7 @@ #endif #if VMIME_HAVE_FILESYSTEM_FEATURES - #include "vmime/platforms/windows/windowsFile.hpp" + #include "vmime/platforms/windows/windowsFile.hpp" #endif @@ -63,6 +63,8 @@ public: #if VMIME_HAVE_FILESYSTEM_FEATURES vmime::utility::fileSystemFactory* getFileSystemFactory() const; + + vmime::utility::childProcessFactory* getChildProcessFactory() const; #endif void wait() const;