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