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