add pipestream unit tests for execution failure
[libi2ncommon] / src / pipestream.cpp
CommitLineData
7e606af5
GE
1 /*
2The software in this package is distributed under the GNU General
3Public License version 2 (with a special exception described below).
4
5A copy of GNU General Public License (GPL) is included in this distribution,
6in the file COPYING.GPL.
7
8As a special exception, if other files instantiate templates or use macros
9or inline functions from this file, or you compile this file and link it
10with other works to produce a work based on this file, this file
11does not by itself cause the resulting work to be covered
12by the GNU General Public License.
13
14However the source code for this file must still be made available
15in accordance with section (3) of the GNU General Public License.
16
17This exception does not invalidate any other reasons why a work based
18on 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
9e322fb7 27#include <errno.h>
7e606af5 28#include <stdio.h>
9e322fb7 29#include <string.h>
f8f119bf 30#include <sys/wait.h>
9e322fb7 31#include <unistd.h>
7e606af5 32
7e606af5
GE
33#include <streambuf>
34#include <istream>
35#include <ostream>
36#include <cstdio>
9e322fb7
PG
37#include <boost/foreach.hpp>
38#include <boost/shared_array.hpp>
7e606af5
GE
39
40#include "exception.hxx"
9e322fb7 41#include "stringfunc.hxx"
7e606af5
GE
42#include "pipestream.hxx"
43
f8f119bf
GE
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 */
9e322fb7 49template <typename CmdT>
cc917897
PG
50std::string capture_exec(CmdT command, ExecResult &rescode,
51 const bool out, const bool err)
f8f119bf
GE
52{
53 std::string output;
54
1d7539d5 55 bool exit_set = false;
f8f119bf
GE
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 {
cc917897 65 inpipestream ips(command, out, err);
f8f119bf
GE
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
cc917897
PG
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 */
9e322fb7 112std::string capture_exec (const std::string &command, ExecResult &res)
cc917897
PG
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 */
130std::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 */
148std::string capture_exec (const std::vector<std::string> &command, ExecResult &res,
149 const bool out, const bool err)
c2c29997 150{
cc917897
PG
151 return capture_exec<const std::vector<std::string> &>
152 (command, res, out, err);
c2c29997
PG
153}
154
9e322fb7
PG
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
cc917897
PG
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 */
9e322fb7
PG
175static boost::shared_array <char *>
176mk_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
cc917897
PG
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 */
9e322fb7 210FILE *
cc917897
PG
211inpipebuf::init_without_shell (const char *const *argv,
212 const bool out,
213 const bool err) const
9e322fb7
PG
214{
215 FILE *pipeobj = NULL;
216 int pipefd [2];
217
cc917897
PG
218 if (!out && !err) {
219 errno = EINVAL;
220 PIPE_CTOR_FAIL("ctor");
221 }
222
9e322fb7
PG
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
cc917897
PG
238 if (!out) {
239 close (STDOUT_FILENO);
240 } else if (dup2 (pipefd[1], STDOUT_FILENO) == -1) {
241 PIPE_CTOR_FAIL("dup2/stdout");
9e322fb7
PG
242 }
243
cc917897
PG
244 if (!err) {
245 close (STDERR_FILENO);
246 } else if (dup2 (pipefd[1], STDERR_FILENO) == -1) {
247 PIPE_CTOR_FAIL("dup2/stderr");
9e322fb7
PG
248 }
249
250 errno = 0;
251 if (execve (argv [0], const_cast <char *const *>(argv), NULL) == -1) {
252 PIPE_CTOR_FAIL("exec");
253 }
254 break;
255 }
256 default: {
257 close (pipefd [1]);
258
259 errno = 0;
260 if ((pipeobj = fdopen (pipefd [0], "r")) == NULL) {
261 PIPE_CTOR_FAIL("fdopen");
262 }
263 break;
264 }
265 }
266
267 return pipeobj;
268}
269
cc917897
PG
270inpipebuf::inpipebuf(const char *const *command,
271 const bool out,
272 const bool err)
9e322fb7
PG
273 : pipe (NULL) /* brr: shadowing global ident */
274 , status_set (NULL)
275 , exit_status (NULL)
276{
277 if (command == NULL || command [0] == NULL) {
278 PIPE_CTOR_FAIL("command");
279 }
280
cc917897 281 this->pipe = this->init_without_shell (command, out, err);
9e322fb7
PG
282
283 setg (&buffer, &buffer, &buffer);
284}
285
cc917897
PG
286inpipebuf::inpipebuf(const std::vector<std::string> &command,
287 const bool out,
288 const bool err)
c2c29997
PG
289 : pipe (NULL) /* brr: shadowing global ident */
290 , status_set (NULL)
291 , exit_status (NULL)
292{
293 if (command.empty ()) {
294 PIPE_CTOR_FAIL("command");
295 }
296
297 const boost::shared_array <char *> argv = mk_argv (command);
298 if (!argv) {
299 PIPE_CTOR_FAIL("malloc");
300 }
301
cc917897 302 this->pipe = this->init_without_shell (argv.get (), out, err);
c2c29997
PG
303
304 setg (&buffer, &buffer, &buffer);
305}
306
cc917897
PG
307inpipebuf::inpipebuf(const std::string& command,
308 const bool _ignored_out,
309 const bool _ignored_err)
7e606af5
GE
310{
311 status_set = NULL;
312 exit_status = NULL;
313
314 pipe = popen (command.c_str(), "r");
315 if (pipe == NULL)
316 throw EXCEPTION (pipestream_error, "can't open program or permission denied");
317
318 // force underflow
319 setg (&buffer, &buffer, &buffer);
320}
321
322inpipebuf::~inpipebuf()
323{
324 if (pipe != NULL) {
325 int pclose_exit = pclose (pipe);
326
d00589a0
GE
327 if (exit_status && pclose_exit != -1)
328 {
329 if (status_set)
330 *status_set = true;
7e606af5
GE
331 *exit_status = pclose_exit;
332 }
333
334 pipe = NULL;
335 }
336}
337
338/** note: exit status only available after destruction */
339void inpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
340{
341 status_set = _status_set;
342 exit_status = _exit_status;
343}
344
345inpipebuf::int_type inpipebuf::underflow()
346{
347 if (gptr() < egptr())
348 return traits_type::to_int_type(*gptr());
349
350 buffer = fgetc (pipe);
351 if (feof (pipe))
352 {
353 // ERROR or EOF
354 return EOF;
355 }
356
357 setg (&buffer, &buffer, &buffer+sizeof(char));
358
359 return traits_type::to_int_type(*gptr());
360}
361
7e606af5
GE
362outpipebuf::outpipebuf(const std::string& command)
363{
364 status_set = NULL;
365 exit_status = NULL;
366
367 pipe = popen (command.c_str(), "w");
368 if (pipe == NULL)
369 throw EXCEPTION (pipestream_error, "can't open program or permission denied");
370}
371
372outpipebuf::~outpipebuf()
373{
374 if (pipe != NULL) {
375 int pclose_exit = pclose (pipe);
376
d00589a0
GE
377 if (exit_status && pclose_exit != -1)
378 {
379 if (status_set)
380 *status_set = true;
7e606af5
GE
381 *exit_status = pclose_exit;
382 }
383
384 pipe = NULL;
385 }
386}
387
388/** note: exit status only available after destruction */
389void outpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
390{
391 status_set = _status_set;
392 exit_status = _exit_status;
393}
394
395outpipebuf::int_type outpipebuf::overflow(int_type c)
396{
397 if (c != EOF)
398 {
399 if (fputc(c,pipe)==EOF)
400 return EOF;
401 }
402 return c;
403}
404
405std::streamsize outpipebuf::xsputn(const char* s, std::streamsize num)
406{
407 return fwrite(s,num,1,pipe);
408}