]> git.ipfire.org Git - thirdparty/pdns.git/blob - modules/remotebackend/pipeconnector.cc
b7fd605ee525793079895461d0e8a8ce7165be41
[thirdparty/pdns.git] / modules / remotebackend / pipeconnector.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 "remotebackend.hh"
26
27 PipeConnector::PipeConnector(std::map<std::string,std::string> optionsMap) {
28 if (optionsMap.count("command") == 0) {
29 L<<Logger::Error<<"Cannot find 'command' option in connection string"<<endl;
30 throw PDNSException();
31 }
32 this->command = optionsMap.find("command")->second;
33 this->options = optionsMap;
34 d_timeout=2000;
35
36 if (optionsMap.find("timeout") != optionsMap.end()) {
37 d_timeout = std::stoi(optionsMap.find("timeout")->second);
38 }
39
40 d_pid = -1;
41 d_fp = NULL;
42 d_fd1[0] = d_fd1[1] = -1;
43 d_fd2[0] = d_fd2[1] = -1;
44 }
45
46 PipeConnector::~PipeConnector(){
47 int status;
48 // just in case...
49 if (d_pid == -1) return;
50
51 if(!waitpid(d_pid, &status, WNOHANG)) {
52 kill(d_pid, 9);
53 waitpid(d_pid, &status, 0);
54 }
55
56 close(d_fd1[1]);
57 if (d_fp != NULL) fclose(d_fp);
58 }
59
60 void PipeConnector::launch() {
61 // no relaunch
62 if (d_pid > 0 && checkStatus()) return;
63
64 std::vector <std::string> v;
65 split(v, command, is_any_of(" "));
66
67 std::vector<const char *>argv(v.size()+1);
68 argv[v.size()]=0;
69
70 for (size_t n = 0; n < v.size(); n++)
71 argv[n]=v[n].c_str();
72
73 signal(SIGPIPE, SIG_IGN);
74
75 if(access(argv[0],X_OK)) // check before fork so we can throw
76 throw PDNSException("Command '"+string(argv[0])+"' cannot be executed: "+stringerror());
77
78 if(pipe(d_fd1)<0 || pipe(d_fd2)<0)
79 throw PDNSException("Unable to open pipe for coprocess: "+string(strerror(errno)));
80
81 if((d_pid=fork())<0)
82 throw PDNSException("Unable to fork for coprocess: "+stringerror());
83 else if(d_pid>0) { // parent speaking
84 close(d_fd1[0]);
85 setCloseOnExec(d_fd1[1]);
86 close(d_fd2[1]);
87 setCloseOnExec(d_fd2[0]);
88 if(!(d_fp=fdopen(d_fd2[0],"r")))
89 throw PDNSException("Unable to associate a file pointer with pipe: "+stringerror());
90 if (d_timeout)
91 setbuf(d_fp,0); // no buffering please, confuses select
92 }
93 else if(!d_pid) { // child
94 signal(SIGCHLD, SIG_DFL); // silence a warning from perl
95 close(d_fd1[1]);
96 close(d_fd2[0]);
97
98 if(d_fd1[0]!= 0) {
99 dup2(d_fd1[0], 0);
100 close(d_fd1[0]);
101 }
102
103 if(d_fd2[1]!= 1) {
104 dup2(d_fd2[1], 1);
105 close(d_fd2[1]);
106 }
107
108 // stdin & stdout are now connected, fire up our coprocess!
109
110 if(execv(argv[0], const_cast<char * const *>(argv.data()))<0) // now what
111 exit(123);
112
113 /* not a lot we can do here. We shouldn't return because that will leave a forked process around.
114 no way to log this either - only thing we can do is make sure that our parent catches this soonest! */
115 }
116
117 Json::array parameters;
118 Json msg = Json(Json::object{
119 { "method", "initialize" },
120 { "parameters", Json(options) },
121 });
122
123 this->send(msg);
124 msg = nullptr;
125 if (this->recv(msg)==false) {
126 L<<Logger::Error<<"Failed to initialize coprocess"<<std::endl;
127 }
128 }
129
130 int PipeConnector::send_message(const Json& input)
131 {
132 auto line = input.dump();
133 launch();
134
135 line.append(1,'\n');
136
137 unsigned int sent=0;
138 int bytes;
139
140 // writen routine - socket may not accept al data in one go
141 while(sent<line.size()) {
142 bytes=write(d_fd1[1],line.c_str()+sent,line.length()-sent);
143 if(bytes<0)
144 throw PDNSException("Writing to coprocess failed: "+std::string(strerror(errno)));
145
146 sent+=bytes;
147 }
148 return sent;
149 }
150
151 int PipeConnector::recv_message(Json& output)
152 {
153 std::string receive;
154 std::string err;
155 std::string s_output;
156 launch();
157
158 while(1) {
159 receive.clear();
160 if(d_timeout) {
161 struct timeval tv;
162 tv.tv_sec = d_timeout/1000;
163 tv.tv_usec = (d_timeout % 1000) * 1000;
164 fd_set rds;
165 FD_ZERO(&rds);
166 FD_SET(fileno(d_fp),&rds);
167 int ret=select(fileno(d_fp)+1,&rds,0,0,&tv);
168 if(ret<0)
169 throw PDNSException("Error waiting on data from coprocess: "+stringerror());
170 if(!ret)
171 throw PDNSException("Timeout waiting for data from coprocess");
172 }
173
174 if(!stringfgets(d_fp, receive))
175 throw PDNSException("Child closed pipe");
176
177 s_output.append(receive);
178 // see if it can be parsed
179 output = Json::parse(s_output, err);
180 if (output != nullptr) return s_output.size();
181 }
182 return 0;
183 }
184
185 bool PipeConnector::checkStatus()
186 {
187 int status;
188 int ret=waitpid(d_pid, &status, WNOHANG);
189 if(ret<0)
190 throw PDNSException("Unable to ascertain status of coprocess "+itoa(d_pid)+" from "+itoa(getpid())+": "+string(strerror(errno)));
191 else if(ret) {
192 if(WIFEXITED(status)) {
193 int exitStatus=WEXITSTATUS(status);
194 throw PDNSException("Coprocess exited with code "+itoa(exitStatus));
195 }
196 if(WIFSIGNALED(status)) {
197 int sig=WTERMSIG(status);
198 string reason="CoProcess died on receiving signal "+itoa(sig);
199 #ifdef WCOREDUMP
200 if(WCOREDUMP(status))
201 reason+=". Dumped core";
202 #endif
203
204 throw PDNSException(reason);
205 }
206 }
207 return true;
208 }