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