protect pipe fd with O_CLOEXEC
[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     sigset_t oldmask, newmask;
253
254     if (!out && !err) {
255         errno = EINVAL;
256         PIPE_CTOR_FAIL("ctor");
257     }
258
259     errno = 0;
260     if (::pipe2 (pipefd, O_CLOEXEC) == -1) {
261         PIPE_CTOR_FAIL("pipe2");
262     }
263
264     sigfillset (&newmask);
265     sigprocmask (SIG_SETMASK, &newmask, &oldmask);
266
267     errno = 0;
268     pid_t childpid = fork ();
269     switch (childpid) {
270         case -1: {
271             sigprocmask (SIG_SETMASK, &oldmask, NULL);
272             PIPE_CTOR_FAIL("fork");
273             break;
274         }
275         case 0: {
276             close (pipefd [0]);
277
278             fcntl (pipefd [1], F_SETFD, 0);
279
280             if (!out) {
281                 if (!redirect_devnull (STDOUT_FILENO)) {
282                     fprintf(stderr, "redirect_devnull/stdout: %m\n");
283                     /* XXX should we bail here? */
284                 }
285             } else if (dup2 (pipefd[1], STDOUT_FILENO) == -1) {
286                 fprintf(stderr, "dup2/stdout: %m\n");
287                 exit(EXIT_FAILURE);
288             }
289
290             if (!err) {
291                 if (!redirect_devnull (STDERR_FILENO)) {
292                     fprintf(stderr, "redirect_devnull/stderr: %m\n");
293                     /* XXX should we bail here? */
294                 }
295             } else if (dup2 (pipefd[1], STDERR_FILENO) == -1) {
296                 fprintf(stderr, "dup2/stderr: %m\n");
297                 exit(EXIT_FAILURE);
298             }
299
300             close (pipefd [1]);
301
302             sigprocmask (SIG_SETMASK, &oldmask, NULL);
303
304             errno = 0;
305             if (path) {
306                 execvpe (argv [0], const_cast <char *const *>(argv), environ);
307             } else {
308                 execve (argv [0], const_cast <char *const *>(argv), NULL);
309             }
310             exit(EXIT_FAILURE);
311             break;
312         }
313         default: {
314             close (pipefd [1]);
315
316             sigprocmask (SIG_SETMASK, &oldmask, NULL);
317
318             errno = 0;
319             if ((pipeobj = fdopen (pipefd [0], "r")) == NULL) {
320                 PIPE_CTOR_FAIL("fdopen");
321             }
322             break;
323         }
324     }
325
326     return std::make_pair (childpid, pipeobj);
327 }
328
329 inpipebuf::inpipebuf(const char *const *command,
330                      const bool out,
331                      const bool err,
332                      const bool path)
333     : pipe (NULL) /* brr: shadowing global ident */
334     , pid (-1)
335     , status_set (NULL)
336     , exit_status (NULL)
337 {
338     if (command == NULL || command [0] == NULL) {
339         PIPE_CTOR_FAIL("command");
340     }
341
342     std::pair <pid_t, FILE *> tmp =
343         this->init_without_shell (command, out, err, path);
344     this->pid  = tmp.first; /* no std::tie :/ */
345     this->pipe = tmp.second;
346
347     setg (&buffer, &buffer, &buffer);
348 }
349
350 inpipebuf::inpipebuf(const std::vector<std::string> &command,
351                      const bool out,
352                      const bool err,
353                      const bool path)
354     : pipe (NULL) /* brr: shadowing global ident */
355     , pid (-1)
356     , status_set (NULL)
357     , exit_status (NULL)
358 {
359     if (command.empty ()) {
360         PIPE_CTOR_FAIL("command");
361     }
362
363     const boost::shared_array <char *> argv = mk_argv (command);
364     if (!argv) {
365         PIPE_CTOR_FAIL("malloc");
366     }
367
368     std::pair <pid_t, FILE *> tmp =
369         this->init_without_shell (argv.get (), out, err, path);
370     this->pid  = tmp.first;
371     this->pipe = tmp.second;
372
373     setg (&buffer, &buffer, &buffer);
374 }
375
376 inpipebuf::inpipebuf(const std::string& command,
377                      const bool _ignored_out,
378                      const bool _ignored_err,
379                      const bool _ignored_path)
380     : pid (-1)
381     , status_set (NULL)
382     , exit_status (NULL)
383 {
384     pipe = popen (command.c_str(), "r");
385     if (pipe == NULL)
386         throw EXCEPTION (pipestream_error, "can't open program or permission denied");
387
388     // force underflow
389     setg (&buffer, &buffer, &buffer);
390 }
391
392 inpipebuf::~inpipebuf()
393 {
394     if (pipe != NULL) {
395         int status;
396
397         if (this->pid == -1)
398         {
399             errno = 0;
400             status = pclose (pipe);
401             if (status != -1) {
402                 if (exit_status != NULL) {
403                     *exit_status = status;
404                     if (status_set != NULL) {
405                         *status_set = true;
406                     }
407                 }
408             }
409         }
410         else
411         {
412             errno = 0;
413             status = fclose (pipe);
414             if (status != EOF) {
415                 if (exit_status != NULL) {
416                     *exit_status = status; /* might be overwritten below */
417                     if (status_set != NULL) {
418                         *status_set = true;
419                     }
420                 }
421             }
422
423             errno = 0;
424             while (waitpid (this->pid, &status, 0) == -1) {
425                 if (errno != EINTR) {
426                     status = -1;
427                     break;
428                 }
429             }
430             if (status != 0 && exit_status != NULL) {
431                 *exit_status = status; /* might overwrite pipe status above */
432                 if (status_set != NULL) {
433                     *status_set = true;
434                 }
435             }
436         }
437
438         pipe = NULL;
439     }
440 }
441
442 /** note: exit status only available after destruction */
443 void inpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
444
445     status_set = _status_set; 
446     exit_status = _exit_status; 
447 }
448
449 inpipebuf::int_type inpipebuf::underflow()
450 {
451     if (gptr() < egptr())
452         return traits_type::to_int_type(*gptr());
453
454     buffer = fgetc (pipe);
455     if (feof (pipe))
456     {
457         // ERROR or EOF
458         return EOF;
459     }
460
461     setg (&buffer, &buffer, &buffer+sizeof(char));
462
463     return traits_type::to_int_type(*gptr());
464 }
465
466 outpipebuf::outpipebuf(const std::string& command)
467 {
468     status_set = NULL;
469     exit_status = NULL;
470
471     pipe = popen (command.c_str(), "w");
472     if (pipe == NULL)
473         throw EXCEPTION (pipestream_error, "can't open program or permission denied");
474 }
475
476 outpipebuf::~outpipebuf()
477 {
478     if (pipe != NULL) {
479         int pclose_exit = pclose (pipe);
480
481         if (exit_status && pclose_exit != -1)
482         {
483             if (status_set)
484                 *status_set = true;
485             *exit_status = pclose_exit;
486         }
487
488         pipe = NULL;
489     }
490 }
491
492 /** note: exit status only available after destruction */
493 void outpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
494
495     status_set = _status_set; 
496     exit_status = _exit_status; 
497 }
498
499 outpipebuf::int_type outpipebuf::overflow(int_type c)
500 {
501     if (c != EOF)
502     {
503         if (fputc(c,pipe)==EOF)
504             return EOF;
505     }
506     return c;
507 }
508
509 std::streamsize outpipebuf::xsputn(const char* s, std::streamsize num)
510 {
511     return fwrite(s,num,1,pipe);
512 }