]>
git.ipfire.org Git - thirdparty/pdns.git/blob - modules/remotebackend/pipeconnector.cc
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
25 #include "remotebackend.hh"
27 PipeConnector :: PipeConnector ( std :: map
< std :: string
, std :: string
> optionsMap
) :
30 if ( optionsMap
. count ( "command" ) == 0 ) {
31 g_log
<< Logger :: Error
<< "Cannot find 'command' option in connection string" << endl
;
32 throw PDNSException ();
34 this -> command
= optionsMap
. find ( "command" )-> second
;
35 this -> options
= optionsMap
;
38 if ( optionsMap
. find ( "timeout" ) != optionsMap
. end ()) {
39 d_timeout
= std :: stoi ( optionsMap
. find ( "timeout" )-> second
);
42 d_fd1
[ 0 ] = d_fd1
[ 1 ] = - 1 ;
43 d_fd2
[ 0 ] = d_fd2
[ 1 ] = - 1 ;
46 PipeConnector ::~ PipeConnector ()
54 if ( waitpid ( d_pid
, & status
, WNOHANG
) == 0 ) {
56 waitpid ( d_pid
, & status
, 0 );
64 void PipeConnector :: launch ()
67 if ( d_pid
> 0 && checkStatus ()) {
71 std :: vector
< std :: string
> v
;
72 split ( v
, command
, boost :: is_any_of ( " " ));
74 std :: vector
< const char *> argv ( v
. size () + 1 );
75 argv
[ v
. size ()] = nullptr ;
77 for ( size_t n
= 0 ; n
< v
. size (); n
++) {
78 argv
[ n
] = v
[ n
]. c_str ();
81 signal ( SIGPIPE
, SIG_IGN
);
83 if ( access ( argv
[ 0 ], X_OK
) != 0 ) { // check before fork so we can throw
84 throw PDNSException ( "Command '" + string ( argv
[ 0 ]) + "' cannot be executed: " + stringerror ());
87 if ( pipe ( d_fd1
) < 0 || pipe ( d_fd2
) < 0 ) {
88 throw PDNSException ( "Unable to open pipe for coprocess: " + string ( strerror ( errno
)));
91 if (( d_pid
= fork ()) < 0 ) {
92 throw PDNSException ( "Unable to fork for coprocess: " + stringerror ());
94 if ( d_pid
> 0 ) { // parent speaking
96 setCloseOnExec ( d_fd1
[ 1 ]);
98 setCloseOnExec ( d_fd2
[ 0 ]);
99 if (!( d_fp
= std :: unique_ptr
< FILE , int (*)( FILE *)>( fdopen ( d_fd2
[ 0 ], "r" ), fclose
))) {
100 throw PDNSException ( "Unable to associate a file pointer with pipe: " + stringerror ());
102 if ( d_timeout
!= 0 ) {
103 setbuf ( d_fp
. get (), nullptr ); // no buffering please, confuses poll
106 else if ( d_pid
== 0 ) { // child
107 signal ( SIGCHLD
, SIG_DFL
); // silence a warning from perl
121 // stdin & stdout are now connected, fire up our coprocess!
123 if ( execv ( argv
[ 0 ], const_cast < char * const *>( argv
. data ())) < 0 ) { // now what
127 /* not a lot we can do here. We shouldn't return because that will leave a forked process around.
128 no way to log this either - only thing we can do is make sure that our parent catches this soonest! */
131 Json :: array parameters
;
132 Json msg
= Json ( Json :: object
{
133 { "method" , "initialize" },
134 { "parameters" , Json ( options
)},
139 if (! this -> recv ( msg
)) {
140 g_log
<< Logger :: Error
<< "Failed to initialize coprocess" << std :: endl
;
144 int PipeConnector :: send_message ( const Json
& input
)
146 auto line
= input
. dump ();
149 line
. append ( 1 , ' \n ' );
151 unsigned int sent
= 0 ;
154 // writen routine - socket may not accept al data in one go
155 while ( sent
< line
. size ()) {
156 bytes
= write ( d_fd1
[ 1 ], line
. c_str () + sent
, line
. length () - sent
);
158 throw PDNSException ( "Writing to coprocess failed: " + std :: string ( strerror ( errno
)));
166 int PipeConnector :: recv_message ( Json
& output
)
170 std :: string s_output
;
175 if ( d_timeout
!= 0 ) {
176 int ret
= waitForData ( fileno ( d_fp
. get ()), 0 , d_timeout
* 1000 );
178 throw PDNSException ( "Error waiting on data from coprocess: " + stringerror ());
181 throw PDNSException ( "Timeout waiting for data from coprocess" );
185 if (! stringfgets ( d_fp
. get (), receive
)) {
186 throw PDNSException ( "Child closed pipe" );
189 s_output
. append ( receive
);
190 // see if it can be parsed
191 output
= Json :: parse ( s_output
, err
);
192 if ( output
!= nullptr ) {
193 return s_output
. size ();
199 bool PipeConnector :: checkStatus () const
202 int ret
= waitpid ( d_pid
, & status
, WNOHANG
);
204 throw PDNSException ( "Unable to ascertain status of coprocess " + std :: to_string ( d_pid
) + " from " + std :: to_string ( getpid ()) + ": " + string ( strerror ( errno
)));
207 if ( WIFEXITED ( status
)) {
208 int exitStatus
= WEXITSTATUS ( status
);
209 throw PDNSException ( "Coprocess exited with code " + std :: to_string ( exitStatus
));
211 if ( WIFSIGNALED ( status
)) {
212 int sig
= WTERMSIG ( status
);
213 string reason
= "CoProcess died on receiving signal " + std :: to_string ( sig
);
215 if ( WCOREDUMP ( status
)) {
216 reason
+= ". Dumped core" ;
220 throw PDNSException ( reason
);