add option to forward environment to pipestream
[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 55 const bool out, const bool err,
cdc166d2 56 const bool path, const bool env)
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 {
cdc166d2 70 inpipestream ips(command, out, err, path, env);
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)
cdc166d2 118{ return capture_exec<const std::string &>(command, res, true, false, 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,
cdc166d2
PG
137 const bool out, const bool err, const bool path,
138 const bool env)
139{ return capture_exec<const char *const *>(command, res, out, err, path, env); }
cc917897
PG
140
141/** @brief Instantiation of \c capture_exec for argument lists. The
142 * pipestream used to run the command will not shell out.
143 * One of \c out or \c err must be set.
144 *
145 * @param command String vector specifying the \c argv array of the
146 * command to run. Note that the binary to executed is
147 * assumed to be present at index 0.
148 * @param res (Out parameter) Store information about the termination
149 * state in this struct.
150 * @param out Whether to collect \c stdout.
151 * @param err Whether to collect \c stderr; combines with \c out.
ad4490f1 152 * @param path Wether to look up the executable in \c $PATH.
cc917897
PG
153 *
154 * @returns Captured output, combined into one string.
155 */
156std::string capture_exec (const std::vector<std::string> &command, ExecResult &res,
cdc166d2
PG
157 const bool out, const bool err, const bool path,
158 const bool env)
c2c29997 159{
cc917897 160 return capture_exec<const std::vector<std::string> &>
cdc166d2 161 (command, res, out, err, path, env);
c2c29997
PG
162}
163
9e322fb7
PG
164#define PIPE_CTOR_FAIL(where) \
165 do { \
166 throw EXCEPTION (pipestream_error, \
167 std::string (where) + ": error " \
168 + I2n::to_string (errno) \
169 + " (" + std::string (strerror (errno)) + ")"); \
170 } while (0)
171
cc917897
PG
172/** @brief Convert a string vector to a refcounted \c char**
173 * that is \c NULL terminated for use with e. g. \c execve(2).
174 *
175 * @param command List of arguments including the binary at index 0.
176 *
177 * @returns A \c boost::shared_array of pointers to the
178 * arguments plus a trailing \c NULL. Note that
179 * while the array itself is refcounted, the
180 * pointees are assumed owned by the caller and
181 * *not copyied*. I. e. they lose validity if the
182 * original strings are freed.
183 */
9e322fb7
PG
184static boost::shared_array <char *>
185mk_argv (const std::vector<std::string> &command)
186{
187 char **ret = NULL;
188
189 try {
190 ret = new char *[command.size () * sizeof (ret[0]) + 1];
191 } catch (std::bad_alloc &) {
192 return boost::shared_array<char *> ();
193 }
194
195 size_t cur = 0;
196 BOOST_FOREACH(const std::string &arg, command) {
197 /*
198 * Casting away constness is safe since the data is always
199 * kept alive until after exec().
200 */
201 ret [cur++] = const_cast<char *> (arg.c_str ());
202 }
203
204 ret [cur] = NULL;
205
206 return boost::shared_array<char *> (ret);
207}
208
ff5191e6
PG
209/** @brief Helper for redirecting a file descriptor to \c /dev/null.
210 * This will only acquire an fd the first time it is called
211 * or if it is called after unsuccessfully attempting to
212 * acquire one.
213 *
214 * @param fd The open file descriptor to operate on.
08199c66 215 * @param save_errno Out parameter: stores errno here after a syscall failure.
ff5191e6
PG
216 *
217 * @returns \c true on success, \c false otherwise (the call to
08199c66
PG
218 * either \c open(2) or \c dup2(2) failed), with errno
219 * communicated through saved_errno.
ff5191e6
PG
220 */
221static bool
08199c66 222redirect_devnull (const int fd, int &save_errno)
ff5191e6
PG
223{
224 static int nullfd = -1;
225
226 errno = 0;
227 if (nullfd == -1 && (nullfd = open ("/dev/null", O_RDWR)) == -1) {
08199c66 228 save_errno = errno;
ff5191e6
PG
229 return false;
230 }
231
232 errno = 0;
233 if (dup2 (nullfd, fd) == -1) {
08199c66 234 save_errno = errno;
ff5191e6
PG
235 return false;
236 }
237
238 return true;
239}
240
cc917897
PG
241/** @brief Helper aggregating common code for the shell-free ctors.
242 *
243 * @param argv Argument list prepared for \c execve(2).
244 * @param out Whether to capture \c stdout.
245 * @param err Whether to capture \c stderr.
246 *
247 * @returns A \c FILE* handle for streaming if successful, \c NULL
248 * otherwise.
249 */
17b459b3 250std::pair <pid_t, FILE *>
cc917897
PG
251inpipebuf::init_without_shell (const char *const *argv,
252 const bool out,
ad4490f1 253 const bool err,
cdc166d2
PG
254 const bool path,
255 const bool env) const
9e322fb7
PG
256{
257 FILE *pipeobj = NULL;
08199c66
PG
258 int pipefd [2]; /* for reading output from the child */
259 int errfd [2]; /* for determining a successful exec() */
f2da42aa 260 sigset_t oldmask, newmask;
cdc166d2 261 char *const *envp = env ? environ : NULL;
9e322fb7 262
cc917897
PG
263 if (!out && !err) {
264 errno = EINVAL;
265 PIPE_CTOR_FAIL("ctor");
266 }
267
9e322fb7 268 errno = 0;
08199c66
PG
269 if ( ::pipe2 (pipefd, O_CLOEXEC) == -1
270 || ::pipe2 (errfd , O_CLOEXEC) == -1) {
a30f9a22 271 PIPE_CTOR_FAIL("pipe2");
9e322fb7
PG
272 }
273
f2da42aa
PG
274 sigfillset (&newmask);
275 sigprocmask (SIG_SETMASK, &newmask, &oldmask);
276
9e322fb7
PG
277 errno = 0;
278 pid_t childpid = fork ();
279 switch (childpid) {
280 case -1: {
f2da42aa 281 sigprocmask (SIG_SETMASK, &oldmask, NULL);
9e322fb7
PG
282 PIPE_CTOR_FAIL("fork");
283 break;
284 }
285 case 0: {
286 close (pipefd [0]);
08199c66 287 close (errfd [0]);
9e322fb7 288
a30f9a22
PG
289 fcntl (pipefd [1], F_SETFD, 0);
290
08199c66 291 int save_errno = 0;
cc917897 292 if (!out) {
08199c66
PG
293 if (!redirect_devnull (STDOUT_FILENO, save_errno)) {
294 (void)write (errfd [1], (char *)&save_errno, sizeof(save_errno));
295 exit (EXIT_FAILURE);
ff5191e6 296 }
cc917897 297 } else if (dup2 (pipefd[1], STDOUT_FILENO) == -1) {
08199c66
PG
298 (void)write (errfd [1], (char *)&save_errno, sizeof(save_errno));
299 exit (EXIT_FAILURE);
9e322fb7
PG
300 }
301
cc917897 302 if (!err) {
08199c66
PG
303 if (!redirect_devnull (STDERR_FILENO, save_errno)) {
304 (void)write (errfd [1], (char *)&save_errno, sizeof(save_errno));
305 exit (EXIT_FAILURE);
ff5191e6 306 }
cc917897 307 } else if (dup2 (pipefd[1], STDERR_FILENO) == -1) {
08199c66
PG
308 (void)write (errfd [1], (char *)&save_errno, sizeof(save_errno));
309 exit (EXIT_FAILURE);
9e322fb7
PG
310 }
311
17b459b3
PG
312 close (pipefd [1]);
313
f2da42aa
PG
314 sigprocmask (SIG_SETMASK, &oldmask, NULL);
315
9e322fb7 316 errno = 0;
ad4490f1 317 if (path) {
cdc166d2 318 execvpe (argv [0], const_cast <char *const *>(argv), envp);
ad4490f1 319 } else {
cdc166d2 320 execve (argv [0], const_cast <char *const *>(argv), envp);
9e322fb7 321 }
08199c66
PG
322
323 (void)write (errfd [1], (char *)&errno, sizeof(errno));
324 exit (EXIT_FAILURE);
9e322fb7
PG
325 break;
326 }
327 default: {
9e322fb7
PG
328 break;
329 }
330 }
331
08199c66
PG
332 close (pipefd [1]);
333 close (errfd [1]);
334
335 /*
336 * Check whether the child exec()’ed by reading from the error pipe.
337 * The call to read(2) will block, uninterruptible due to signals being
338 * blocked. If all went well, the read(2) will return zero bytes and we can
339 * ditch the error channel.
340 *
341 * Otherwise either the read(2) failed or we actually received something
342 * through the error pipe. Both cases are treated as errors and cause an
343 * exit from the ctor.
344 */
345 char buf [sizeof (errno)];
346 int ret;
347 memset (buf, 0, sizeof (buf));
348 errno = 0;
349 if ((ret = read (errfd [0], buf, sizeof (buf))) != 0) {
350 close (pipefd [0]);
351 close (errfd [0]);
352 sigprocmask (SIG_SETMASK, &oldmask, NULL);
353 if (ret == - 1) {
354 /* read(2) failed */
355 PIPE_CTOR_FAIL("read");
356 } else {
357 /*
358 * We received data on the error channel indicating the child
359 * process never successfully exec()’ed. We grab the error code
360 * from the buffer and bail.
361 */
362 errno = *((int *)&buf[0]);
363 PIPE_CTOR_FAIL("child failed to exec()");
364 }
365 }
366
367 /*
368 * read(2) yielded zero bytes; it’s safe to use the pipe so close our end
369 * and continue.
370 */
371 close (errfd [0]);
372
373 sigprocmask (SIG_SETMASK, &oldmask, NULL);
374
375 errno = 0;
376 if ((pipeobj = fdopen (pipefd [0], "r")) == NULL) {
377 close (pipefd [0]);
378 PIPE_CTOR_FAIL("fdopen");
379 }
380
17b459b3 381 return std::make_pair (childpid, pipeobj);
9e322fb7
PG
382}
383
cc917897
PG
384inpipebuf::inpipebuf(const char *const *command,
385 const bool out,
ad4490f1 386 const bool err,
cdc166d2
PG
387 const bool path,
388 const bool env)
9e322fb7 389 : pipe (NULL) /* brr: shadowing global ident */
17b459b3 390 , pid (-1)
9e322fb7
PG
391 , status_set (NULL)
392 , exit_status (NULL)
393{
394 if (command == NULL || command [0] == NULL) {
395 PIPE_CTOR_FAIL("command");
396 }
397
ad4490f1 398 std::pair <pid_t, FILE *> tmp =
cdc166d2 399 this->init_without_shell (command, out, err, path, env);
17b459b3
PG
400 this->pid = tmp.first; /* no std::tie :/ */
401 this->pipe = tmp.second;
9e322fb7
PG
402
403 setg (&buffer, &buffer, &buffer);
404}
405
cc917897
PG
406inpipebuf::inpipebuf(const std::vector<std::string> &command,
407 const bool out,
ad4490f1 408 const bool err,
cdc166d2
PG
409 const bool path,
410 const bool env)
c2c29997 411 : pipe (NULL) /* brr: shadowing global ident */
17b459b3 412 , pid (-1)
c2c29997
PG
413 , status_set (NULL)
414 , exit_status (NULL)
415{
416 if (command.empty ()) {
417 PIPE_CTOR_FAIL("command");
418 }
419
420 const boost::shared_array <char *> argv = mk_argv (command);
421 if (!argv) {
422 PIPE_CTOR_FAIL("malloc");
423 }
424
ad4490f1 425 std::pair <pid_t, FILE *> tmp =
cdc166d2 426 this->init_without_shell (argv.get (), out, err, path, env);
17b459b3
PG
427 this->pid = tmp.first;
428 this->pipe = tmp.second;
c2c29997
PG
429
430 setg (&buffer, &buffer, &buffer);
431}
432
cc917897
PG
433inpipebuf::inpipebuf(const std::string& command,
434 const bool _ignored_out,
ad4490f1 435 const bool _ignored_err,
cdc166d2
PG
436 const bool _ignored_path,
437 const bool _ignored_env)
17b459b3
PG
438 : pid (-1)
439 , status_set (NULL)
440 , exit_status (NULL)
7e606af5 441{
7e606af5
GE
442 pipe = popen (command.c_str(), "r");
443 if (pipe == NULL)
444 throw EXCEPTION (pipestream_error, "can't open program or permission denied");
445
446 // force underflow
447 setg (&buffer, &buffer, &buffer);
448}
449
450inpipebuf::~inpipebuf()
451{
452 if (pipe != NULL) {
17b459b3 453 int status;
7e606af5 454
17b459b3 455 if (this->pid == -1)
d00589a0 456 {
17b459b3
PG
457 errno = 0;
458 status = pclose (pipe);
459 if (status != -1) {
460 if (exit_status != NULL) {
461 *exit_status = status;
462 if (status_set != NULL) {
463 *status_set = true;
464 }
465 }
466 }
467 }
468 else
469 {
470 errno = 0;
471 status = fclose (pipe);
472 if (status != EOF) {
473 if (exit_status != NULL) {
474 *exit_status = status; /* might be overwritten below */
475 if (status_set != NULL) {
476 *status_set = true;
477 }
478 }
479 }
480
481 errno = 0;
482 while (waitpid (this->pid, &status, 0) == -1) {
483 if (errno != EINTR) {
484 status = -1;
485 break;
486 }
487 }
488 if (status != 0 && exit_status != NULL) {
489 *exit_status = status; /* might overwrite pipe status above */
490 if (status_set != NULL) {
491 *status_set = true;
492 }
493 }
7e606af5
GE
494 }
495
496 pipe = NULL;
497 }
498}
499
500/** note: exit status only available after destruction */
501void inpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
502{
503 status_set = _status_set;
504 exit_status = _exit_status;
505}
506
507inpipebuf::int_type inpipebuf::underflow()
508{
509 if (gptr() < egptr())
510 return traits_type::to_int_type(*gptr());
511
512 buffer = fgetc (pipe);
513 if (feof (pipe))
514 {
515 // ERROR or EOF
516 return EOF;
517 }
518
519 setg (&buffer, &buffer, &buffer+sizeof(char));
520
521 return traits_type::to_int_type(*gptr());
522}
523
7e606af5
GE
524outpipebuf::outpipebuf(const std::string& command)
525{
526 status_set = NULL;
527 exit_status = NULL;
528
529 pipe = popen (command.c_str(), "w");
530 if (pipe == NULL)
531 throw EXCEPTION (pipestream_error, "can't open program or permission denied");
532}
533
534outpipebuf::~outpipebuf()
535{
536 if (pipe != NULL) {
537 int pclose_exit = pclose (pipe);
538
d00589a0
GE
539 if (exit_status && pclose_exit != -1)
540 {
541 if (status_set)
542 *status_set = true;
7e606af5
GE
543 *exit_status = pclose_exit;
544 }
545
546 pipe = NULL;
547 }
548}
549
550/** note: exit status only available after destruction */
551void outpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
552{
553 status_set = _status_set;
554 exit_status = _exit_status;
555}
556
557outpipebuf::int_type outpipebuf::overflow(int_type c)
558{
559 if (c != EOF)
560 {
561 if (fputc(c,pipe)==EOF)
562 return EOF;
563 }
564 return c;
565}
566
567std::streamsize outpipebuf::xsputn(const char* s, std::streamsize num)
568{
569 return fwrite(s,num,1,pipe);
570}