libzypp
10.5.0
|
00001 /*---------------------------------------------------------------------\ 00002 | ____ _ __ __ ___ | 00003 | |__ / \ / / . \ . \ | 00004 | / / \ V /| _/ _/ | 00005 | / /__ | | | | | | | 00006 | /_____||_| |_| |_| | 00007 | | 00008 \---------------------------------------------------------------------*/ 00012 #include <sys/types.h> 00013 #include <signal.h> 00014 00015 #include <iostream> 00016 #include <sstream> 00017 00018 #include "zypp/base/LogTools.h" 00019 #include "zypp/base/DefaultIntegral.h" 00020 #include "zypp/base/String.h" 00021 #include "zypp/base/Signal.h" 00022 #include "zypp/base/IOStream.h" 00023 00024 #include "zypp/PluginScript.h" 00025 #include "zypp/ExternalProgram.h" 00026 #include "zypp/PathInfo.h" 00027 00028 using std::endl; 00029 00031 namespace zypp 00032 { 00033 00034 namespace 00035 { 00036 const char * PLUGIN_DEBUG = getenv( "ZYPP_PLUGIN_DEBUG" ); 00037 00041 struct PluginDebugBuffer 00042 { 00043 PluginDebugBuffer( const std::string & buffer_r ) : _buffer( buffer_r ) {} 00044 ~PluginDebugBuffer() 00045 { 00046 if ( PLUGIN_DEBUG ) 00047 { 00048 if ( _buffer.empty() ) 00049 { 00050 _DBG("PLUGIN") << "< (empty)" << endl; 00051 } 00052 else 00053 { 00054 std::istringstream datas( _buffer ); 00055 iostr::copyIndent( datas, _DBG("PLUGIN"), "< " ) << endl; 00056 } 00057 } 00058 } 00059 const std::string & _buffer; 00060 }; 00061 00065 struct PluginDumpStderr 00066 { 00067 PluginDumpStderr( ExternalProgramWithStderr & prog_r ) : _prog( prog_r ) {} 00068 ~PluginDumpStderr() 00069 { 00070 std::string line; 00071 while ( _prog.stderrGetline( line ) ) 00072 _WAR("PLUGIN") << "! " << line << endl; 00073 } 00074 ExternalProgramWithStderr & _prog; 00075 }; 00076 00077 inline void setBlocking( FILE * file_r, bool yesno_r = true ) 00078 { 00079 if ( ! file_r ) 00080 ZYPP_THROW( PluginScriptException( "setNonBlocking" ) ); 00081 00082 int fd = ::fileno( file_r ); 00083 if ( fd == -1 ) 00084 ZYPP_THROW( PluginScriptException( "setNonBlocking" ) ); 00085 00086 int flags = ::fcntl( fd, F_GETFL ); 00087 if ( flags == -1 ) 00088 ZYPP_THROW( PluginScriptException( "setNonBlocking" ) ); 00089 00090 if ( ! yesno_r ) 00091 flags |= O_NONBLOCK; 00092 else if ( flags & O_NONBLOCK ) 00093 flags ^= O_NONBLOCK; 00094 00095 flags = ::fcntl( fd, F_SETFL, flags ); 00096 if ( flags == -1 ) 00097 ZYPP_THROW( PluginScriptException( "setNonBlocking" ) ); 00098 } 00099 00100 inline void setNonBlocking( FILE * file_r, bool yesno_r = true ) 00101 { setBlocking( file_r, !yesno_r ); } 00102 } 00103 00105 // 00106 // CLASS NAME : PluginScript::Impl 00107 // 00109 struct PluginScript::Impl 00110 { 00111 public: 00112 Impl( const Pathname & script_r = Pathname(), const Arguments & args_r = Arguments() ) 00113 : _sendTimeout( _defaultSendTimeout ) 00114 , _receiveTimeout( _defaultReceiveTimeout ) 00115 , _script( script_r ) 00116 , _args( args_r ) 00117 {} 00118 00119 ~ Impl() 00120 { try { close(); } catch(...) {} } 00121 00122 public: 00123 static long _defaultSendTimeout; 00124 static long _defaultReceiveTimeout; 00125 00126 long _sendTimeout; 00127 long _receiveTimeout; 00128 00129 public: 00130 const Pathname & script() const 00131 { return _script; } 00132 00133 const Arguments & args() const 00134 { return _args; } 00135 00136 pid_t getPid() const 00137 { return _cmd ? _cmd->getpid() : NotConnected; } 00138 00139 bool isOpen() const 00140 { return _cmd; } 00141 00142 int lastReturn() const 00143 { return _lastReturn; } 00144 00145 const std::string & lastExecError() const 00146 { return _lastExecError; } 00147 00148 public: 00149 void open( const Pathname & script_r = Pathname(), const Arguments & args_r = Arguments() ); 00150 00151 int close(); 00152 00153 void send( const PluginFrame & frame_r ) const; 00154 00155 PluginFrame receive() const; 00156 00157 private: 00158 Pathname _script; 00159 Arguments _args; 00160 scoped_ptr<ExternalProgramWithStderr> _cmd; 00161 DefaultIntegral<int,0> _lastReturn; 00162 std::string _lastExecError; 00163 }; 00165 00167 inline std::ostream & operator<<( std::ostream & str, const PluginScript::Impl & obj ) 00168 { 00169 return dumpRangeLine( str << "PluginScript[" << obj.getPid() << "] " << obj.script(), 00170 obj.args().begin(), obj.args().end() ); 00171 } 00172 00174 00175 namespace 00176 { 00177 const long PLUGIN_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_TIMEOUT" ) ); 00178 const long PLUGIN_SEND_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_SEND_TIMEOUT" ) ); 00179 const long PLUGIN_RECEIVE_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_RECEIVE_TIMEOUT" ) ); 00180 } 00181 00182 long PluginScript::Impl::_defaultSendTimeout = ( PLUGIN_SEND_TIMEOUT > 0 ? PLUGIN_SEND_TIMEOUT 00183 : ( PLUGIN_TIMEOUT > 0 ? PLUGIN_TIMEOUT : 30 ) ); 00184 long PluginScript::Impl::_defaultReceiveTimeout = ( PLUGIN_RECEIVE_TIMEOUT > 0 ? PLUGIN_RECEIVE_TIMEOUT 00185 : ( PLUGIN_TIMEOUT > 0 ? PLUGIN_TIMEOUT : 30 ) ); 00186 00188 00189 void PluginScript::Impl::open( const Pathname & script_r, const Arguments & args_r ) 00190 { 00191 dumpRangeLine( DBG << "Open " << script_r, args_r.begin(), args_r.end() ) << endl; 00192 00193 if ( _cmd ) 00194 ZYPP_THROW( PluginScriptException( "Already connected", str::Str() << *this ) ); 00195 00196 { 00197 PathInfo pi( script_r ); 00198 if ( ! ( pi.isFile() && pi.isX() ) ) 00199 ZYPP_THROW( PluginScriptException( "Script is not executable", str::Str() << pi ) ); 00200 } 00201 00202 // go and launch script 00203 // TODO: ExternalProgram::maybe use Stderr_To_FileDesc for script loging 00204 Arguments args; 00205 args.reserve( args_r.size()+1 ); 00206 args.push_back( script_r.asString() ); 00207 args.insert( args.end(), args_r.begin(), args_r.end() ); 00208 _cmd.reset( new ExternalProgramWithStderr( args ) ); 00209 00210 // Be protected against full pipe, etc. 00211 setNonBlocking( _cmd->outputFile() ); 00212 setNonBlocking( _cmd->inputFile() ); 00213 00214 // store running scripts data 00215 _script = script_r; 00216 _args = args_r; 00217 _lastReturn.reset(); 00218 _lastExecError.clear(); 00219 00220 DBG << *this << endl; 00221 } 00222 00223 int PluginScript::Impl::close() 00224 { 00225 if ( _cmd ) 00226 { 00227 DBG << "Close:" << *this << endl; 00228 bool doKill = true; 00229 try { 00230 // do not kill script if _DISCONNECT is ACKed. 00231 send( PluginFrame( "_DISCONNECT" ) ); 00232 PluginFrame ret( receive() ); 00233 if ( ret.isAckCommand() ) 00234 { 00235 doKill = false; 00236 str::strtonum( ret.getHeaderNT( "exit" ), _lastReturn.get() ); 00237 _lastExecError = ret.body(); 00238 } 00239 } 00240 catch (...) 00241 { /* NOP */ } 00242 00243 if ( doKill ) 00244 { 00245 _cmd->kill(); 00246 _lastReturn = _cmd->close(); 00247 _lastExecError = _cmd->execError(); 00248 } 00249 DBG << *this << " -> [" << _lastReturn << "] " << _lastExecError << endl; 00250 _cmd.reset(); 00251 } 00252 return _lastReturn; 00253 } 00254 00255 void PluginScript::Impl::send( const PluginFrame & frame_r ) const 00256 { 00257 if ( !_cmd ) 00258 ZYPP_THROW( PluginScriptNotConnected( "Not connected", str::Str() << *this ) ); 00259 00260 if ( frame_r.command().empty() ) 00261 WAR << "Send: No command in frame" << frame_r << endl; 00262 00263 // prepare frame data to write 00264 std::string data; 00265 { 00266 std::ostringstream datas; 00267 frame_r.writeTo( datas ); 00268 datas.str().swap( data ); 00269 } 00270 DBG << "->send " << frame_r << endl; 00271 00272 if ( PLUGIN_DEBUG ) 00273 { 00274 std::istringstream datas( data ); 00275 iostr::copyIndent( datas, _DBG("PLUGIN") ) << endl; 00276 } 00277 00278 // try writing the pipe.... 00279 FILE * filep = _cmd->outputFile(); 00280 if ( ! filep ) 00281 ZYPP_THROW( PluginScriptException( "Bad file pointer." ) ); 00282 00283 int fd = ::fileno( filep ); 00284 if ( fd == -1 ) 00285 ZYPP_THROW( PluginScriptException( "Bad file descriptor" ) ); 00286 00287 //DBG << " ->[" << fd << " " << (::feof(filep)?'e':'_') << (::ferror(filep)?'F':'_') << "]" << endl; 00288 { 00289 PluginDumpStderr _dump( *_cmd ); // dump scripts stderr before leaving 00290 SignalSaver sigsav( SIGPIPE, SIG_IGN ); 00291 const char * buffer = data.c_str(); 00292 ssize_t buffsize = data.size(); 00293 do { 00294 fd_set wfds; 00295 FD_ZERO( &wfds ); 00296 FD_SET( fd, &wfds ); 00297 00298 struct timeval tv; 00299 tv.tv_sec = _sendTimeout; 00300 tv.tv_usec = 0; 00301 00302 int retval = select( fd+1, NULL, &wfds, NULL, &tv ); 00303 if ( retval > 0 ) // FD_ISSET( fd, &wfds ) will be true. 00304 { 00305 //DBG << "Ready to write..." << endl; 00306 ssize_t ret = ::write( fd, buffer, buffsize ); 00307 if ( ret == buffsize ) 00308 { 00309 //DBG << "::write(" << buffsize << ") -> " << ret << endl; 00310 ::fflush( filep ); 00311 break; // -> done 00312 } 00313 else if ( ret > 0 ) 00314 { 00315 //WAR << "::write(" << buffsize << ") -> " << ret << " INCOMPLETE..." << endl; 00316 ::fflush( filep ); 00317 buffsize -= ret; 00318 buffer += ret; // -> continue 00319 } 00320 else // ( retval == -1 ) 00321 { 00322 if ( errno != EINTR ) 00323 { 00324 ERR << "write(): " << Errno() << endl; 00325 if ( errno == EPIPE ) 00326 ZYPP_THROW( PluginScriptDiedUnexpectedly( "Send: script died unexpectedly", str::Str() << Errno() ) ); 00327 else 00328 ZYPP_THROW( PluginScriptException( "Send: send error", str::Str() << Errno() ) ); 00329 } 00330 } 00331 } 00332 else if ( retval == 0 ) 00333 { 00334 WAR << "Not ready to write within timeout." << endl; 00335 ZYPP_THROW( PluginScriptSendTimeout( "Not ready to write within timeout." ) ); 00336 } 00337 else // ( retval == -1 ) 00338 { 00339 if ( errno != EINTR ) 00340 { 00341 ERR << "select(): " << Errno() << endl; 00342 ZYPP_THROW( PluginScriptException( "Error waiting on file descriptor", str::Str() << Errno() ) ); 00343 } 00344 } 00345 } while( true ); 00346 } 00347 } 00348 00349 PluginFrame PluginScript::Impl::receive() const 00350 { 00351 if ( !_cmd ) 00352 ZYPP_THROW( PluginScriptNotConnected( "Not connected", str::Str() << *this ) ); 00353 00354 // try reading the pipe.... 00355 FILE * filep = _cmd->inputFile(); 00356 if ( ! filep ) 00357 ZYPP_THROW( PluginScriptException( "Bad file pointer." ) ); 00358 00359 int fd = ::fileno( filep ); 00360 if ( fd == -1 ) 00361 ZYPP_THROW( PluginScriptException( "Bad file descriptor" ) ); 00362 00363 ::clearerr( filep ); 00364 std::string data; 00365 { 00366 PluginDebugBuffer _debug( data ); // dump receive buffer if PLUGIN_DEBUG 00367 PluginDumpStderr _dump( *_cmd ); // dump scripts stderr before leaving 00368 do { 00369 int ch = fgetc( filep ); 00370 if ( ch != EOF ) 00371 { 00372 data.push_back( ch ); 00373 if ( ch == '\0' ) 00374 break; 00375 } 00376 else if ( ::feof( filep ) ) 00377 { 00378 WAR << "Unexpected EOF" << endl; 00379 ZYPP_THROW( PluginScriptDiedUnexpectedly( "Receive: script died unexpectedly", str::Str() << Errno() ) ); 00380 } 00381 else if ( errno != EINTR ) 00382 { 00383 if ( errno == EWOULDBLOCK ) 00384 { 00385 // wait a while for fd to become ready for reading... 00386 fd_set rfds; 00387 FD_ZERO( &rfds ); 00388 FD_SET( fd, &rfds ); 00389 00390 struct timeval tv; 00391 tv.tv_sec = _receiveTimeout; 00392 tv.tv_usec = 0; 00393 00394 int retval = select( fd+1, &rfds, NULL, NULL, &tv ); 00395 if ( retval > 0 ) // FD_ISSET( fd, &rfds ) will be true. 00396 { 00397 ::clearerr( filep ); 00398 } 00399 else if ( retval == 0 ) 00400 { 00401 WAR << "Not ready to read within timeout." << endl; 00402 ZYPP_THROW( PluginScriptReceiveTimeout( "Not ready to read within timeout." ) ); 00403 } 00404 else // ( retval == -1 ) 00405 { 00406 if ( errno != EINTR ) 00407 { 00408 ERR << "select(): " << Errno() << endl; 00409 ZYPP_THROW( PluginScriptException( "Error waiting on file descriptor", str::Str() << Errno() ) ); 00410 } 00411 } 00412 } 00413 else 00414 { 00415 ERR << "read(): " << Errno() << endl; 00416 ZYPP_THROW( PluginScriptException( "Receive: receive error", str::Str() << Errno() ) ); 00417 } 00418 } 00419 } while ( true ); 00420 } 00421 // DBG << " <-read " << data.size() << endl; 00422 std::istringstream datas( data ); 00423 PluginFrame ret( datas ); 00424 DBG << "<-" << ret << endl; 00425 return ret; 00426 } 00427 00429 // 00430 // CLASS NAME : PluginScript 00431 // 00433 00434 const pid_t PluginScript::NotConnected( -1 ); 00435 00436 long PluginScript::defaultSendTimeout() 00437 { return Impl::_defaultSendTimeout; } 00438 00439 long PluginScript::defaultReceiveTimeout() 00440 { return Impl::_defaultReceiveTimeout; } 00441 00442 void PluginScript::defaultSendTimeout( long newval_r ) 00443 { Impl::_defaultSendTimeout = newval_r > 0 ? newval_r : 0; } 00444 00445 void PluginScript::defaultReceiveTimeout( long newval_r ) 00446 { Impl::_defaultReceiveTimeout = newval_r > 0 ? newval_r : 0; } 00447 00448 long PluginScript::sendTimeout() const 00449 { return _pimpl->_sendTimeout; } 00450 00451 long PluginScript::receiveTimeout() const 00452 { return _pimpl->_receiveTimeout; } 00453 00454 void PluginScript::sendTimeout( long newval_r ) 00455 { _pimpl->_sendTimeout = newval_r > 0 ? newval_r : 0; } 00456 00457 void PluginScript::receiveTimeout( long newval_r ) 00458 { _pimpl->_receiveTimeout = newval_r > 0 ? newval_r : 0; } 00459 00460 PluginScript::PluginScript() 00461 : _pimpl( new Impl ) 00462 {} 00463 00464 PluginScript::PluginScript( const Pathname & script_r ) 00465 : _pimpl( new Impl( script_r ) ) 00466 {} 00467 00468 PluginScript::PluginScript( const Pathname & script_r, const Arguments & args_r ) 00469 : _pimpl( new Impl( script_r, args_r ) ) 00470 {} 00471 00472 const Pathname & PluginScript::script() const 00473 { return _pimpl->script(); } 00474 00475 const PluginScript::Arguments & PluginScript::args() const 00476 { return _pimpl->args(); } 00477 00478 bool PluginScript::isOpen() const 00479 { return _pimpl->isOpen(); } 00480 00481 pid_t PluginScript::getPid() const 00482 { return _pimpl->getPid(); } 00483 00484 int PluginScript::lastReturn() const 00485 { return _pimpl->lastReturn(); } 00486 00487 const std::string & PluginScript::lastExecError() const 00488 { return _pimpl->lastExecError(); } 00489 00490 void PluginScript::open() 00491 { _pimpl->open( _pimpl->script(), _pimpl->args() ); } 00492 00493 void PluginScript::open( const Pathname & script_r ) 00494 { _pimpl->open( script_r ); } 00495 00496 void PluginScript::open( const Pathname & script_r, const Arguments & args_r ) 00497 { _pimpl->open( script_r, args_r ); } 00498 00499 int PluginScript::close() 00500 { return _pimpl->close(); } 00501 00502 void PluginScript::send( const PluginFrame & frame_r ) const 00503 { _pimpl->send( frame_r ); } 00504 00505 PluginFrame PluginScript::receive() const 00506 { return _pimpl->receive(); } 00507 00509 00510 std::ostream & operator<<( std::ostream & str, const PluginScript & obj ) 00511 { return str << *obj._pimpl; } 00512 00514 } // namespace zypp