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 ///////////////////////////////////////////////////////////////////
32 { /////////////////////////////////////////////////////////////////
37 const char * PLUGIN_DEBUG = getenv( "ZYPP_PLUGIN_DEBUG" );
38 const long PLUGIN_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_TIMEOUT" ) );
39 const long PLUGIN_SEND_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_SEND_TIMEOUT" ) );
40 const long PLUGIN_RECEIVE_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_RECEIVE_TIMEOUT" ) );
42 /** Dump buffer string if PLUGIN_DEBUG is on.
45 struct PluginDebugBuffer
47 PluginDebugBuffer( const std::string & buffer_r ) : _buffer( buffer_r ) {}
52 if ( _buffer.empty() )
54 _DBG("PLUGIN") << "< (empty)" << endl;
58 std::istringstream datas( _buffer );
59 iostr::copyIndent( datas, _DBG("PLUGIN"), "< " ) << endl;
63 const std::string & _buffer;
66 /** Dump buffer string if PLUGIN_DEBUG is on.
69 struct PluginDumpStderr
71 PluginDumpStderr( ExternalProgramWithStderr & prog_r ) : _prog( prog_r ) {}
75 while ( _prog.stderrGetline( line ) )
76 _WAR("PLUGIN") << "! " << line << endl;
78 ExternalProgramWithStderr & _prog;
81 inline void setBlocking( FILE * file_r, bool yesno_r = true )
84 ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
86 int fd = ::fileno( file_r );
88 ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
90 int flags = ::fcntl( fd, F_GETFL );
92 ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
96 else if ( flags & O_NONBLOCK )
99 flags = ::fcntl( fd, F_SETFL, flags );
101 ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
104 inline void setNonBlocking( FILE * file_r, bool yesno_r = true )
105 { setBlocking( file_r, !yesno_r ); }
108 ///////////////////////////////////////////////////////////////////
110 // CLASS NAME : PluginScript::Impl
112 /** \ref PluginScript implementation. */
113 struct PluginScript::Impl
116 Impl( const Pathname & script_r = Pathname(), const Arguments & args_r = Arguments() )
117 : _script( script_r )
122 { try { close(); } catch(...) {} }
125 /** Timeout (sec.) when sending data. */
126 static const long send_timeout;
127 /** Timeout (sec.) when receiving data. */
128 static const long receive_timeout;
131 const Pathname & script() const
134 const Arguments & args() const
138 { return _cmd ? _cmd->getpid() : NotConnected; }
143 int lastReturn() const
144 { return _lastReturn; }
146 const std::string & lastExecError() const
147 { return _lastExecError; }
150 void open( const Pathname & script_r = Pathname(), const Arguments & args_r = Arguments() );
154 void send( const PluginFrame & frame_r ) const;
156 PluginFrame receive() const;
161 scoped_ptr<ExternalProgramWithStderr> _cmd;
162 DefaultIntegral<int,0> _lastReturn;
163 std::string _lastExecError;
165 ///////////////////////////////////////////////////////////////////
167 /** \relates PluginScrip::Impl Stream output */
168 inline std::ostream & operator<<( std::ostream & str, const PluginScript::Impl & obj )
170 return dumpRangeLine( str << "PluginScript[" << obj.getPid() << "] " << obj.script(),
171 obj.args().begin(), obj.args().end() );
174 ///////////////////////////////////////////////////////////////////
176 const long PluginScript::Impl::send_timeout = ( PLUGIN_SEND_TIMEOUT > 0 ? PLUGIN_SEND_TIMEOUT
177 : ( PLUGIN_TIMEOUT > 0 ? PLUGIN_TIMEOUT : 30 ) );
178 const long PluginScript::Impl::receive_timeout = ( PLUGIN_RECEIVE_TIMEOUT > 0 ? PLUGIN_RECEIVE_TIMEOUT
179 : ( PLUGIN_TIMEOUT > 0 ? PLUGIN_TIMEOUT : 30 ) );
181 ///////////////////////////////////////////////////////////////////
183 void PluginScript::Impl::open( const Pathname & script_r, const Arguments & args_r )
185 dumpRangeLine( DBG << "Open " << script_r, args_r.begin(), args_r.end() ) << endl;
188 ZYPP_THROW( PluginScriptException( "Already connected", str::Str() << *this ) );
191 PathInfo pi( script_r );
192 if ( ! ( pi.isFile() && pi.isX() ) )
193 ZYPP_THROW( PluginScriptException( "Script is not executable", str::Str() << pi ) );
196 // go and launch script
197 // TODO: ExternalProgram::maybe use Stderr_To_FileDesc for script loging
199 args.reserve( args_r.size()+1 );
200 args.push_back( script_r.asString() );
201 args.insert( args.end(), args_r.begin(), args_r.end() );
202 _cmd.reset( new ExternalProgramWithStderr( args ) );
204 // Be protected against full pipe, etc.
205 setNonBlocking( _cmd->outputFile() );
206 setNonBlocking( _cmd->inputFile() );
208 // store running scripts data
212 _lastExecError.clear();
214 DBG << *this << endl;
217 int PluginScript::Impl::close()
221 DBG << "Close:" << *this << endl;
223 _lastReturn = _cmd->close();
224 _lastExecError = _cmd->execError();
226 DBG << *this << " -> [" << _lastReturn << "] " << _lastExecError << endl;
231 void PluginScript::Impl::send( const PluginFrame & frame_r ) const
234 ZYPP_THROW( PluginScriptNotConnected( "Not connected", str::Str() << *this ) );
236 if ( frame_r.command().empty() )
237 WAR << "Send: No command in frame" << frame_r << endl;
239 // prepare frame data to write
242 std::ostringstream datas;
243 frame_r.writeTo( datas );
244 datas.str().swap( data );
246 DBG << "->send " << frame_r << endl;
250 std::istringstream datas( data );
251 iostr::copyIndent( datas, _DBG("PLUGIN") ) << endl;
254 // try writing the pipe....
255 FILE * filep = _cmd->outputFile();
257 ZYPP_THROW( PluginScriptException( "Bad file pointer." ) );
259 int fd = ::fileno( filep );
261 ZYPP_THROW( PluginScriptException( "Bad file descriptor" ) );
263 //DBG << " ->[" << fd << " " << (::feof(filep)?'e':'_') << (::ferror(filep)?'F':'_') << "]" << endl;
265 PluginDumpStderr _dump( *_cmd ); // dump scripts stderr before leaving
266 SignalSaver sigsav( SIGPIPE, SIG_IGN );
267 const char * buffer = data.c_str();
268 ssize_t buffsize = data.size();
275 tv.tv_sec = send_timeout;
278 int retval = select( fd+1, NULL, &wfds, NULL, &tv );
279 if ( retval > 0 ) // FD_ISSET( fd, &wfds ) will be true.
281 //DBG << "Ready to write..." << endl;
282 ssize_t ret = ::write( fd, buffer, buffsize );
283 if ( ret == buffsize )
285 //DBG << "::write(" << buffsize << ") -> " << ret << endl;
291 //WAR << "::write(" << buffsize << ") -> " << ret << " INCOMPLETE..." << endl;
294 buffer += ret; // -> continue
296 else // ( retval == -1 )
298 if ( errno != EINTR )
300 ERR << "write(): " << Errno() << endl;
301 if ( errno == EPIPE )
302 ZYPP_THROW( PluginScriptDiedUnexpectedly( "Send: script died unexpectedly", str::Str() << Errno() ) );
304 ZYPP_THROW( PluginScriptException( "Send: send error", str::Str() << Errno() ) );
308 else if ( retval == 0 )
310 WAR << "Not ready to write within timeout." << endl;
311 ZYPP_THROW( PluginScriptSendTimeout( "Not ready to write within timeout." ) );
313 else // ( retval == -1 )
315 if ( errno != EINTR )
317 ERR << "select(): " << Errno() << endl;
318 ZYPP_THROW( PluginScriptException( "Error waiting on file descriptor", str::Str() << Errno() ) );
325 PluginFrame PluginScript::Impl::receive() const
328 ZYPP_THROW( PluginScriptNotConnected( "Not connected", str::Str() << *this ) );
330 // try reading the pipe....
331 FILE * filep = _cmd->inputFile();
333 ZYPP_THROW( PluginScriptException( "Bad file pointer." ) );
335 int fd = ::fileno( filep );
337 ZYPP_THROW( PluginScriptException( "Bad file descriptor" ) );
342 PluginDebugBuffer _debug( data ); // dump receive buffer if PLUGIN_DEBUG
343 PluginDumpStderr _dump( *_cmd ); // dump scripts stderr before leaving
345 int ch = fgetc( filep );
348 data.push_back( ch );
352 else if ( ::feof( filep ) )
354 WAR << "Unexpected EOF" << endl;
355 ZYPP_THROW( PluginScriptDiedUnexpectedly( "Receive: script died unexpectedly", str::Str() << Errno() ) );
357 else if ( errno != EINTR )
359 if ( errno == EWOULDBLOCK )
361 // wait a while for fd to become ready for reading...
367 tv.tv_sec = receive_timeout;
370 int retval = select( fd+1, &rfds, NULL, NULL, &tv );
371 if ( retval > 0 ) // FD_ISSET( fd, &rfds ) will be true.
375 else if ( retval == 0 )
377 WAR << "Not ready to read within timeout." << endl;
378 ZYPP_THROW( PluginScriptReceiveTimeout( "Not ready to read within timeout." ) );
380 else // ( retval == -1 )
382 if ( errno != EINTR )
384 ERR << "select(): " << Errno() << endl;
385 ZYPP_THROW( PluginScriptException( "Error waiting on file descriptor", str::Str() << Errno() ) );
391 ERR << "read(): " << Errno() << endl;
392 ZYPP_THROW( PluginScriptException( "Receive: receive error", str::Str() << Errno() ) );
397 DBG << " <-read " << data.size() << endl;
398 std::istringstream datas( data );
399 PluginFrame ret( datas );
404 ///////////////////////////////////////////////////////////////////
406 // CLASS NAME : PluginScript
408 ///////////////////////////////////////////////////////////////////
410 const pid_t PluginScript::NotConnected( -1 );
412 PluginScript::PluginScript()
416 PluginScript::PluginScript( const Pathname & script_r )
417 : _pimpl( new Impl( script_r ) )
420 PluginScript::PluginScript( const Pathname & script_r, const Arguments & args_r )
421 : _pimpl( new Impl( script_r, args_r ) )
424 const Pathname & PluginScript::script() const
425 { return _pimpl->script(); }
427 const PluginScript::Arguments & PluginScript::args() const
428 { return _pimpl->args(); }
430 bool PluginScript::isOpen() const
431 { return _pimpl->isOpen(); }
433 pid_t PluginScript::getPid() const
434 { return _pimpl->getPid(); }
436 int PluginScript::lastReturn() const
437 { return _pimpl->lastReturn(); }
439 const std::string & PluginScript::lastExecError() const
440 { return _pimpl->lastExecError(); }
442 void PluginScript::open()
443 { _pimpl->open( _pimpl->script(), _pimpl->args() ); }
445 void PluginScript::open( const Pathname & script_r )
446 { _pimpl->open( script_r ); }
448 void PluginScript::open( const Pathname & script_r, const Arguments & args_r )
449 { _pimpl->open( script_r, args_r ); }
451 int PluginScript::close()
452 { return _pimpl->close(); }
454 void PluginScript::send( const PluginFrame & frame_r ) const
455 { _pimpl->send( frame_r ); }
457 PluginFrame PluginScript::receive() const
458 { return _pimpl->receive(); }
460 ///////////////////////////////////////////////////////////////////
462 std::ostream & operator<<( std::ostream & str, const PluginScript & obj )
463 { return str << *obj._pimpl; }
465 /////////////////////////////////////////////////////////////////
467 ///////////////////////////////////////////////////////////////////