ebcc6c295655eaec015bb6762b59cffb40fea743
[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 #define PIPE_CTOR_FAIL(where) \
104     do { \
105         throw EXCEPTION (pipestream_error, \
106                          std::string (where) + ": error " \
107                          + I2n::to_string (errno) \
108                          + " (" + std::string (strerror (errno)) + ")"); \
109     } while (0)
110
111 static boost::shared_array <char *>
112 mk_argv (const std::vector<std::string> &command)
113 {
114     char **ret = NULL;
115
116     try {
117         ret = new char *[command.size () * sizeof (ret[0]) + 1];
118     } catch (std::bad_alloc &) {
119         return boost::shared_array<char *> ();
120     }
121
122     size_t cur = 0;
123     BOOST_FOREACH(const std::string &arg, command) {
124         /*
125          * Casting away constness is safe since the data is always
126          * kept alive until after exec().
127          */
128         ret [cur++] = const_cast<char *> (arg.c_str ());
129     }
130
131     ret [cur] = NULL;
132
133     return boost::shared_array<char *> (ret);
134 }
135
136
137 FILE *
138 inpipebuf::init_without_shell (const char *const *argv) const
139 {
140     FILE *pipeobj = NULL;
141     int pipefd [2];
142
143     errno = 0;
144     if (::pipe (pipefd) == -1) {
145         PIPE_CTOR_FAIL("pipe");
146     }
147
148     errno = 0;
149     pid_t childpid = fork ();
150     switch (childpid) {
151         case -1: {
152             PIPE_CTOR_FAIL("fork");
153             break;
154         }
155         case 0: {
156             close (pipefd [0]);
157
158             if (dup2 (pipefd[1], STDOUT_FILENO) == -1) {
159                 PIPE_CTOR_FAIL("dup2");
160             }
161
162             if (dup2 (pipefd[1], STDERR_FILENO) == -1) {
163                 PIPE_CTOR_FAIL("dup2");
164             }
165
166             errno = 0;
167             if (execve (argv [0], const_cast <char *const *>(argv), NULL) == -1) {
168                 PIPE_CTOR_FAIL("exec");
169             }
170             break;
171         }
172         default: {
173             close (pipefd [1]);
174
175             errno = 0;
176             if ((pipeobj = fdopen (pipefd [0], "r")) == NULL) {
177                 PIPE_CTOR_FAIL("fdopen");
178             }
179             break;
180         }
181     }
182
183     return pipeobj;
184 }
185
186 inpipebuf::inpipebuf(const char *const *command)
187     : pipe (NULL) /* brr: shadowing global ident */
188     , status_set (NULL)
189     , exit_status (NULL)
190 {
191     if (command == NULL || command [0] == NULL) {
192         PIPE_CTOR_FAIL("command");
193     }
194
195     this->pipe = this->init_without_shell (command);
196
197     setg (&buffer, &buffer, &buffer);
198 }
199
200 inpipebuf::inpipebuf(const std::string& command)
201 {
202     status_set = NULL;
203     exit_status = NULL;
204
205     pipe = popen (command.c_str(), "r");
206     if (pipe == NULL)
207         throw EXCEPTION (pipestream_error, "can't open program or permission denied");
208
209     // force underflow
210     setg (&buffer, &buffer, &buffer);
211 }
212
213 inpipebuf::~inpipebuf()
214 {
215     if (pipe != NULL) {
216         int pclose_exit = pclose (pipe);
217
218         if (exit_status && pclose_exit != -1)
219         {
220             if (status_set)
221                 *status_set = true;
222             *exit_status = pclose_exit;
223         }
224
225         pipe = NULL;
226     }
227 }
228
229 /** note: exit status only available after destruction */
230 void inpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
231
232     status_set = _status_set; 
233     exit_status = _exit_status; 
234 }
235
236 inpipebuf::int_type inpipebuf::underflow()
237 {
238     if (gptr() < egptr())
239         return traits_type::to_int_type(*gptr());
240
241     buffer = fgetc (pipe);
242     if (feof (pipe))
243     {
244         // ERROR or EOF
245         return EOF;
246     }
247
248     setg (&buffer, &buffer, &buffer+sizeof(char));
249
250     return traits_type::to_int_type(*gptr());
251 }
252
253 outpipebuf::outpipebuf(const std::string& command)
254 {
255     status_set = NULL;
256     exit_status = NULL;
257
258     pipe = popen (command.c_str(), "w");
259     if (pipe == NULL)
260         throw EXCEPTION (pipestream_error, "can't open program or permission denied");
261 }
262
263 outpipebuf::~outpipebuf()
264 {
265     if (pipe != NULL) {
266         int pclose_exit = pclose (pipe);
267
268         if (exit_status && pclose_exit != -1)
269         {
270             if (status_set)
271                 *status_set = true;
272             *exit_status = pclose_exit;
273         }
274
275         pipe = NULL;
276     }
277 }
278
279 /** note: exit status only available after destruction */
280 void outpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
281
282     status_set = _status_set; 
283     exit_status = _exit_status; 
284 }
285
286 outpipebuf::int_type outpipebuf::overflow(int_type c)
287 {
288     if (c != EOF)
289     {
290         if (fputc(c,pipe)==EOF)
291             return EOF;
292     }
293     return c;
294 }
295
296 std::streamsize outpipebuf::xsputn(const char* s, std::streamsize num)
297 {
298     return fwrite(s,num,1,pipe);
299 }