00001
00002
00003
00004
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
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
00197
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
00205 setNonBlocking( _cmd->outputFile() );
00206 setNonBlocking( _cmd->inputFile() );
00207
00208
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
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
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
00264 {
00265 PluginDumpStderr _dump( *_cmd );
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 )
00280 {
00281
00282 ssize_t ret = ::write( fd, buffer, buffsize );
00283 if ( ret == buffsize )
00284 {
00285
00286 ::fflush( filep );
00287 break;
00288 }
00289 else if ( ret > 0 )
00290 {
00291
00292 ::fflush( filep );
00293 buffsize -= ret;
00294 buffer += ret;
00295 }
00296 else
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
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
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 );
00343 PluginDumpStderr _dump( *_cmd );
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
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 )
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
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
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 }