]> git.ipfire.org Git - thirdparty/pdns.git/blob - modules/pipebackend/coprocess.cc
dnsdist: Handle EAGAIN when reading from the non-blocking TCP pipe
[thirdparty/pdns.git] / modules / pipebackend / coprocess.cc
1 /*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include "coprocess.hh"
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <string>
29 #include <errno.h>
30 #include <signal.h>
31 #include <string.h>
32 #include <sys/types.h>
33 #include <sys/wait.h>
34 #include "pdns/utility.hh"
35 #include <sys/un.h>
36 #include "pdns/misc.hh"
37 #include "pdns/pdnsexception.hh"
38 #include <sys/stat.h>
39 #include <unistd.h>
40 #include <boost/algorithm/string.hpp>
41 #include <vector>
42
43 CoProcess::CoProcess(const string &command,int timeout, int infd, int outfd)
44 {
45 vector <string> v;
46 split(v, command, is_any_of(" "));
47
48 std::vector<const char *>argv(v.size()+1);
49 argv[v.size()]=0;
50
51 for (size_t n = 0; n < v.size(); n++)
52 argv[n]=v[n].c_str();
53 // we get away with not copying since nobody resizes v
54 launch(argv.data(), timeout, infd, outfd);
55 }
56
57 void CoProcess::launch(const char **argv, int timeout, int infd, int outfd)
58 {
59 d_timeout=timeout;
60 d_infd=infd;
61 d_outfd=outfd;
62
63 signal(SIGPIPE, SIG_IGN);
64
65 if(access(argv[0],X_OK)) // check before fork so we can throw
66 throw PDNSException("Command '"+string(argv[0])+"' cannot be executed: "+stringerror());
67
68 if(pipe(d_fd1)<0 || pipe(d_fd2)<0)
69 throw PDNSException("Unable to open pipe for coprocess: "+string(strerror(errno)));
70
71 if((d_pid=fork())<0)
72 throw PDNSException("Unable to fork for coprocess: "+stringerror());
73 else if(d_pid>0) { // parent speaking
74 close(d_fd1[0]);
75 setCloseOnExec(d_fd1[1]);
76 close(d_fd2[1]);
77 setCloseOnExec(d_fd2[0]);
78 if(!(d_fp=fdopen(d_fd2[0],"r")))
79 throw PDNSException("Unable to associate a file pointer with pipe: "+stringerror());
80 if( d_timeout)
81 setbuf(d_fp,0); // no buffering please, confuses select
82 }
83 else if(!d_pid) { // child
84 signal(SIGCHLD, SIG_DFL); // silence a warning from perl
85 close(d_fd1[1]);
86 close(d_fd2[0]);
87
88 if(d_fd1[0]!= infd) {
89 dup2(d_fd1[0], infd);
90 close(d_fd1[0]);
91 }
92
93 if(d_fd2[1]!= outfd) {
94 dup2(d_fd2[1], outfd);
95 close(d_fd2[1]);
96 }
97
98 // stdin & stdout are now connected, fire up our coprocess!
99
100 if(execv(argv[0], const_cast<char * const *>(argv))<0) // now what
101 exit(123);
102
103 /* not a lot we can do here. We shouldn't return because that will leave a forked process around.
104 no way to log this either - only thing we can do is make sure that our parent catches this soonest! */
105 }
106 }
107
108 CoProcess::~CoProcess()
109 {
110 int status;
111 if(!waitpid(d_pid, &status, WNOHANG)) {
112 kill(d_pid, 9);
113 waitpid(d_pid, &status, 0);
114 }
115
116 close(d_fd1[1]);
117 fclose(d_fp);
118 }
119
120 void CoProcess::checkStatus()
121 {
122 int status;
123 int ret=waitpid(d_pid, &status, WNOHANG);
124 if(ret<0)
125 throw PDNSException("Unable to ascertain status of coprocess "+itoa(d_pid)+" from "+itoa(getpid())+": "+string(strerror(errno)));
126 else if(ret) {
127 if(WIFEXITED(status)) {
128 int exitStatus=WEXITSTATUS(status);
129 throw PDNSException("Coprocess exited with code "+itoa(exitStatus));
130 }
131 if(WIFSIGNALED(status)) {
132 int sig=WTERMSIG(status);
133 string reason="CoProcess died on receiving signal "+itoa(sig);
134 #ifdef WCOREDUMP
135 if(WCOREDUMP(status))
136 reason+=". Dumped core";
137 #endif
138
139 throw PDNSException(reason);
140 }
141 }
142 }
143
144 void CoProcess::send(const string &snd)
145 {
146 checkStatus();
147 string line(snd);
148 line.append(1,'\n');
149
150 unsigned int sent=0;
151 int bytes;
152
153 // writen routine - socket may not accept al data in one go
154 while(sent<line.size()) {
155 bytes=write(d_fd1[1],line.c_str()+sent,line.length()-sent);
156 if(bytes<0)
157 throw PDNSException("Writing to coprocess failed: "+string(strerror(errno)));
158
159 sent+=bytes;
160 }
161 }
162
163 void CoProcess::receive(string &receive)
164 {
165 receive.clear();
166
167 if(d_timeout) {
168 struct timeval tv;
169 tv.tv_sec=d_timeout/1000;
170 tv.tv_usec=(d_timeout % 1000) * 1000;
171
172 fd_set rds;
173 FD_ZERO(&rds);
174 FD_SET(fileno(d_fp),&rds);
175 int ret=select(fileno(d_fp)+1,&rds,0,0,&tv);
176 if(ret<0)
177 throw PDNSException("Error waiting on data from coprocess: "+stringerror());
178 if(!ret)
179 throw PDNSException("Timeout waiting for data from coprocess");
180 }
181
182 if(!stringfgets(d_fp, receive))
183 throw PDNSException("Child closed pipe");
184
185 trim_right(receive);
186 }
187
188 void CoProcess::sendReceive(const string &snd, string &rcv)
189 {
190 checkStatus();
191 send(snd);
192 receive(rcv);
193
194 }
195
196 UnixRemote::UnixRemote(const string& path, int timeout)
197 {
198 d_fd = socket(AF_UNIX, SOCK_STREAM, 0);
199 if(d_fd < 0)
200 throw PDNSException("Unable to create UNIX domain socket: "+string(strerror(errno)));
201
202 struct sockaddr_un remote;
203 if (makeUNsockaddr(path, &remote))
204 throw PDNSException("Unable to create UNIX domain socket: Path '"+path+"' is not a valid UNIX socket path.");
205
206 // fcntl(fd, F_SETFL, O_NONBLOCK, &sock);
207
208 if(connect(d_fd, (struct sockaddr*)&remote, sizeof(remote)) < 0)
209 unixDie("Unable to connect to remote '"+path+"' using UNIX domain socket");
210
211 d_fp = fdopen(d_fd, "r");
212 }
213
214 UnixRemote::~UnixRemote()
215 {
216 fclose(d_fp);
217 }
218
219 void UnixRemote::send(const string& line)
220 {
221 string nline(line);
222 nline.append(1, '\n');
223 writen2(d_fd, nline);
224 }
225
226 void UnixRemote::receive(string& line)
227 {
228 line.clear();
229 stringfgets(d_fp, line);
230 trim_right(line);
231 }
232
233 void UnixRemote::sendReceive(const string &snd, string &rcv)
234 {
235 // checkStatus();
236 send(snd);
237 receive(rcv);
238 }
239
240 bool isUnixSocket(const string& fname)
241 {
242 struct stat st;
243 if(stat(fname.c_str(), &st) < 0)
244 return false; // not a unix socket in any case ;-)
245
246 return (st.st_mode & S_IFSOCK) == S_IFSOCK;
247 }
248
249
250 #ifdef TESTDRIVER
251 main()
252 {
253 try {
254 CoProcess cp("./irc.pl");
255 string reply;
256 cp.sendReceive("www.trilab.com", reply);
257 cout<<"Answered: '"<<reply<<"'"<<endl;
258 }
259 catch(PDNSException &ae) {
260 cerr<<ae.reason<<endl;
261 }
262
263 }
264 #endif