allow path lookup for pipestream
[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
ad4490f1
PG
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.
f8f119bf
GE
50 * @returns the output (stdout) of the called program
51 */
9e322fb7 52template <typename CmdT>
cc917897 53std::string capture_exec(CmdT command, ExecResult &rescode,
ad4490f1
PG
54 const bool out, const bool err,
55 const bool path)
f8f119bf
GE
56{
57 std::string output;
58
1d7539d5 59 bool exit_set = false;
f8f119bf
GE
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 {
ad4490f1 69 inpipestream ips(command, out, err, path);
f8f119bf
GE
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
cc917897
PG
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 */
9e322fb7 116std::string capture_exec (const std::string &command, ExecResult &res)
ad4490f1 117{ return capture_exec<const std::string &>(command, res, true, false, false); }
cc917897
PG
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.
ad4490f1 131 * @param path Wether to look up the executable in \c $PATH.
cc917897
PG
132 *
133 * @returns Captured output, combined into one string.
134 */
135std::string capture_exec (const char *const *command, ExecResult &res,
ad4490f1
PG
136 const bool out, const bool err, const bool path)
137{ return capture_exec<const char *const *>(command, res, out, err, path); }
cc917897
PG
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.
ad4490f1 150 * @param path Wether to look up the executable in \c $PATH.
cc917897
PG
151 *
152 * @returns Captured output, combined into one string.
153 */
154std::string capture_exec (const std::vector<std::string> &command, ExecResult &res,
ad4490f1 155 const bool out, const bool err, const bool path)
c2c29997 156{
cc917897 157 return capture_exec<const std::vector<std::string> &>
ad4490f1 158 (command, res, out, err, path);
c2c29997
PG
159}
160
9e322fb7
PG
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
cc917897
PG
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 */
9e322fb7
PG
181static boost::shared_array <char *>
182mk_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
cc917897
PG
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 */
17b459b3 215std::pair <pid_t, FILE *>
cc917897
PG
216inpipebuf::init_without_shell (const char *const *argv,
217 const bool out,
ad4490f1
PG
218 const bool err,
219 const bool path) const
9e322fb7
PG
220{
221 FILE *pipeobj = NULL;
222 int pipefd [2];
223
cc917897
PG
224 if (!out && !err) {
225 errno = EINVAL;
226 PIPE_CTOR_FAIL("ctor");
227 }
228
9e322fb7
PG
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
cc917897
PG
244 if (!out) {
245 close (STDOUT_FILENO);
246 } else if (dup2 (pipefd[1], STDOUT_FILENO) == -1) {
ad4490f1
PG
247 fprintf(stderr, "dup2/stdout: %m\n");
248 exit(EXIT_FAILURE);
9e322fb7
PG
249 }
250
cc917897
PG
251 if (!err) {
252 close (STDERR_FILENO);
253 } else if (dup2 (pipefd[1], STDERR_FILENO) == -1) {
ad4490f1
PG
254 fprintf(stderr, "dup2/stderr: %m\n");
255 exit(EXIT_FAILURE);
9e322fb7
PG
256 }
257
17b459b3
PG
258 close (pipefd [1]);
259
9e322fb7 260 errno = 0;
ad4490f1
PG
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);
9e322fb7 265 }
ad4490f1 266 exit(EXIT_FAILURE);
9e322fb7
PG
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
17b459b3 280 return std::make_pair (childpid, pipeobj);
9e322fb7
PG
281}
282
cc917897
PG
283inpipebuf::inpipebuf(const char *const *command,
284 const bool out,
ad4490f1
PG
285 const bool err,
286 const bool path)
9e322fb7 287 : pipe (NULL) /* brr: shadowing global ident */
17b459b3 288 , pid (-1)
9e322fb7
PG
289 , status_set (NULL)
290 , exit_status (NULL)
291{
292 if (command == NULL || command [0] == NULL) {
293 PIPE_CTOR_FAIL("command");
294 }
295
ad4490f1
PG
296 std::pair <pid_t, FILE *> tmp =
297 this->init_without_shell (command, out, err, path);
17b459b3
PG
298 this->pid = tmp.first; /* no std::tie :/ */
299 this->pipe = tmp.second;
9e322fb7
PG
300
301 setg (&buffer, &buffer, &buffer);
302}
303
cc917897
PG
304inpipebuf::inpipebuf(const std::vector<std::string> &command,
305 const bool out,
ad4490f1
PG
306 const bool err,
307 const bool path)
c2c29997 308 : pipe (NULL) /* brr: shadowing global ident */
17b459b3 309 , pid (-1)
c2c29997
PG
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
ad4490f1
PG
322 std::pair <pid_t, FILE *> tmp =
323 this->init_without_shell (argv.get (), out, err, path);
17b459b3
PG
324 this->pid = tmp.first;
325 this->pipe = tmp.second;
c2c29997
PG
326
327 setg (&buffer, &buffer, &buffer);
328}
329
cc917897
PG
330inpipebuf::inpipebuf(const std::string& command,
331 const bool _ignored_out,
ad4490f1
PG
332 const bool _ignored_err,
333 const bool _ignored_path)
17b459b3
PG
334 : pid (-1)
335 , status_set (NULL)
336 , exit_status (NULL)
7e606af5 337{
7e606af5
GE
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
346inpipebuf::~inpipebuf()
347{
348 if (pipe != NULL) {
17b459b3 349 int status;
7e606af5 350
17b459b3 351 if (this->pid == -1)
d00589a0 352 {
17b459b3
PG
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 }
7e606af5
GE
390 }
391
392 pipe = NULL;
393 }
394}
395
396/** note: exit status only available after destruction */
397void inpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
398{
399 status_set = _status_set;
400 exit_status = _exit_status;
401}
402
403inpipebuf::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
7e606af5
GE
420outpipebuf::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
430outpipebuf::~outpipebuf()
431{
432 if (pipe != NULL) {
433 int pclose_exit = pclose (pipe);
434
d00589a0
GE
435 if (exit_status && pclose_exit != -1)
436 {
437 if (status_set)
438 *status_set = true;
7e606af5
GE
439 *exit_status = pclose_exit;
440 }
441
442 pipe = NULL;
443 }
444}
445
446/** note: exit status only available after destruction */
447void outpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
448{
449 status_set = _status_set;
450 exit_status = _exit_status;
451}
452
453outpipebuf::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
463std::streamsize outpipebuf::xsputn(const char* s, std::streamsize num)
464{
465 return fwrite(s,num,1,pipe);
466}