redirect unused fds to /dev/null in pipestream
[libi2ncommon] / src / pipestream.cpp
1  /*
2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
4
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
7
8 As a special exception, if other files instantiate templates or use macros
9 or inline functions from this file, or you compile this file and link it
10 with other works to produce a work based on this file, this file
11 does not by itself cause the resulting work to be covered
12 by the GNU General Public License.
13
14 However the source code for this file must still be made available
15 in accordance with section (3) of the GNU General Public License.
16
17 This exception does not invalidate any other reasons why a work based
18 on 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
27 #include <errno.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <fcntl.h>
31 #include <sys/wait.h>
32 #include <unistd.h>
33
34 #include <streambuf>
35 #include <istream>
36 #include <ostream>
37 #include <cstdio>
38 #include <boost/foreach.hpp>
39 #include <boost/shared_array.hpp>
40
41 #include "exception.hxx"
42 #include "stringfunc.hxx"
43 #include "pipestream.hxx"
44
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
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.
51  *  @returns the output (stdout) of the called program
52  */
53 template <typename CmdT>
54 std::string capture_exec(CmdT command, ExecResult &rescode,
55                          const bool out, const bool err,
56                          const bool path)
57 {
58     std::string output;
59
60     bool exit_set = false;
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         {
70             inpipestream ips(command, out, err, path);
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
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  */
117 std::string capture_exec (const std::string &command, ExecResult &res)
118 { return capture_exec<const std::string &>(command, res, true, false, false); }
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.
132  *  @param path       Wether to look up the executable in \c $PATH.
133  *
134  *  @returns          Captured output, combined into one string.
135  */
136 std::string capture_exec (const char *const *command, ExecResult &res,
137                           const bool out, const bool err, const bool path)
138 { return capture_exec<const char *const *>(command, res, out, err, path); }
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.
151  *  @param path       Wether to look up the executable in \c $PATH.
152  *
153  *  @returns          Captured output, combined into one string.
154  */
155 std::string capture_exec (const std::vector<std::string> &command, ExecResult &res,
156                           const bool out, const bool err, const bool path)
157 {
158     return capture_exec<const std::vector<std::string> &>
159         (command, res, out, err, path);
160 }
161
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
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  */
182 static boost::shared_array <char *>
183 mk_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
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  */
217 static bool
218 redirect_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
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  */
244 std::pair <pid_t, FILE *>
245 inpipebuf::init_without_shell (const char *const *argv,
246                                const bool out,
247                                const bool err,
248                                const bool path) const
249 {
250     FILE *pipeobj = NULL;
251     int pipefd [2];
252
253     if (!out && !err) {
254         errno = EINVAL;
255         PIPE_CTOR_FAIL("ctor");
256     }
257
258     errno = 0;
259     if (::pipe (pipefd) == -1) {
260         PIPE_CTOR_FAIL("pipe");
261     }
262
263     errno = 0;
264     pid_t childpid = fork ();
265     switch (childpid) {
266         case -1: {
267             PIPE_CTOR_FAIL("fork");
268             break;
269         }
270         case 0: {
271             close (pipefd [0]);
272
273             if (!out) {
274                 if (!redirect_devnull (STDOUT_FILENO)) {
275                     fprintf(stderr, "redirect_devnull/stdout: %m\n");
276                     /* XXX should we bail here? */
277                 }
278             } else if (dup2 (pipefd[1], STDOUT_FILENO) == -1) {
279                 fprintf(stderr, "dup2/stdout: %m\n");
280                 exit(EXIT_FAILURE);
281             }
282
283             if (!err) {
284                 if (!redirect_devnull (STDERR_FILENO)) {
285                     fprintf(stderr, "redirect_devnull/stderr: %m\n");
286                     /* XXX should we bail here? */
287                 }
288             } else if (dup2 (pipefd[1], STDERR_FILENO) == -1) {
289                 fprintf(stderr, "dup2/stderr: %m\n");
290                 exit(EXIT_FAILURE);
291             }
292
293             close (pipefd [1]);
294
295             errno = 0;
296             if (path) {
297                 execvpe (argv [0], const_cast <char *const *>(argv), environ);
298             } else {
299                 execve (argv [0], const_cast <char *const *>(argv), NULL);
300             }
301             exit(EXIT_FAILURE);
302             break;
303         }
304         default: {
305             close (pipefd [1]);
306
307             errno = 0;
308             if ((pipeobj = fdopen (pipefd [0], "r")) == NULL) {
309                 PIPE_CTOR_FAIL("fdopen");
310             }
311             break;
312         }
313     }
314
315     return std::make_pair (childpid, pipeobj);
316 }
317
318 inpipebuf::inpipebuf(const char *const *command,
319                      const bool out,
320                      const bool err,
321                      const bool path)
322     : pipe (NULL) /* brr: shadowing global ident */
323     , pid (-1)
324     , status_set (NULL)
325     , exit_status (NULL)
326 {
327     if (command == NULL || command [0] == NULL) {
328         PIPE_CTOR_FAIL("command");
329     }
330
331     std::pair <pid_t, FILE *> tmp =
332         this->init_without_shell (command, out, err, path);
333     this->pid  = tmp.first; /* no std::tie :/ */
334     this->pipe = tmp.second;
335
336     setg (&buffer, &buffer, &buffer);
337 }
338
339 inpipebuf::inpipebuf(const std::vector<std::string> &command,
340                      const bool out,
341                      const bool err,
342                      const bool path)
343     : pipe (NULL) /* brr: shadowing global ident */
344     , pid (-1)
345     , status_set (NULL)
346     , exit_status (NULL)
347 {
348     if (command.empty ()) {
349         PIPE_CTOR_FAIL("command");
350     }
351
352     const boost::shared_array <char *> argv = mk_argv (command);
353     if (!argv) {
354         PIPE_CTOR_FAIL("malloc");
355     }
356
357     std::pair <pid_t, FILE *> tmp =
358         this->init_without_shell (argv.get (), out, err, path);
359     this->pid  = tmp.first;
360     this->pipe = tmp.second;
361
362     setg (&buffer, &buffer, &buffer);
363 }
364
365 inpipebuf::inpipebuf(const std::string& command,
366                      const bool _ignored_out,
367                      const bool _ignored_err,
368                      const bool _ignored_path)
369     : pid (-1)
370     , status_set (NULL)
371     , exit_status (NULL)
372 {
373     pipe = popen (command.c_str(), "r");
374     if (pipe == NULL)
375         throw EXCEPTION (pipestream_error, "can't open program or permission denied");
376
377     // force underflow
378     setg (&buffer, &buffer, &buffer);
379 }
380
381 inpipebuf::~inpipebuf()
382 {
383     if (pipe != NULL) {
384         int status;
385
386         if (this->pid == -1)
387         {
388             errno = 0;
389             status = pclose (pipe);
390             if (status != -1) {
391                 if (exit_status != NULL) {
392                     *exit_status = status;
393                     if (status_set != NULL) {
394                         *status_set = true;
395                     }
396                 }
397             }
398         }
399         else
400         {
401             errno = 0;
402             status = fclose (pipe);
403             if (status != EOF) {
404                 if (exit_status != NULL) {
405                     *exit_status = status; /* might be overwritten below */
406                     if (status_set != NULL) {
407                         *status_set = true;
408                     }
409                 }
410             }
411
412             errno = 0;
413             while (waitpid (this->pid, &status, 0) == -1) {
414                 if (errno != EINTR) {
415                     status = -1;
416                     break;
417                 }
418             }
419             if (status != 0 && exit_status != NULL) {
420                 *exit_status = status; /* might overwrite pipe status above */
421                 if (status_set != NULL) {
422                     *status_set = true;
423                 }
424             }
425         }
426
427         pipe = NULL;
428     }
429 }
430
431 /** note: exit status only available after destruction */
432 void inpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
433
434     status_set = _status_set; 
435     exit_status = _exit_status; 
436 }
437
438 inpipebuf::int_type inpipebuf::underflow()
439 {
440     if (gptr() < egptr())
441         return traits_type::to_int_type(*gptr());
442
443     buffer = fgetc (pipe);
444     if (feof (pipe))
445     {
446         // ERROR or EOF
447         return EOF;
448     }
449
450     setg (&buffer, &buffer, &buffer+sizeof(char));
451
452     return traits_type::to_int_type(*gptr());
453 }
454
455 outpipebuf::outpipebuf(const std::string& command)
456 {
457     status_set = NULL;
458     exit_status = NULL;
459
460     pipe = popen (command.c_str(), "w");
461     if (pipe == NULL)
462         throw EXCEPTION (pipestream_error, "can't open program or permission denied");
463 }
464
465 outpipebuf::~outpipebuf()
466 {
467     if (pipe != NULL) {
468         int pclose_exit = pclose (pipe);
469
470         if (exit_status && pclose_exit != -1)
471         {
472             if (status_set)
473                 *status_set = true;
474             *exit_status = pclose_exit;
475         }
476
477         pipe = NULL;
478     }
479 }
480
481 /** note: exit status only available after destruction */
482 void outpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
483
484     status_set = _status_set; 
485     exit_status = _exit_status; 
486 }
487
488 outpipebuf::int_type outpipebuf::overflow(int_type c)
489 {
490     if (c != EOF)
491     {
492         if (fputc(c,pipe)==EOF)
493             return EOF;
494     }
495     return c;
496 }
497
498 std::streamsize outpipebuf::xsputn(const char* s, std::streamsize num)
499 {
500     return fwrite(s,num,1,pipe);
501 }