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