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