1 //
2 // VMime library (http://www.vmime.org)
3 // Copyright (C) 2002-2013 Vincent Richard <vincent@vmime.org>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 3 of
8 // the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 //
19 // Linking this library statically or dynamically with other modules is making
20 // a combined work based on this library.  Thus, the terms and conditions of
21 // the GNU General Public License cover the whole combination.
22 //
23 
24 #include "vmime/config.hpp"
25 
26 
27 #if VMIME_PLATFORM_IS_POSIX && VMIME_HAVE_FILESYSTEM_FEATURES
28 
29 
30 #include "vmime/platforms/posix/posixChildProcess.hpp"
31 #include "vmime/platforms/posix/posixFile.hpp"
32 
33 #include "vmime/exception.hpp"
34 
35 #include <unistd.h>
36 #include <string.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <signal.h>
40 #include <sys/wait.h>
41 
42 
43 namespace vmime {
44 namespace platforms {
45 namespace posix {
46 
47 
48 // posixChildProcessFactory
49 
create(const utility::file::path & path) const50 shared_ptr <utility::childProcess> posixChildProcessFactory::create(const utility::file::path& path) const
51 {
52 	return make_shared <posixChildProcess>(path);
53 }
54 
55 
56 
57 #ifndef VMIME_BUILDING_DOC
58 
59 
60 // getPosixSignalMessage
61 // Returns the name of a POSIX signal.
62 
getPosixSignalMessage(const int num)63 static const string getPosixSignalMessage(const int num)
64 {
65 	switch (num)
66 	{
67 	case SIGHUP:  return "SIGHUP";
68 	case SIGINT:  return "SIGINT";
69 	case SIGQUIT: return "SIGQUIT";
70 	case SIGILL:  return "SIGILL";
71 	case SIGABRT: return "SIGABRT";
72 	case SIGFPE:  return "SIGFPE";
73 	case SIGKILL: return "SIGKILL";
74 	case SIGSEGV: return "SIGSEGV";
75 	case SIGPIPE: return "SIGPIPE";
76 	case SIGALRM: return "SIGALRM";
77 	case SIGTERM: return "SIGTERM";
78 	case SIGUSR1: return "SIGUSR1";
79 	case SIGUSR2: return "SIGUSR2";
80 	case SIGCHLD: return "SIGCHLD";
81 	case SIGCONT: return "SIGCONT";
82 	case SIGSTOP: return "SIGSTOP";
83 	case SIGTSTP: return "SIGTSTP";
84 	case SIGTTIN: return "SIGTTIN";
85 	case SIGTTOU: return "SIGTTOU";
86 	}
87 
88 	return "(unknown)";
89 }
90 
91 
92 // getPosixErrorMessage
93 // Returns a message corresponding to an error code.
94 
getPosixErrorMessage(const int num)95 static const string getPosixErrorMessage(const int num)
96 {
97 #ifdef strerror_r
98 	char res[256];
99 	res[0] = '\0';
100 
101 	strerror_r(num, res, sizeof(res));
102 
103 	return string(res);
104 #else
105 	return string(strerror(num));
106 #endif
107 }
108 
109 
110 // Output stream adapter for POSIX pipe
111 
112 class outputStreamPosixPipeAdapter : public utility::outputStream
113 {
114 public:
115 
outputStreamPosixPipeAdapter(const int desc)116 	outputStreamPosixPipeAdapter(const int desc)
117 		: m_desc(desc)
118 	{
119 	}
120 
flush()121 	void flush()
122 	{
123 		::fsync(m_desc);
124 	}
125 
126 protected:
127 
writeImpl(const byte_t * const data,const size_t count)128 	void writeImpl(const byte_t* const data, const size_t count)
129 	{
130 		if (::write(m_desc, data, count) == -1)
131 		{
132 			const string errorMsg = getPosixErrorMessage(errno);
133 			throw exceptions::system_error(errorMsg);
134 		}
135 	}
136 
137 private:
138 
139 	const int m_desc;
140 };
141 
142 
143 // Input stream adapter for POSIX pipe
144 
145 class inputStreamPosixPipeAdapter : public utility::inputStream
146 {
147 public:
148 
inputStreamPosixPipeAdapter(const int desc)149 	inputStreamPosixPipeAdapter(const int desc)
150 		: m_desc(desc)
151 	{
152 	}
153 
eof() const154 	bool eof() const
155 	{
156 		return (m_eof);
157 	}
158 
reset()159 	void reset()
160 	{
161 		// Do nothing: unsupported
162 	}
163 
skip(const size_t count)164 	size_t skip(const size_t count)
165 	{
166 		// TODO: not tested
167 		byte_t buffer[4096];
168 
169 		ssize_t bytesSkipped = 0;
170 		ssize_t bytesRead = 0;
171 
172 		while ((bytesRead = ::read(m_desc, buffer,
173 			std::min(sizeof(buffer), count - bytesSkipped))) != 0)
174 		{
175 			if (bytesRead == -1)
176 			{
177 				const string errorMsg = getPosixErrorMessage(errno);
178 				throw exceptions::system_error(errorMsg);
179 			}
180 
181 			bytesSkipped += bytesRead;
182 		}
183 
184 		return static_cast <size_t>(bytesSkipped);
185 	}
186 
read(byte_t * const data,const size_t count)187 	size_t read(byte_t* const data, const size_t count)
188 	{
189 		ssize_t bytesRead = 0;
190 
191 		if ((bytesRead = ::read(m_desc, data, count)) == -1)
192 		{
193 			const string errorMsg = getPosixErrorMessage(errno);
194 			throw exceptions::system_error(errorMsg);
195 		}
196 
197 		m_eof = (bytesRead == 0);
198 
199 		return static_cast <size_t>(bytesRead);
200 	}
201 
202 private:
203 
204 	const int m_desc;
205 
206 	bool m_eof;
207 };
208 
209 
210 #endif // VMIME_BUILDING_DOC
211 
212 
213 
214 // posixChildProcess
215 
posixChildProcess(const utility::file::path & path)216 posixChildProcess::posixChildProcess(const utility::file::path& path)
217 	: m_processPath(path), m_started(false),
218 	  m_stdIn(null), m_stdOut(null), m_pid(0), m_argArray(NULL)
219 {
220 	m_pipe[0] = 0;
221 	m_pipe[1] = 0;
222 
223 	sigemptyset(&m_oldProcMask);
224 }
225 
226 
~posixChildProcess()227 posixChildProcess::~posixChildProcess()
228 {
229 	if (m_started)
230 		sigprocmask(SIG_SETMASK, &m_oldProcMask, NULL);
231 
232 	if (m_pipe[0] != 0)
233 		close(m_pipe[0]);
234 
235 	if (m_pipe[1] != 0)
236 		close(m_pipe[1]);
237 
238 	delete [] (m_argArray);
239 }
240 
241 
242 // The following code is highly inspired and adapted from the 'sendmail'
243 // provider module in Evolution data server code.
244 //
245 // Original authors: Dan Winship <danw@ximian.com>
246 // Copyright 2000 Ximian, Inc. (www.ximian.com)
247 
start(const std::vector<string> args,const int flags)248 void posixChildProcess::start(const std::vector <string> args, const int flags)
249 {
250 	if (m_started)
251 		return;
252 
253 	// Construct C-style argument array
254 	const char** argv = new const char*[args.size() + 2];
255 
256 	m_argVector = args;  // for c_str() pointer to remain valid
257 	m_argArray = argv;   // to free later
258 
259 	argv[0] = m_processPath.getLastComponent().getBuffer().c_str();
260 	argv[args.size() + 1] = NULL;
261 
262 	for (unsigned int i = 0 ; i < m_argVector.size() ; ++i)
263 		argv[i + 1] = m_argVector[i].c_str();
264 
265 	// Create a pipe to communicate with the child process
266 	int fd[2];
267 
268 	if (pipe(fd) == -1)
269 	{
270 		throw exceptions::system_error(getPosixErrorMessage(errno));
271 	}
272 
273 	m_pipe[0] = fd[0];
274 	m_pipe[1] = fd[1];
275 
276 	// Block SIGCHLD so the calling application doesn't notice
277 	// process exiting before we do
278 	sigset_t mask;
279 
280 	sigemptyset(&mask);
281 	sigaddset(&mask, SIGCHLD);
282 	sigprocmask(SIG_BLOCK, &mask, &m_oldProcMask);
283 
284 	// Spawn process
285 	const pid_t pid = fork();
286 
287 	if (pid == -1)  // error
288 	{
289 		const string errorMsg = getPosixErrorMessage(errno);
290 
291 		sigprocmask(SIG_SETMASK, &m_oldProcMask, NULL);
292 
293 		close(fd[0]);
294 		close(fd[1]);
295 
296 		throw exceptions::system_error(errorMsg);
297 	}
298 	else if (pid == 0)  // child process
299 	{
300 		if (flags & FLAG_REDIRECT_STDIN)
301 			dup2(fd[0], STDIN_FILENO);
302 		else
303 			close(fd[0]);
304 
305 		if (flags & FLAG_REDIRECT_STDOUT)
306 			dup2(fd[1], STDOUT_FILENO);
307 		else
308 			close(fd[1]);
309 
310 		posixFileSystemFactory* pfsf = new posixFileSystemFactory();
311 
312 		const string path = pfsf->pathToString(m_processPath);
313 
314 		delete (pfsf);
315 
316 		execv(path.c_str(), const_cast <char**>(argv));
317 		_exit(255);
318 	}
319 
320 	if (flags & FLAG_REDIRECT_STDIN)
321 	{
322 		m_stdIn = make_shared <outputStreamPosixPipeAdapter>(m_pipe[1]);
323 	}
324 	else
325 	{
326 		close(m_pipe[1]);
327 		m_pipe[1] = 0;
328 	}
329 
330 	if (flags & FLAG_REDIRECT_STDOUT)
331 	{
332 		m_stdOut = make_shared <inputStreamPosixPipeAdapter>(m_pipe[0]);
333 	}
334 	else
335 	{
336 		close(m_pipe[0]);
337 		m_pipe[0] = 0;
338 	}
339 
340 	m_pid = pid;
341 	m_started = true;
342 }
343 
344 
getStdIn()345 shared_ptr <utility::outputStream> posixChildProcess::getStdIn()
346 {
347 	return (m_stdIn);
348 }
349 
350 
getStdOut()351 shared_ptr <utility::inputStream> posixChildProcess::getStdOut()
352 {
353 	return (m_stdOut);
354 }
355 
356 
waitForFinish()357 void posixChildProcess::waitForFinish()
358 {
359 	// Close stdin pipe
360 	if (m_pipe[1] != 0)
361 	{
362 		close(m_pipe[1]);
363 		m_pipe[1] = 0;
364 	}
365 
366 	int wstat;
367 
368 	while (waitpid(m_pid, &wstat, 0) == -1 && errno == EINTR)
369 		;
370 
371 	if (!WIFEXITED(wstat))
372 	{
373 		throw exceptions::system_error("Process exited with signal "
374 			+ getPosixSignalMessage(WTERMSIG(wstat)));
375 	}
376 	else if (WEXITSTATUS(wstat) != 0)
377 	{
378 		if (WEXITSTATUS(wstat) == 255)
379 		{
380 			scoped_ptr <posixFileSystemFactory> pfsf(new posixFileSystemFactory());
381 
382 			throw exceptions::system_error("Could not execute '"
383 				+ pfsf->pathToString(m_processPath) + "'");
384 		}
385 		else
386 		{
387 			std::ostringstream oss;
388 			oss.imbue(std::locale::classic());
389 
390 			oss << "Process exited with status " << WEXITSTATUS(wstat);
391 
392 			throw exceptions::system_error(oss.str());
393 		}
394 	}
395 }
396 
397 
398 } // posix
399 } // platforms
400 } // vmime
401 
402 
403 #endif // VMIME_PLATFORM_IS_POSIX && VMIME_HAVE_FILESYSTEM_FEATURES
404 
405