1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/PluginScript.cc
12 #include <sys/types.h>
18 #include "zypp/base/LogTools.h"
19 #include "zypp/base/DefaultIntegral.h"
20 #include "zypp/base/String.h"
21 #include "zypp/base/Signal.h"
22 #include "zypp/base/IOStream.h"
24 #include "zypp/PluginScript.h"
25 #include "zypp/ExternalProgram.h"
26 #include "zypp/PathInfo.h"
30 #undef ZYPP_BASE_LOGGER_LOGGROUP
31 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::plugin"
33 ///////////////////////////////////////////////////////////////////
35 { /////////////////////////////////////////////////////////////////
39 const char * PLUGIN_DEBUG = getenv( "ZYPP_PLUGIN_DEBUG" );
41 /** Dump buffer string if PLUGIN_DEBUG is on.
44 struct PluginDebugBuffer
46 PluginDebugBuffer( const std::string & buffer_r ) : _buffer( buffer_r ) {}
51 if ( _buffer.empty() )
53 _DBG("PLUGIN") << "< (empty)" << endl;
57 std::istringstream datas( _buffer );
58 iostr::copyIndent( datas, _DBG("PLUGIN"), "< " ) << endl;
62 const std::string & _buffer;
65 /** Dump programms stderr.
68 struct PluginDumpStderr
70 PluginDumpStderr( ExternalProgramWithStderr & prog_r ) : _prog( prog_r ) {}
74 while ( _prog.stderrGetline( line ) )
75 _WAR("PLUGIN") << "! " << line << endl;
77 ExternalProgramWithStderr & _prog;
80 inline void setBlocking( FILE * file_r, bool yesno_r = true )
83 ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
85 int fd = ::fileno( file_r );
87 ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
89 int flags = ::fcntl( fd, F_GETFL );
91 ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
95 else if ( flags & O_NONBLOCK )
98 flags = ::fcntl( fd, F_SETFL, flags );
100 ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
103 inline void setNonBlocking( FILE * file_r, bool yesno_r = true )
104 { setBlocking( file_r, !yesno_r ); }
107 ///////////////////////////////////////////////////////////////////
109 // CLASS NAME : PluginScript::Impl
111 /** \ref PluginScript implementation. */
112 struct PluginScript::Impl
115 Impl( const Pathname & script_r = Pathname(), const Arguments & args_r = Arguments() )
116 : _sendTimeout( _defaultSendTimeout )
117 , _receiveTimeout( _defaultReceiveTimeout )
118 , _script( script_r )
123 { try { close(); } catch(...) {} }
126 static long _defaultSendTimeout;
127 static long _defaultReceiveTimeout;
130 long _receiveTimeout;
133 const Pathname & script() const
136 const Arguments & args() const
140 { return _cmd ? _cmd->getpid() : NotConnected; }
143 { return _cmd != nullptr; }
145 int lastReturn() const
146 { return _lastReturn; }
148 const std::string & lastExecError() const
149 { return _lastExecError; }
152 void open( const Pathname & script_r = Pathname(), const Arguments & args_r = Arguments() );
156 void send( const PluginFrame & frame_r ) const;
158 PluginFrame receive() const;
163 scoped_ptr<ExternalProgramWithStderr> _cmd;
164 DefaultIntegral<int,0> _lastReturn;
165 std::string _lastExecError;
167 ///////////////////////////////////////////////////////////////////
169 /** \relates PluginScrip::Impl Stream output */
170 inline std::ostream & operator<<( std::ostream & str, const PluginScript::Impl & obj )
172 return str << "PluginScript[" << obj.getPid() << "] " << obj.script();
175 ///////////////////////////////////////////////////////////////////
179 const long PLUGIN_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_TIMEOUT" ) );
180 const long PLUGIN_SEND_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_SEND_TIMEOUT" ) );
181 const long PLUGIN_RECEIVE_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_RECEIVE_TIMEOUT" ) );
184 long PluginScript::Impl::_defaultSendTimeout = ( PLUGIN_SEND_TIMEOUT > 0 ? PLUGIN_SEND_TIMEOUT
185 : ( PLUGIN_TIMEOUT > 0 ? PLUGIN_TIMEOUT : 30 ) );
186 long PluginScript::Impl::_defaultReceiveTimeout = ( PLUGIN_RECEIVE_TIMEOUT > 0 ? PLUGIN_RECEIVE_TIMEOUT
187 : ( PLUGIN_TIMEOUT > 0 ? PLUGIN_TIMEOUT : 30 ) );
189 ///////////////////////////////////////////////////////////////////
191 void PluginScript::Impl::open( const Pathname & script_r, const Arguments & args_r )
193 dumpRangeLine( DBG << "Open " << script_r, args_r.begin(), args_r.end() ) << endl;
196 ZYPP_THROW( PluginScriptException( "Already connected", str::Str() << *this ) );
199 PathInfo pi( script_r );
200 if ( ! ( pi.isFile() && pi.isX() ) )
201 ZYPP_THROW( PluginScriptException( "Script is not executable", str::Str() << pi ) );
204 // go and launch script
205 // TODO: ExternalProgram::maybe use Stderr_To_FileDesc for script loging
207 args.reserve( args_r.size()+1 );
208 args.push_back( script_r.asString() );
209 args.insert( args.end(), args_r.begin(), args_r.end() );
210 _cmd.reset( new ExternalProgramWithStderr( args ) );
212 // Be protected against full pipe, etc.
213 setNonBlocking( _cmd->outputFile() );
214 setNonBlocking( _cmd->inputFile() );
216 // store running scripts data
220 _lastExecError.clear();
222 dumpRangeLine( DBG << *this, _args.begin(), _args.end() ) << endl;
225 int PluginScript::Impl::close()
229 DBG << "Close:" << *this << endl;
232 // do not kill script if _DISCONNECT is ACKed.
233 send( PluginFrame( "_DISCONNECT" ) );
234 PluginFrame ret( receive() );
235 if ( ret.isAckCommand() )
238 str::strtonum( ret.getHeaderNT( "exit" ), _lastReturn.get() );
239 _lastExecError = ret.body();
248 _lastReturn = _cmd->close();
249 _lastExecError = _cmd->execError();
251 DBG << *this << " -> [" << _lastReturn << "] " << _lastExecError << endl;
257 void PluginScript::Impl::send( const PluginFrame & frame_r ) const
260 ZYPP_THROW( PluginScriptNotConnected( "Not connected", str::Str() << *this ) );
262 if ( frame_r.command().empty() )
263 WAR << "Send: No command in frame" << frame_r << endl;
265 // prepare frame data to write
268 std::ostringstream datas;
269 frame_r.writeTo( datas );
270 datas.str().swap( data );
272 DBG << *this << " ->send " << frame_r << endl;
276 std::istringstream datas( data );
277 iostr::copyIndent( datas, _DBG("PLUGIN") ) << endl;
280 // try writing the pipe....
281 FILE * filep = _cmd->outputFile();
283 ZYPP_THROW( PluginScriptException( "Bad file pointer." ) );
285 int fd = ::fileno( filep );
287 ZYPP_THROW( PluginScriptException( "Bad file descriptor" ) );
289 //DBG << " ->[" << fd << " " << (::feof(filep)?'e':'_') << (::ferror(filep)?'F':'_') << "]" << endl;
291 PluginDumpStderr _dump( *_cmd ); // dump scripts stderr before leaving
292 SignalSaver sigsav( SIGPIPE, SIG_IGN );
293 const char * buffer = data.c_str();
294 ssize_t buffsize = data.size();
301 tv.tv_sec = _sendTimeout;
304 int retval = select( fd+1, NULL, &wfds, NULL, &tv );
305 if ( retval > 0 ) // FD_ISSET( fd, &wfds ) will be true.
307 //DBG << "Ready to write..." << endl;
308 ssize_t ret = ::write( fd, buffer, buffsize );
309 if ( ret == buffsize )
311 //DBG << "::write(" << buffsize << ") -> " << ret << endl;
317 //WAR << "::write(" << buffsize << ") -> " << ret << " INCOMPLETE..." << endl;
320 buffer += ret; // -> continue
322 else // ( retval == -1 )
324 if ( errno != EINTR )
326 ERR << "write(): " << Errno() << endl;
327 if ( errno == EPIPE )
328 ZYPP_THROW( PluginScriptDiedUnexpectedly( "Send: script died unexpectedly", str::Str() << Errno() ) );
330 ZYPP_THROW( PluginScriptException( "Send: send error", str::Str() << Errno() ) );
334 else if ( retval == 0 )
336 WAR << "Not ready to write within timeout." << endl;
337 ZYPP_THROW( PluginScriptSendTimeout( "Not ready to write within timeout." ) );
339 else // ( retval == -1 )
341 if ( errno != EINTR )
343 ERR << "select(): " << Errno() << endl;
344 ZYPP_THROW( PluginScriptException( "Error waiting on file descriptor", str::Str() << Errno() ) );
351 PluginFrame PluginScript::Impl::receive() const
354 ZYPP_THROW( PluginScriptNotConnected( "Not connected", str::Str() << *this ) );
356 // try reading the pipe....
357 FILE * filep = _cmd->inputFile();
359 ZYPP_THROW( PluginScriptException( "Bad file pointer." ) );
361 int fd = ::fileno( filep );
363 ZYPP_THROW( PluginScriptException( "Bad file descriptor" ) );
368 PluginDebugBuffer _debug( data ); // dump receive buffer if PLUGIN_DEBUG
369 PluginDumpStderr _dump( *_cmd ); // dump scripts stderr before leaving
371 int ch = fgetc( filep );
374 data.push_back( ch );
378 else if ( ::feof( filep ) )
380 WAR << "Unexpected EOF" << endl;
381 ZYPP_THROW( PluginScriptDiedUnexpectedly( "Receive: script died unexpectedly", str::Str() << Errno() ) );
383 else if ( errno != EINTR )
385 if ( errno == EWOULDBLOCK )
387 // wait a while for fd to become ready for reading...
393 tv.tv_sec = _receiveTimeout;
396 int retval = select( fd+1, &rfds, NULL, NULL, &tv );
397 if ( retval > 0 ) // FD_ISSET( fd, &rfds ) will be true.
401 else if ( retval == 0 )
403 WAR << "Not ready to read within timeout." << endl;
404 ZYPP_THROW( PluginScriptReceiveTimeout( "Not ready to read within timeout." ) );
406 else // ( retval == -1 )
408 if ( errno != EINTR )
410 ERR << "select(): " << Errno() << endl;
411 ZYPP_THROW( PluginScriptException( "Error waiting on file descriptor", str::Str() << Errno() ) );
417 ERR << "read(): " << Errno() << endl;
418 ZYPP_THROW( PluginScriptException( "Receive: receive error", str::Str() << Errno() ) );
423 // DBG << " <-read " << data.size() << endl;
424 std::istringstream datas( data );
425 PluginFrame ret( datas );
426 DBG << *this << " <-" << ret << endl;
430 ///////////////////////////////////////////////////////////////////
432 // CLASS NAME : PluginScript
434 ///////////////////////////////////////////////////////////////////
436 const pid_t PluginScript::NotConnected( -1 );
438 long PluginScript::defaultSendTimeout()
439 { return Impl::_defaultSendTimeout; }
441 long PluginScript::defaultReceiveTimeout()
442 { return Impl::_defaultReceiveTimeout; }
444 void PluginScript::defaultSendTimeout( long newval_r )
445 { Impl::_defaultSendTimeout = newval_r > 0 ? newval_r : 0; }
447 void PluginScript::defaultReceiveTimeout( long newval_r )
448 { Impl::_defaultReceiveTimeout = newval_r > 0 ? newval_r : 0; }
450 long PluginScript::sendTimeout() const
451 { return _pimpl->_sendTimeout; }
453 long PluginScript::receiveTimeout() const
454 { return _pimpl->_receiveTimeout; }
456 void PluginScript::sendTimeout( long newval_r )
457 { _pimpl->_sendTimeout = newval_r > 0 ? newval_r : 0; }
459 void PluginScript::receiveTimeout( long newval_r )
460 { _pimpl->_receiveTimeout = newval_r > 0 ? newval_r : 0; }
462 PluginScript::PluginScript()
466 PluginScript::PluginScript( const Pathname & script_r )
467 : _pimpl( new Impl( script_r ) )
470 PluginScript::PluginScript( const Pathname & script_r, const Arguments & args_r )
471 : _pimpl( new Impl( script_r, args_r ) )
474 const Pathname & PluginScript::script() const
475 { return _pimpl->script(); }
477 const PluginScript::Arguments & PluginScript::args() const
478 { return _pimpl->args(); }
480 bool PluginScript::isOpen() const
481 { return _pimpl->isOpen(); }
483 pid_t PluginScript::getPid() const
484 { return _pimpl->getPid(); }
486 int PluginScript::lastReturn() const
487 { return _pimpl->lastReturn(); }
489 const std::string & PluginScript::lastExecError() const
490 { return _pimpl->lastExecError(); }
492 void PluginScript::open()
493 { _pimpl->open( _pimpl->script(), _pimpl->args() ); }
495 void PluginScript::open( const Pathname & script_r )
496 { _pimpl->open( script_r ); }
498 void PluginScript::open( const Pathname & script_r, const Arguments & args_r )
499 { _pimpl->open( script_r, args_r ); }
501 int PluginScript::close()
502 { return _pimpl->close(); }
504 void PluginScript::send( const PluginFrame & frame_r ) const
505 { _pimpl->send( frame_r ); }
507 PluginFrame PluginScript::receive() const
508 { return _pimpl->receive(); }
510 ///////////////////////////////////////////////////////////////////
512 std::ostream & operator<<( std::ostream & str, const PluginScript & obj )
513 { return str << *obj._pimpl; }
515 /////////////////////////////////////////////////////////////////
517 ///////////////////////////////////////////////////////////////////