add pipestream ctor overload for vectors of string
[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 {
52     std::string output;
53
54     bool exit_set = false;
55     int exit_status_waitpid;
56
57     // set the results to false until we are sure we have proper values
58     rescode.normal_exit = false;
59     rescode.terminated_by_signal = false;
60
61     try
62     {
63         {
64             inpipestream ips(command);
65
66             ips.store_exit_status(&exit_set, &exit_status_waitpid);
67
68             char buffer[2048];
69             while (ips.good())
70             {
71                 ips.read(buffer, sizeof(buffer));
72                 output.append(buffer, ips.gcount());
73             }
74         }
75
76         // exit_status_waitpid only valid after destruction of the inpipestream
77
78         if (exit_set)
79         {
80             rescode.normal_exit = WIFEXITED(exit_status_waitpid);
81             if (rescode.normal_exit)
82                 rescode.return_code = WEXITSTATUS(exit_status_waitpid);
83
84             rescode.terminated_by_signal = WIFSIGNALED(exit_status_waitpid);
85             if (rescode.terminated_by_signal)
86                 rescode.signal = WTERMSIG(exit_status_waitpid);
87         }
88     }
89     catch (pipestream_error &e)
90     {
91         rescode.error_message = e.what();
92     }
93
94     return output;
95 }
96
97 std::string capture_exec (const std::string &command, ExecResult &res)
98 { return capture_exec<const std::string &>(command, res); }
99
100 std::string capture_exec (const char *const *command, ExecResult &res)
101 { return capture_exec<const char *const *>(command, res); }
102
103 std::string capture_exec (const std::vector<std::string> &command, ExecResult &res)
104 {
105     return capture_exec<const std::vector<std::string> &>(command, res);
106 }
107
108 #define PIPE_CTOR_FAIL(where) \
109     do { \
110         throw EXCEPTION (pipestream_error, \
111                          std::string (where) + ": error " \
112                          + I2n::to_string (errno) \
113                          + " (" + std::string (strerror (errno)) + ")"); \
114     } while (0)
115
116 static boost::shared_array <char *>
117 mk_argv (const std::vector<std::string> &command)
118 {
119     char **ret = NULL;
120
121     try {
122         ret = new char *[command.size () * sizeof (ret[0]) + 1];
123     } catch (std::bad_alloc &) {
124         return boost::shared_array<char *> ();
125     }
126
127     size_t cur = 0;
128     BOOST_FOREACH(const std::string &arg, command) {
129         /*
130          * Casting away constness is safe since the data is always
131          * kept alive until after exec().
132          */
133         ret [cur++] = const_cast<char *> (arg.c_str ());
134     }
135
136     ret [cur] = NULL;
137
138     return boost::shared_array<char *> (ret);
139 }
140
141
142 FILE *
143 inpipebuf::init_without_shell (const char *const *argv) const
144 {
145     FILE *pipeobj = NULL;
146     int pipefd [2];
147
148     errno = 0;
149     if (::pipe (pipefd) == -1) {
150         PIPE_CTOR_FAIL("pipe");
151     }
152
153     errno = 0;
154     pid_t childpid = fork ();
155     switch (childpid) {
156         case -1: {
157             PIPE_CTOR_FAIL("fork");
158             break;
159         }
160         case 0: {
161             close (pipefd [0]);
162
163             if (dup2 (pipefd[1], STDOUT_FILENO) == -1) {
164                 PIPE_CTOR_FAIL("dup2");
165             }
166
167             if (dup2 (pipefd[1], STDERR_FILENO) == -1) {
168                 PIPE_CTOR_FAIL("dup2");
169             }
170
171             errno = 0;
172             if (execve (argv [0], const_cast <char *const *>(argv), NULL) == -1) {
173                 PIPE_CTOR_FAIL("exec");
174             }
175             break;
176         }
177         default: {
178             close (pipefd [1]);
179
180             errno = 0;
181             if ((pipeobj = fdopen (pipefd [0], "r")) == NULL) {
182                 PIPE_CTOR_FAIL("fdopen");
183             }
184             break;
185         }
186     }
187
188     return pipeobj;
189 }
190
191 inpipebuf::inpipebuf(const char *const *command)
192     : pipe (NULL) /* brr: shadowing global ident */
193     , status_set (NULL)
194     , exit_status (NULL)
195 {
196     if (command == NULL || command [0] == NULL) {
197         PIPE_CTOR_FAIL("command");
198     }
199
200     this->pipe = this->init_without_shell (command);
201
202     setg (&buffer, &buffer, &buffer);
203 }
204
205 inpipebuf::inpipebuf(const std::vector<std::string> &command)
206     : pipe (NULL) /* brr: shadowing global ident */
207     , status_set (NULL)
208     , exit_status (NULL)
209 {
210     if (command.empty ()) {
211         PIPE_CTOR_FAIL("command");
212     }
213
214     const boost::shared_array <char *> argv = mk_argv (command);
215     if (!argv) {
216         PIPE_CTOR_FAIL("malloc");
217     }
218
219     this->pipe = this->init_without_shell (argv.get ());
220
221     setg (&buffer, &buffer, &buffer);
222 }
223
224 inpipebuf::inpipebuf(const std::string& command)
225 {
226     status_set = NULL;
227     exit_status = NULL;
228
229     pipe = popen (command.c_str(), "r");
230     if (pipe == NULL)
231         throw EXCEPTION (pipestream_error, "can't open program or permission denied");
232
233     // force underflow
234     setg (&buffer, &buffer, &buffer);
235 }
236
237 inpipebuf::~inpipebuf()
238 {
239     if (pipe != NULL) {
240         int pclose_exit = pclose (pipe);
241
242         if (exit_status && pclose_exit != -1)
243         {
244             if (status_set)
245                 *status_set = true;
246             *exit_status = pclose_exit;
247         }
248
249         pipe = NULL;
250     }
251 }
252
253 /** note: exit status only available after destruction */
254 void inpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
255
256     status_set = _status_set; 
257     exit_status = _exit_status; 
258 }
259
260 inpipebuf::int_type inpipebuf::underflow()
261 {
262     if (gptr() < egptr())
263         return traits_type::to_int_type(*gptr());
264
265     buffer = fgetc (pipe);
266     if (feof (pipe))
267     {
268         // ERROR or EOF
269         return EOF;
270     }
271
272     setg (&buffer, &buffer, &buffer+sizeof(char));
273
274     return traits_type::to_int_type(*gptr());
275 }
276
277 outpipebuf::outpipebuf(const std::string& command)
278 {
279     status_set = NULL;
280     exit_status = NULL;
281
282     pipe = popen (command.c_str(), "w");
283     if (pipe == NULL)
284         throw EXCEPTION (pipestream_error, "can't open program or permission denied");
285 }
286
287 outpipebuf::~outpipebuf()
288 {
289     if (pipe != NULL) {
290         int pclose_exit = pclose (pipe);
291
292         if (exit_status && pclose_exit != -1)
293         {
294             if (status_set)
295                 *status_set = true;
296             *exit_status = pclose_exit;
297         }
298
299         pipe = NULL;
300     }
301 }
302
303 /** note: exit status only available after destruction */
304 void outpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
305
306     status_set = _status_set; 
307     exit_status = _exit_status; 
308 }
309
310 outpipebuf::int_type outpipebuf::overflow(int_type c)
311 {
312     if (c != EOF)
313     {
314         if (fputc(c,pipe)==EOF)
315             return EOF;
316     }
317     return c;
318 }
319
320 std::streamsize outpipebuf::xsputn(const char* s, std::streamsize num)
321 {
322     return fwrite(s,num,1,pipe);
323 }