implement pipe_to_string() and use it to shorten the test case sourcecode
[libi2ncommon] / src / pipestream.hxx
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.hxx  -  C++ streambuffer wrapper 
22                              -------------------
23     begin                : Thu Dec 27 2001
24     copyright            : (C) 2001 by Intra2net AG
25  ***************************************************************************/
26
27 #ifndef _PIPESTREAM
28 #define _PIPESTREAM
29
30 #include <stdio.h>
31
32 #include <string>
33 #include <streambuf>
34 #include <istream>
35 #include <ostream>
36 #include <cstdio>
37
38 #include "exception.hxx"
39
40 /** @brief runs command and provides buffered input for it through pipe
41  *
42  * opens pipe to command using popen; exit status available after destruction
43  * (use WEXITSTATUS to get the "regular" return code (lowest byte))
44  *
45  * ATTENTION: A lot of mysterious STL bugs occured
46  *            with a "real" buffer (buffer larger than 1 byte and up to 100 bytes)
47  *            -> Keep it slow and working!
48  */
49 class inpipebuf : public std::streambuf
50 {
51 protected:
52     char buffer;
53     FILE *pipe;
54
55     // "callback" variables for destructor to store exit status
56     bool *status_set;
57     int *exit_status;
58
59 public:
60     inpipebuf(const std::string& command)
61     {
62         status_set = NULL;
63         exit_status = NULL;
64
65         pipe = popen (command.c_str(), "r");
66         if (pipe == NULL)
67             throw EXCEPTION (pipestream_error, "can't open program or permission denied");
68
69         // force underflow
70         setg (&buffer, &buffer, &buffer);
71     }
72
73     ~inpipebuf()
74     {
75         if (pipe != NULL) {
76             int pclose_exit = pclose (pipe);
77
78             if (exit_status && pclose_exit != -1) {
79                 *status_set = true;
80                 *exit_status = pclose_exit;
81             }
82
83             pipe = NULL;
84         }
85     }
86
87     /** note: exit status only available after destruction */
88     void store_exit_status(bool *_status_set, int *_exit_status)
89     { status_set = _status_set; exit_status = _exit_status; }
90
91 protected:
92     virtual int_type underflow()
93     {
94         if (gptr() < egptr())
95             return traits_type::to_int_type(*gptr());
96
97         buffer = fgetc (pipe);
98         if (feof (pipe))
99         {
100             // ERROR or EOF
101             return EOF;
102         }
103
104         setg (&buffer, &buffer, &buffer+sizeof(char));
105
106         return traits_type::to_int_type(*gptr());
107     }
108 };
109
110 /** @brief stream around inpipebuf -- see comment there */
111 class inpipestream : public std::istream
112 {
113 protected:
114     inpipebuf buf;
115
116 public:
117     inpipestream(const std::string& command)
118             : std::istream(&buf), buf(command)
119     {}
120
121     void store_exit_status(bool *_status_set, int *_exit_status)
122     { buf.store_exit_status(_status_set, _exit_status); }
123 };
124
125 /** @brief runs command and returns it's output as string
126  *  @param command the full command with all parameters
127  *  @param exit_status the full exit status, use WEXITSTATUS to get the "regular" return code
128  *  @returns the output (stderr) of the called program
129  */
130 std::string pipe_to_string(const std::string& command, std::string *error=NULL, int *_exit_status=NULL)
131 {
132     std::string result;
133     bool exit_set;
134
135     try
136     {
137         inpipestream ips(command);
138
139         ips.store_exit_status(&exit_set, _exit_status);
140
141         char buffer[2048];
142         while (ips.good())
143         {
144             ips.read(buffer, sizeof(buffer));
145             result.append(buffer, ips.gcount());
146         }
147     }
148     catch (pipestream_error &e)
149     {
150         if (error)
151             *error=e.what();
152         return "";
153     }
154     catch(...)
155     {
156         throw;
157     }
158
159     return result;
160 }
161
162 /** @brief runs command and provides buffered ouptput from it through pipe
163  *
164  * opens pipe to command using popen; exit status available after destruction
165  * (use WEXITSTATUS to get the "regular" return code (lowest byte))
166  */
167 class outpipebuf : public std::streambuf
168 {
169 protected:
170     FILE *pipe;
171
172     // "callback" variables for destructor to store exit status
173     bool *status_set;
174     int *exit_status;
175
176 public:
177     outpipebuf(const std::string& command)
178     {
179         status_set = NULL;
180         exit_status = NULL;
181
182         pipe = popen (command.c_str(), "w");
183         if (pipe == NULL)
184             throw EXCEPTION (pipestream_error, "can't open program or permission denied");
185     }
186
187     ~outpipebuf()
188     {
189         if (pipe != NULL) {
190             int pclose_exit = pclose (pipe);
191
192             if (exit_status && pclose_exit != -1) {
193                 *status_set = true;
194                 *exit_status = pclose_exit;
195             }
196
197             pipe = NULL;
198         }
199     }
200
201     /** note: exit status only available after destruction */
202     void store_exit_status(bool *_status_set, int *_exit_status)
203     { status_set = _status_set; exit_status = _exit_status; }
204
205 protected:
206     virtual int_type overflow(int_type c)
207     {
208         if (c != EOF)
209         {
210             if (fputc(c,pipe)==EOF)
211                 return EOF;
212         }
213         return c;
214     }
215
216     virtual std::streamsize xsputn(const char* s, std::streamsize num)
217     {
218         return fwrite(s,num,1,pipe);
219     }
220 };
221
222
223 /** @brief stream around outpipebuf -- see comment there */
224 class outpipestream : public std::ostream
225 {
226 protected:
227     outpipebuf buf;
228 public:
229     outpipestream(const std::string& command)
230             : std::ostream(&buf), buf(command)
231     {}
232
233     void store_exit_status(bool *_status_set, int *_exit_status)
234     { buf.store_exit_status(_status_set, _exit_status); }
235 };
236
237 #endif