]> git.ipfire.org Git - thirdparty/pdns.git/blame - modules/pipebackend/coprocess.cc
clang-tidy: modernize deprecated header invarious places
[thirdparty/pdns.git] / modules / pipebackend / coprocess.cc
CommitLineData
12471842
PL
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 */
870a0fe4
AT
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
8bd743a8
BH
25#include "coprocess.hh"
26#include <stdlib.h>
27#include <unistd.h>
28#include <string>
dc6aa7f5
AV
29#include <cerrno>
30#include <csignal>
8bd743a8
BH
31#include <string.h>
32#include <sys/types.h>
33#include <sys/wait.h>
37079a8e 34#include "pdns/utility.hh"
2f2b0145 35#include <sys/un.h>
21e99384
RK
36#include "pdns/misc.hh"
37#include "pdns/pdnsexception.hh"
2f2b0145
BH
38#include <sys/stat.h>
39#include <unistd.h>
40#include <boost/algorithm/string.hpp>
41#include <vector>
8bd743a8 42
ff05c7e1
O
43CoProcess::CoProcess(const string& command, int timeout, int infd, int outfd) :
44 d_infd(infd), d_outfd(outfd), d_timeout(timeout)
8bd743a8 45{
dc593046 46 split(d_params, command, boost::is_any_of(" "));
8bd743a8 47
ff05c7e1
O
48 d_argv.resize(d_params.size() + 1);
49 d_argv[d_params.size()] = nullptr;
8bd743a8 50
214fd99e 51 for (size_t n = 0; n < d_params.size(); n++) {
ff05c7e1 52 d_argv[n] = d_params[n].c_str();
214fd99e 53 }
bc058b8b 54 d_pid = 0;
8bd743a8
BH
55}
56
214fd99e 57void CoProcess::launch()
8bd743a8 58{
8bd743a8
BH
59 signal(SIGPIPE, SIG_IGN);
60
ff05c7e1
O
61 if (access(d_argv[0], X_OK)) // check before fork so we can throw
62 throw PDNSException("Command '" + string(d_argv[0]) + "' cannot be executed: " + stringerror());
8bd743a8 63
ff05c7e1
O
64 if (pipe(d_fd1) < 0 || pipe(d_fd2) < 0)
65 throw PDNSException("Unable to open pipe for coprocess: " + string(strerror(errno)));
8bd743a8 66
ff05c7e1
O
67 if ((d_pid = fork()) < 0)
68 throw PDNSException("Unable to fork for coprocess: " + stringerror());
69 else if (d_pid > 0) { // parent speaking
214fd99e
RG
70 // no need to keep this around
71 d_argv.clear();
8bd743a8 72 close(d_fd1[0]);
bc886258 73 setCloseOnExec(d_fd1[1]);
8bd743a8 74 close(d_fd2[1]);
bc886258 75 setCloseOnExec(d_fd2[0]);
214fd99e
RG
76
77 if (d_timeout) {
78 setNonBlocking(d_fd2[0]);
79 }
8bd743a8 80 }
ff05c7e1 81 else if (!d_pid) { // child
fb44deff 82 signal(SIGCHLD, SIG_DFL); // silence a warning from perl
8bd743a8
BH
83 close(d_fd1[1]);
84 close(d_fd2[0]);
85
ff05c7e1 86 if (d_fd1[0] != d_infd) {
214fd99e 87 dup2(d_fd1[0], d_infd);
8bd743a8
BH
88 close(d_fd1[0]);
89 }
90
ff05c7e1 91 if (d_fd2[1] != d_outfd) {
214fd99e 92 dup2(d_fd2[1], d_outfd);
8bd743a8
BH
93 close(d_fd2[1]);
94 }
95
96 // stdin & stdout are now connected, fire up our coprocess!
ff05c7e1 97 if (execv(d_argv[0], const_cast<char* const*>(d_argv.data())) < 0) // now what
8bd743a8
BH
98 exit(123);
99
100 /* not a lot we can do here. We shouldn't return because that will leave a forked process around.
101 no way to log this either - only thing we can do is make sure that our parent catches this soonest! */
102 }
f7a23c1c 103}
8bd743a8 104
f7a23c1c
BH
105CoProcess::~CoProcess()
106{
107 int status;
ff05c7e1
O
108 if (d_pid) {
109 if (!waitpid(d_pid, &status, WNOHANG)) {
bc058b8b
PD
110 kill(d_pid, 9);
111 waitpid(d_pid, &status, 0);
112 }
013b6194 113 }
ff05c7e1 114
01b7bc4d 115 close(d_fd1[1]);
214fd99e 116 close(d_fd2[0]);
8bd743a8
BH
117}
118
119void CoProcess::checkStatus()
120{
121 int status;
ff05c7e1
O
122 int ret = waitpid(d_pid, &status, WNOHANG);
123 if (ret < 0)
564db8c5 124 throw PDNSException("Unable to ascertain status of coprocess " + std::to_string(d_pid) + " from " + std::to_string(getpid()) + ": " + string(strerror(errno)));
ff05c7e1
O
125 else if (ret) {
126 if (WIFEXITED(status)) {
127 int exitStatus = WEXITSTATUS(status);
564db8c5 128 throw PDNSException("Coprocess exited with code " + std::to_string(exitStatus));
8bd743a8 129 }
ff05c7e1
O
130 if (WIFSIGNALED(status)) {
131 int sig = WTERMSIG(status);
564db8c5 132 string reason = "CoProcess died on receiving signal " + std::to_string(sig);
8bd743a8 133#ifdef WCOREDUMP
ff05c7e1
O
134 if (WCOREDUMP(status))
135 reason += ". Dumped core";
8bd743a8 136#endif
ff05c7e1 137
3f81d239 138 throw PDNSException(reason);
8bd743a8
BH
139 }
140 }
141}
142
ff05c7e1 143void CoProcess::send(const string& snd)
8bd743a8
BH
144{
145 checkStatus();
146 string line(snd);
ff05c7e1
O
147 line.append(1, '\n');
148
149 unsigned int sent = 0;
8bd743a8
BH
150 int bytes;
151
152 // writen routine - socket may not accept al data in one go
ff05c7e1
O
153 while (sent < line.size()) {
154 bytes = write(d_fd1[1], line.c_str() + sent, line.length() - sent);
155 if (bytes < 0)
156 throw PDNSException("Writing to coprocess failed: " + string(strerror(errno)));
8bd743a8 157
ff05c7e1 158 sent += bytes;
8bd743a8
BH
159 }
160}
161
ff05c7e1 162void CoProcess::receive(string& received)
8bd743a8 163{
000f9287 164 received.clear();
214fd99e
RG
165
166 // we might still have some remaining data from our last read
167 if (!d_remaining.empty()) {
000f9287 168 received = std::move(d_remaining);
8bd743a8
BH
169 }
170
214fd99e
RG
171 size_t lastPos = 0;
172 size_t eolPos;
000f9287
AT
173 while ((eolPos = received.find('\n', lastPos)) == std::string::npos) {
174 size_t existingSize = received.size();
214fd99e 175 lastPos = existingSize;
000f9287
AT
176 received.resize(existingSize + 4096);
177 ssize_t got = read(d_fd2[0], &received.at(existingSize), 4096);
214fd99e
RG
178 if (got == 0) {
179 throw PDNSException("Child closed pipe");
180 }
181 else if (got < 0) {
000f9287 182 received.resize(existingSize);
214fd99e
RG
183 int saved = errno;
184 if (saved == EINTR) {
185 continue;
186 }
187 if (saved == EAGAIN) {
ff05c7e1 188 if (d_timeout) {
214fd99e 189 int ret = waitForData(d_fd2[0], 0, d_timeout * 1000);
ff05c7e1
O
190 if (ret < 0)
191 throw PDNSException("Error waiting on data from coprocess: " + string(strerror(saved)));
192 if (!ret)
214fd99e
RG
193 throw PDNSException("Timeout waiting for data from coprocess");
194 }
195 }
196 else {
197 throw PDNSException("Error reading from child's pipe:" + string(strerror(saved)));
198 }
ff05c7e1
O
199 }
200 else {
000f9287 201 received.resize(existingSize + static_cast<size_t>(got));
214fd99e
RG
202 }
203 }
204
000f9287 205 if (eolPos != received.size() - 1) {
214fd99e 206 /* we have some data remaining after the first '\n', let's keep it for later */
000f9287 207 d_remaining.append(received, eolPos + 1, received.size() - eolPos - 1);
214fd99e
RG
208 }
209
000f9287 210 received.resize(eolPos);
dc593046 211 boost::trim_right(received);
8bd743a8 212}
04e240ef 213
ff05c7e1 214void CoProcess::sendReceive(const string& snd, string& rcv)
8bd743a8
BH
215{
216 checkStatus();
217 send(snd);
218 receive(rcv);
8bd743a8 219}
2f2b0145 220
ff05c7e1 221UnixRemote::UnixRemote(const string& path, int timeout)
2f2b0145 222{
f615dd15 223 d_fd = socket(AF_UNIX, SOCK_STREAM, 0);
ff05c7e1
O
224 if (d_fd < 0)
225 throw PDNSException("Unable to create UNIX domain socket: " + string(strerror(errno)));
2f2b0145
BH
226
227 struct sockaddr_un remote;
76cb4593 228 if (makeUNsockaddr(path, &remote))
ff05c7e1 229 throw PDNSException("Unable to create UNIX domain socket: Path '" + path + "' is not a valid UNIX socket path.");
2f2b0145
BH
230
231 // fcntl(fd, F_SETFL, O_NONBLOCK, &sock);
232
ff05c7e1
O
233 if (connect(d_fd, (struct sockaddr*)&remote, sizeof(remote)) < 0)
234 unixDie("Unable to connect to remote '" + path + "' using UNIX domain socket");
2f2b0145 235
ff05c7e1 236 d_fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(d_fd, "r"), fclose);
d9dd76b0
BH
237}
238
2f2b0145
BH
239void UnixRemote::send(const string& line)
240{
241 string nline(line);
242 nline.append(1, '\n');
243 writen2(d_fd, nline);
244}
245
246void UnixRemote::receive(string& line)
247{
248 line.clear();
e30bc0cf 249 stringfgets(d_fp.get(), line);
dc593046 250 boost::trim_right(line);
2f2b0145
BH
251}
252
ff05c7e1 253void UnixRemote::sendReceive(const string& snd, string& rcv)
2f2b0145
BH
254{
255 // checkStatus();
256 send(snd);
257 receive(rcv);
258}
259
260bool isUnixSocket(const string& fname)
261{
262 struct stat st;
ff05c7e1 263 if (stat(fname.c_str(), &st) < 0)
2f2b0145
BH
264 return false; // not a unix socket in any case ;-)
265
266 return (st.st_mode & S_IFSOCK) == S_IFSOCK;
267}
268
8bd743a8
BH
269#ifdef TESTDRIVER
270main()
271{
272 try {
273 CoProcess cp("./irc.pl");
274 string reply;
275 cp.sendReceive("www.trilab.com", reply);
ff05c7e1 276 cout << "Answered: '" << reply << "'" << endl;
8bd743a8 277 }
ff05c7e1
O
278 catch (PDNSException& ae) {
279 cerr << ae.reason << endl;
8bd743a8 280 }
8bd743a8
BH
281}
282#endif