add shell-free 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
47 * @returns the output (stdout) of the called program
48 */
9e322fb7
PG
49template <typename CmdT>
50std::string capture_exec(CmdT command, ExecResult &rescode)
f8f119bf
GE
51{
52 std::string output;
53
1d7539d5 54 bool exit_set = false;
f8f119bf
GE
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
9e322fb7
PG
97std::string capture_exec (const std::string &command, ExecResult &res)
98{ return capture_exec<const std::string &>(command, res); }
99
100std::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
111static boost::shared_array <char *>
112mk_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
137FILE *
138inpipebuf::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
186inpipebuf::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
7e606af5
GE
200inpipebuf::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
213inpipebuf::~inpipebuf()
214{
215 if (pipe != NULL) {
216 int pclose_exit = pclose (pipe);
217
d00589a0
GE
218 if (exit_status && pclose_exit != -1)
219 {
220 if (status_set)
221 *status_set = true;
7e606af5
GE
222 *exit_status = pclose_exit;
223 }
224
225 pipe = NULL;
226 }
227}
228
229/** note: exit status only available after destruction */
230void inpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
231{
232 status_set = _status_set;
233 exit_status = _exit_status;
234}
235
236inpipebuf::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
7e606af5
GE
253outpipebuf::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
263outpipebuf::~outpipebuf()
264{
265 if (pipe != NULL) {
266 int pclose_exit = pclose (pipe);
267
d00589a0
GE
268 if (exit_status && pclose_exit != -1)
269 {
270 if (status_set)
271 *status_set = true;
7e606af5
GE
272 *exit_status = pclose_exit;
273 }
274
275 pipe = NULL;
276 }
277}
278
279/** note: exit status only available after destruction */
280void outpipebuf::store_exit_status(bool *_status_set, int *_exit_status)
281{
282 status_set = _status_set;
283 exit_status = _exit_status;
284}
285
286outpipebuf::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
296std::streamsize outpipebuf::xsputn(const char* s, std::streamsize num)
297{
298 return fwrite(s,num,1,pipe);
299}