block signals before fork()ing the pipestream child
[libi2ncommon] / src / pipestream.cpp
CommitLineData
7e606af5
GE
1 /*
2The software in this package is distributed under the GNU General
3Public License version 2 (with a special exception described below).
4
5A copy of GNU General Public License (GPL) is included in this distribution,
6in the file COPYING.GPL.
7
8As a special exception, if other files instantiate templates or use macros
9or inline functions from this file, or you compile this file and link it
10with other works to produce a work based on this file, this file
11does not by itself cause the resulting work to be covered
12by the GNU General Public License.
13
14However the source code for this file must still be made available
15in accordance with section (3) of the GNU General Public License.
16
17This exception does not invalidate any other reasons why a work based
18on this file might be covered by the GNU General Public License.
19*/
20/***************************************************************************
21 inpipestream.cpp - C++ streambuffer wrapper
22 -------------------
23 begin : Thu Dec 27 2001
24 copyright : (C) 2001 by Intra2net AG
25 ***************************************************************************/
26
9e322fb7 27#include <errno.h>
7e606af5 28#include <stdio.h>
9e322fb7 29#include <string.h>
ff5191e6 30#include <fcntl.h>
f8f119bf 31#include <sys/wait.h>
9e322fb7 32#include <unistd.h>
7e606af5 33
7e606af5
GE
34#include <streambuf>
35#include <istream>
36#include <ostream>
37#include <cstdio>
9e322fb7
PG
38#include <boost/foreach.hpp>
39#include <boost/shared_array.hpp>
7e606af5
GE
40
41#include "exception.hxx"
9e322fb7 42#include "stringfunc.hxx"
7e606af5
GE
43#include "pipestream.hxx"
44
f8f119bf
GE
45/** @brief runs command and returns it's output as string
46 * @param command the full command with all parameters
47 * @param rescode struct containing the return code, if the program exited normally and so on
ad4490f1
PG
48 * @param out Whether to collect \c stdout.
49 * @param err Whether to collect \c stderr; combines with \c out.
50 * @param path Wether to look up the executable in \c $PATH.
f8f119bf
GE
51 * @returns the output (stdout) of the called program
52 */
9e322fb7 53template <typename CmdT>
cc917897 54std::string capture_exec(CmdT command, ExecResult &rescode,
ad4490f1
PG
55 const bool out, const bool err,
56 const bool path)
f8f119bf
GE
57{
58 std::string output;
59
1d7539d5 60 bool exit_set = false;
f8f119bf
GE
61 int exit_status_waitpid;
62
63 // set the results to false until we are sure we have proper values
64 rescode.normal_exit = false;
65 rescode.terminated_by_signal = false;
66
67 try
68 {
69 {
ad4490f1 70 inpipestream ips(command, out, err, path);
f8f119bf
GE
71
72 ips.store_exit_status(&exit_set, &exit_status_waitpid);
73
74 char buffer[2048];
75 while (ips.good())
76 {
77 ips.read(buffer, sizeof(buffer));
78 output.append(buffer, ips.gcount());
79 }
80 }
81
82 // exit_status_waitpid only valid after destruction of the inpipestream
83
84 if (exit_set)
85 {
86 rescode.normal_exit = WIFEXITED(exit_status_waitpid);
87 if (rescode.normal_exit)
88 rescode.return_code = WEXITSTATUS(exit_status_waitpid);
89
90 rescode.terminated_by_signal = WIFSIGNALED(exit_status_waitpid);
91 if (rescode.terminated_by_signal)
92 rescode.signal = WTERMSIG(exit_status_waitpid);
93 }
94 }
95 catch (pipestream_error &e)
96 {
97 rescode.error_message = e.what();
98 }
99
100 return output;
101}
102
cc917897
PG
103/** @brief Instantiation of \c capture_exec for STL string arguments.
104 * Caveat emptor: this will cause the backing stream to use \c
105 * popen(3). To avoid shelling out, please refer to one of the
106 * variants that allow passing an argument list.
107 *
108 * @param command String specifying the shell expression to be executed.
109 * @param res (Out parameter) Store information about the termination
110 * state in this struct.
111 *
112 * @returns Result of \c stdout. Note that due to the use of \c
113 * popen, the correct way to collect stderr output as
114 * well is to use shell redirection inside the expression
115 * passed.
116 */
9e322fb7 117std::string capture_exec (const std::string &command, ExecResult &res)
ad4490f1 118{ return capture_exec<const std::string &>(command, res, true, false, false); }
cc917897
PG
119
120/** @brief Instantiation of \c capture_exec for argument lists. The
121 * pipestream used to run the command will not shell out.
122 * One of \c out or \c err must be set.
123 *
124 * @param command List of \c char* specifying the \c argv array of the
125 * command to run. Note that the binary to executed is
126 * assumed to be present at index 0 and that the input
127 * is properly \c NULL terminated.
128 * @param res (Out parameter) Store information about the termination
129 * state in this struct.
130 * @param out Whether to collect \c stdout.
131 * @param err Whether to collect \c stderr; combines with \c out.
ad4490f1 132 * @param path Wether to look up the executable in \c $PATH.
cc917897
PG
133 *
134 * @returns Captured output, combined into one string.
135 */
136std::string capture_exec (const char *const *command, ExecResult &res,
ad4490f1
PG
137 const bool out, const bool err, const bool path)
138{ return capture_exec<const char *const *>(command, res, out, err, path); }
cc917897
PG
139
140/** @brief Instantiation of \c capture_exec for argument lists. The
141 * pipestream used to run the command will not shell out.
142 * One of \c out or \c err must be set.
143 *
144 * @param command String vector specifying the \c argv array of the
145 * command to run. Note that the binary to executed is
146 * assumed to be present at index 0.
147 * @param res (Out parameter) Store information about the termination
148 * state in this struct.
149 * @param out Whether to collect \c stdout.
150 * @param err Whether to collect \c stderr; combines with \c out.
ad4490f1 151 * @param path Wether to look up the executable in \c $PATH.
cc917897
PG
152 *
153 * @returns Captured output, combined into one string.
154 */
155std::string capture_exec (const std::vector<std::string> &command, ExecResult &res,
ad4490f1 156 const bool out, const bool err, const bool path)
c2c29997 157{
cc917897 158 return capture_exec<const std::vector<std::string> &>
ad4490f1 159 (command, res, out, err, path);
c2c29997
PG
160}
161
9e322fb7
PG
162#define PIPE_CTOR_FAIL(where) \
163 do { \
164 throw EXCEPTION (pipestream_error, \
165 std::string (where) + ": error " \
166 + I2n::to_string (errno) \
167 + " (" + std::string (strerror (errno)) + ")"); \
168 } while (0)
169
cc917897
PG
170/** @brief Convert a string vector to a refcounted \c char**
171 * that is \c NULL terminated for use with e. g. \c execve(2).
172 *
173 * @param command List of arguments including the binary at index 0.
174 *
175 * @returns A \c boost::shared_array of pointers to the
176 * arguments plus a trailing \c NULL. Note that
177 * while the array itself is refcounted, the
178 * pointees are assumed owned by the caller and
179 * *not copyied*. I. e. they lose validity if the
180 * original strings are freed.
181 */
9e322fb7
PG
182static boost::shared_array <char *>
183mk_argv (const std::vector<std::string> &command)
184{
185 char **ret = NULL;
186
187 try {
188 ret = new char *[command.size () * sizeof (ret[0]) + 1];
189 } catch (std::bad_alloc &) {
190 return boost::shared_array<char *> ();
191 }
192
193 size_t cur = 0;
194 BOOST_FOREACH(const std::string &arg, command) {
195 /*
196 * Casting away constness is safe since the data is always
197 * kept alive until after exec().
198 */
199 ret [cur++] = const_cast<char *> (arg.c_str ());
200 }
201
202 ret [cur] = NULL;
203
204 return boost::shared_array<char *> (ret);
205}
206
ff5191e6
PG
207/** @brief Helper for redirecting a file descriptor to \c /dev/null.
208 * This will only acquire an fd the first time it is called
209 * or if it is called after unsuccessfully attempting to
210 * acquire one.
211 *
212 * @param fd The open file descriptor to operate on.
213 *
214 * @returns \c true on success, \c false otherwise (the call to
215 * either \c open(2) or \c dup2(2) failed).
216 */
217static bool
218redirect_devnull (const int fd)
219{
220 static int nullfd = -1;
221
222 errno = 0;
223 if (nullfd == -1 && (nullfd = open ("/dev/null", O_RDWR)) == -1) {
224 return false;
225 }
226
227 errno = 0;
228 if (dup2 (nullfd, fd) == -1) {
229 return false;
230 }
231
232 return true;
233}
234
cc917897
PG
235/** @brief Helper aggregating common code for the shell-free ctors.
236 *
237 * @param argv Argument list prepared for \c execve(2).
238 * @param out Whether to capture \c stdout.
239 * @param err Whether to capture \c stderr.
240 *
241 * @returns A \c FILE* handle for streaming if successful, \c NULL
242 * otherwise.
243 */
17b459b3 244std::pair <pid_t, FILE *>
cc917897
PG
245inpipebuf::init_without_shell (const char *const *argv,
246 const bool out,
ad4490f1
PG
247 const bool err,
248 const bool path) const
9e322fb7
PG
249{
250 FILE *pipeobj = NULL;
251 int pipefd [2];
f2da42aa 252 sigset_t oldmask, newmask;
9e322fb7 253
cc917897
PG
254 if (!out && !err) {
255 errno = EINVAL;
256 PIPE_CTOR_FAIL("ctor");
257 }
258
9e322fb7
PG
259 errno = 0;
260 if (::pipe (pipefd) == -1) {
261 PIPE_CTOR_FAIL("pipe");
262 }
263
f2da42aa
PG
264 sigfillset (&newmask);
265 sigprocmask (SIG_SETMASK, &newmask, &oldmask);
266
9e322fb7
PG
267 errno = 0;
268 pid_t childpid = fork ();
269 switch (childpid) {
270 case -1: {
f2da42aa 271 sigprocmask (SIG_SETMASK, &oldmask, NULL);
9e322fb7
PG
272 PIPE_CTOR_FAIL("fork");
273 break;
274 }
275 case 0: {
276 close (pipefd [0]);
277
cc917897 278 if (!out) {
ff5191e6
PG
279 if (!redirect_devnull (STDOUT_FILENO)) {
280 fprintf(stderr, "redirect_devnull/stdout: %m\n");
281 /* XXX should we bail here? */
282 }
cc917897 283 } else if (dup2 (pipefd[1], STDOUT_FILENO) == -1) {
ad4490f1
PG
284 fprintf(stderr, "dup2/stdout: %m\n");
285 exit(EXIT_FAILURE);
9e322fb7
PG
286 }
287
cc917897 288 if (!err) {
ff5191e6
PG
289 if (!redirect_devnull (STDERR_FILENO)) {
290 fprintf(stderr, "redirect_devnull/stderr: %m\n");
291 /* XXX should we bail here? */
292 }
cc917897 293 } else if (dup2 (pipefd[1], STDERR_FILENO) == -1) {
ad4490f1
PG
294 fprintf(stderr, "dup2/stderr: %m\n");
295 exit(EXIT_FAILURE);
9e322fb7
PG
296 }
297
17b459b3
PG
298 close (pipefd [1]);
299
f2da42aa
PG
300 sigprocmask (SIG_SETMASK, &oldmask, NULL);
301
9e322fb7 302 errno = 0;
ad4490f1
PG
303 if (path) {
304 execvpe (argv [0], const_cast <char *const *>(argv), environ);
305 } else {
306 execve (argv [0], const_cast <char *const *>(argv), NULL);
9e322fb7 307 }
ad4490f1 308 exit(EXIT_FAILURE);
9e322fb7
PG
309 break;
310 }
311 default: {
312 close (pipefd [1]);
313
f2da42aa
PG
314 sigprocmask (SIG_SETMASK, &oldmask, NULL);
315
9e322fb7
PG
316 errno = 0;
317 if ((pipeobj = fdopen (pipefd [0], "r")) == NULL) {
318 PIPE_CTOR_FAIL("fdopen");
319 }
320 break;
321 }
322 }
323
17b459b3 324 return std::make_pair (childpid, pipeobj);
9e322fb7
PG
325}
326
cc917897
PG
327inpipebuf::inpipebuf(const char *const *command,
328 const bool out,
ad4490f1
PG
329 const bool err,
330 const bool path)
9e322fb7 331 : pipe (NULL) /* brr: shadowing global ident */
17b459b3 332 , pid (-1)
9e322fb7
PG
333 , status_set (NULL)
334 , exit_status (NULL)
335{
336 if (command == NULL || command [0] == NULL) {
337 PIPE_CTOR_FAIL("command");
338 }
339
ad4490f1
PG
340 std::pair <pid_t, FILE *> tmp =
341 this->init_without_shell (command, out, err, path);
17b459b3
PG
342 this->pid = tmp.first; /* no std::tie :/ */
343 this->pipe = tmp.second;
9e322fb7
PG
344
345 setg (&buffer, &buffer, &buffer);
346}
347
cc917897
PG
348inpipebuf::inpipebuf(const std::vector<std::string> &command,
349 const bool out,
ad4490f1
PG
350 const bool err,
351 const bool path)
c2c29997 352 : pipe (NULL) /* brr: shadowing global ident */
17b459b3 353 , pid (-1)
c2c29997
PG
354 , status_set (NULL)
355 , exit_status (NULL)
356{
357 if (command.empty ()) {
358 PIPE_CTOR_FAIL("command");
359 }
360
361 const boost::shared_array <char *> argv = mk_argv (command);
362 if (!argv) {
363 PIPE_CTOR_FAIL("malloc");
364 }
365
ad4490f1
PG
366 std::pair <pid_t, FILE *> tmp =
367 this->init_without_shell (argv.get (), out, err, path);
17b459b3
PG
368 this->pid = tmp.first;
369 this->pipe = tmp.second;
c2c29997
PG
370
371 setg (&buffer, &buffer, &buffer);
372}
373
cc917897
PG
374inpipebuf::inpipebuf(const std::string& command,
375 const bool _ignored_out,
ad4490f1
PG
376 const bool _ignored_err,
377 const bool _ignored_path)
17b459b3
PG
378 : pid (-1)
379 , status_set (NULL)
380 , exit_status (NULL)
7e606af5 381{
7e606af5
GE
382 pipe = popen (command.c_str(), "r");
383 if (pipe == NULL)
384 throw EXCEPTION (pipestream_error, "can't open program or permission denied");
385
386 // force underflow
387 setg (&buffer, &buffer, &buffer);
388}
389
390inpipebuf::~inpipebuf()
391{
392 if (pipe != NULL) {
17b459b3 393 int status;
7e606af5 394
17b459b3 395 if (this->pid == -1)
d00589a0 396 {
17b459b3
PG
397 errno = 0;
398 status = pclose (pipe);
399 if (status != -1) {
400 if (exit_status != NULL) {
401 *exit_status = status;
402 if (status_set != NULL) {
403 *status_set = true;
404 }
405 }
406 }
407 }
408 else
409 {
410 errno = 0;
411 status = fclose (pipe);
412 if (status != EOF) {
413 if (exit_status != NULL) {
414 *exit_status = status; /* might be overwritten below */
415 if (status_set != NULL) {
416 *status_set = true;
417 }
418 }
419 }
420
421 errno = 0;
422 while (waitpid (this->pid, &status, 0) == -1) {
423 if (errno != EINTR) {
424 status = -1;
425 break;
426 }
427 }
428 if (status != 0 && exit_status != NULL) {
429 *exit_status = status; /* might overwrite pipe status above */
430 if (status_set != NULL) {
431 *status_set = true;
432 }
433 }
7e606af5
GE
434 }
435
436 pipe = NULL;
437 }
438}
439
440/** note: exit status only available after destruction */
441void inpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
442{
443 status_set = _status_set;
444 exit_status = _exit_status;
445}
446
447inpipebuf::int_type inpipebuf::underflow()
448{
449 if (gptr() < egptr())
450 return traits_type::to_int_type(*gptr());
451
452 buffer = fgetc (pipe);
453 if (feof (pipe))
454 {
455 // ERROR or EOF
456 return EOF;
457 }
458
459 setg (&buffer, &buffer, &buffer+sizeof(char));
460
461 return traits_type::to_int_type(*gptr());
462}
463
7e606af5
GE
464outpipebuf::outpipebuf(const std::string& command)
465{
466 status_set = NULL;
467 exit_status = NULL;
468
469 pipe = popen (command.c_str(), "w");
470 if (pipe == NULL)
471 throw EXCEPTION (pipestream_error, "can't open program or permission denied");
472}
473
474outpipebuf::~outpipebuf()
475{
476 if (pipe != NULL) {
477 int pclose_exit = pclose (pipe);
478
d00589a0
GE
479 if (exit_status && pclose_exit != -1)
480 {
481 if (status_set)
482 *status_set = true;
7e606af5
GE
483 *exit_status = pclose_exit;
484 }
485
486 pipe = NULL;
487 }
488}
489
490/** note: exit status only available after destruction */
491void outpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
492{
493 status_set = _status_set;
494 exit_status = _exit_status;
495}
496
497outpipebuf::int_type outpipebuf::overflow(int_type c)
498{
499 if (c != EOF)
500 {
501 if (fputc(c,pipe)==EOF)
502 return EOF;
503 }
504 return c;
505}
506
507std::streamsize outpipebuf::xsputn(const char* s, std::streamsize num)
508{
509 return fwrite(s,num,1,pipe);
510}