libzypp  13.10.6
PluginScript.cc
Go to the documentation of this file.
1 /*---------------------------------------------------------------------\
2 | ____ _ __ __ ___ |
3 | |__ / \ / / . \ . \ |
4 | / / \ V /| _/ _/ |
5 | / /__ | | | | | | |
6 | /_____||_| |_| |_| |
7 | |
8 \---------------------------------------------------------------------*/
12 #include <sys/types.h>
13 #include <signal.h>
14 
15 #include <iostream>
16 #include <sstream>
17 
18 #include "zypp/base/LogTools.h"
20 #include "zypp/base/String.h"
21 #include "zypp/base/Signal.h"
22 #include "zypp/base/IOStream.h"
23 
24 #include "zypp/PluginScript.h"
25 #include "zypp/ExternalProgram.h"
26 #include "zypp/PathInfo.h"
27 
28 using std::endl;
29 
31 namespace zypp
32 {
33 
34  namespace
35  {
36  const char * PLUGIN_DEBUG = getenv( "ZYPP_PLUGIN_DEBUG" );
37 
41  struct PluginDebugBuffer
42  {
43  PluginDebugBuffer( const std::string & buffer_r ) : _buffer( buffer_r ) {}
44  ~PluginDebugBuffer()
45  {
46  if ( PLUGIN_DEBUG )
47  {
48  if ( _buffer.empty() )
49  {
50  _DBG("PLUGIN") << "< (empty)" << endl;
51  }
52  else
53  {
54  std::istringstream datas( _buffer );
55  iostr::copyIndent( datas, _DBG("PLUGIN"), "< " ) << endl;
56  }
57  }
58  }
59  const std::string & _buffer;
60  };
61 
65  struct PluginDumpStderr
66  {
67  PluginDumpStderr( ExternalProgramWithStderr & prog_r ) : _prog( prog_r ) {}
68  ~PluginDumpStderr()
69  {
70  std::string line;
71  while ( _prog.stderrGetline( line ) )
72  _WAR("PLUGIN") << "! " << line << endl;
73  }
74  ExternalProgramWithStderr & _prog;
75  };
76 
77  inline void setBlocking( FILE * file_r, bool yesno_r = true )
78  {
79  if ( ! file_r )
80  ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
81 
82  int fd = ::fileno( file_r );
83  if ( fd == -1 )
84  ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
85 
86  int flags = ::fcntl( fd, F_GETFL );
87  if ( flags == -1 )
88  ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
89 
90  if ( ! yesno_r )
91  flags |= O_NONBLOCK;
92  else if ( flags & O_NONBLOCK )
93  flags ^= O_NONBLOCK;
94 
95  flags = ::fcntl( fd, F_SETFL, flags );
96  if ( flags == -1 )
97  ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
98  }
99 
100  inline void setNonBlocking( FILE * file_r, bool yesno_r = true )
101  { setBlocking( file_r, !yesno_r ); }
102  }
103 
105  //
106  // CLASS NAME : PluginScript::Impl
107  //
110  {
111  public:
112  Impl( const Pathname & script_r = Pathname(), const Arguments & args_r = Arguments() )
115  , _script( script_r )
116  , _args( args_r )
117  {}
118 
120  { try { close(); } catch(...) {} }
121 
122  public:
123  static long _defaultSendTimeout;
125 
128 
129  public:
130  const Pathname & script() const
131  { return _script; }
132 
133  const Arguments & args() const
134  { return _args; }
135 
136  pid_t getPid() const
137  { return _cmd ? _cmd->getpid() : NotConnected; }
138 
139  bool isOpen() const
140  { return _cmd != nullptr; }
141 
142  int lastReturn() const
143  { return _lastReturn; }
144 
145  const std::string & lastExecError() const
146  { return _lastExecError; }
147 
148  public:
149  void open( const Pathname & script_r = Pathname(), const Arguments & args_r = Arguments() );
150 
151  int close();
152 
153  void send( const PluginFrame & frame_r ) const;
154 
155  PluginFrame receive() const;
156 
157  private:
158  Pathname _script;
160  scoped_ptr<ExternalProgramWithStderr> _cmd;
162  std::string _lastExecError;
163  };
165 
167  inline std::ostream & operator<<( std::ostream & str, const PluginScript::Impl & obj )
168  {
169  return dumpRangeLine( str << "PluginScript[" << obj.getPid() << "] " << obj.script(),
170  obj.args().begin(), obj.args().end() );
171  }
172 
174 
175  namespace
176  {
177  const long PLUGIN_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_TIMEOUT" ) );
178  const long PLUGIN_SEND_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_SEND_TIMEOUT" ) );
179  const long PLUGIN_RECEIVE_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_RECEIVE_TIMEOUT" ) );
180  }
181 
182  long PluginScript::Impl::_defaultSendTimeout = ( PLUGIN_SEND_TIMEOUT > 0 ? PLUGIN_SEND_TIMEOUT
183  : ( PLUGIN_TIMEOUT > 0 ? PLUGIN_TIMEOUT : 30 ) );
184  long PluginScript::Impl::_defaultReceiveTimeout = ( PLUGIN_RECEIVE_TIMEOUT > 0 ? PLUGIN_RECEIVE_TIMEOUT
185  : ( PLUGIN_TIMEOUT > 0 ? PLUGIN_TIMEOUT : 30 ) );
186 
188 
189  void PluginScript::Impl::open( const Pathname & script_r, const Arguments & args_r )
190  {
191  dumpRangeLine( DBG << "Open " << script_r, args_r.begin(), args_r.end() ) << endl;
192 
193  if ( _cmd )
194  ZYPP_THROW( PluginScriptException( "Already connected", str::Str() << *this ) );
195 
196  {
197  PathInfo pi( script_r );
198  if ( ! ( pi.isFile() && pi.isX() ) )
199  ZYPP_THROW( PluginScriptException( "Script is not executable", str::Str() << pi ) );
200  }
201 
202  // go and launch script
203  // TODO: ExternalProgram::maybe use Stderr_To_FileDesc for script loging
204  Arguments args;
205  args.reserve( args_r.size()+1 );
206  args.push_back( script_r.asString() );
207  args.insert( args.end(), args_r.begin(), args_r.end() );
208  _cmd.reset( new ExternalProgramWithStderr( args ) );
209 
210  // Be protected against full pipe, etc.
211  setNonBlocking( _cmd->outputFile() );
212  setNonBlocking( _cmd->inputFile() );
213 
214  // store running scripts data
215  _script = script_r;
216  _args = args_r;
217  _lastReturn.reset();
218  _lastExecError.clear();
219 
220  DBG << *this << endl;
221  }
222 
224  {
225  if ( _cmd )
226  {
227  DBG << "Close:" << *this << endl;
228  bool doKill = true;
229  try {
230  // do not kill script if _DISCONNECT is ACKed.
231  send( PluginFrame( "_DISCONNECT" ) );
232  PluginFrame ret( receive() );
233  if ( ret.isAckCommand() )
234  {
235  doKill = false;
236  str::strtonum( ret.getHeaderNT( "exit" ), _lastReturn.get() );
237  _lastExecError = ret.body();
238  }
239  }
240  catch (...)
241  { /* NOP */ }
242 
243  if ( doKill )
244  {
245  _cmd->kill();
246  _lastReturn = _cmd->close();
247  _lastExecError = _cmd->execError();
248  }
249  DBG << *this << " -> [" << _lastReturn << "] " << _lastExecError << endl;
250  _cmd.reset();
251  }
252  return _lastReturn;
253  }
254 
255  void PluginScript::Impl::send( const PluginFrame & frame_r ) const
256  {
257  if ( !_cmd )
258  ZYPP_THROW( PluginScriptNotConnected( "Not connected", str::Str() << *this ) );
259 
260  if ( frame_r.command().empty() )
261  WAR << "Send: No command in frame" << frame_r << endl;
262 
263  // prepare frame data to write
264  std::string data;
265  {
266  std::ostringstream datas;
267  frame_r.writeTo( datas );
268  datas.str().swap( data );
269  }
270  DBG << "->send " << frame_r << endl;
271 
272  if ( PLUGIN_DEBUG )
273  {
274  std::istringstream datas( data );
275  iostr::copyIndent( datas, _DBG("PLUGIN") ) << endl;
276  }
277 
278  // try writing the pipe....
279  FILE * filep = _cmd->outputFile();
280  if ( ! filep )
281  ZYPP_THROW( PluginScriptException( "Bad file pointer." ) );
282 
283  int fd = ::fileno( filep );
284  if ( fd == -1 )
285  ZYPP_THROW( PluginScriptException( "Bad file descriptor" ) );
286 
287  //DBG << " ->[" << fd << " " << (::feof(filep)?'e':'_') << (::ferror(filep)?'F':'_') << "]" << endl;
288  {
289  PluginDumpStderr _dump( *_cmd ); // dump scripts stderr before leaving
290  SignalSaver sigsav( SIGPIPE, SIG_IGN );
291  const char * buffer = data.c_str();
292  ssize_t buffsize = data.size();
293  do {
294  fd_set wfds;
295  FD_ZERO( &wfds );
296  FD_SET( fd, &wfds );
297 
298  struct timeval tv;
299  tv.tv_sec = _sendTimeout;
300  tv.tv_usec = 0;
301 
302  int retval = select( fd+1, NULL, &wfds, NULL, &tv );
303  if ( retval > 0 ) // FD_ISSET( fd, &wfds ) will be true.
304  {
305  //DBG << "Ready to write..." << endl;
306  ssize_t ret = ::write( fd, buffer, buffsize );
307  if ( ret == buffsize )
308  {
309  //DBG << "::write(" << buffsize << ") -> " << ret << endl;
310  ::fflush( filep );
311  break; // -> done
312  }
313  else if ( ret > 0 )
314  {
315  //WAR << "::write(" << buffsize << ") -> " << ret << " INCOMPLETE..." << endl;
316  ::fflush( filep );
317  buffsize -= ret;
318  buffer += ret; // -> continue
319  }
320  else // ( retval == -1 )
321  {
322  if ( errno != EINTR )
323  {
324  ERR << "write(): " << Errno() << endl;
325  if ( errno == EPIPE )
326  ZYPP_THROW( PluginScriptDiedUnexpectedly( "Send: script died unexpectedly", str::Str() << Errno() ) );
327  else
328  ZYPP_THROW( PluginScriptException( "Send: send error", str::Str() << Errno() ) );
329  }
330  }
331  }
332  else if ( retval == 0 )
333  {
334  WAR << "Not ready to write within timeout." << endl;
335  ZYPP_THROW( PluginScriptSendTimeout( "Not ready to write within timeout." ) );
336  }
337  else // ( retval == -1 )
338  {
339  if ( errno != EINTR )
340  {
341  ERR << "select(): " << Errno() << endl;
342  ZYPP_THROW( PluginScriptException( "Error waiting on file descriptor", str::Str() << Errno() ) );
343  }
344  }
345  } while( true );
346  }
347  }
348 
350  {
351  if ( !_cmd )
352  ZYPP_THROW( PluginScriptNotConnected( "Not connected", str::Str() << *this ) );
353 
354  // try reading the pipe....
355  FILE * filep = _cmd->inputFile();
356  if ( ! filep )
357  ZYPP_THROW( PluginScriptException( "Bad file pointer." ) );
358 
359  int fd = ::fileno( filep );
360  if ( fd == -1 )
361  ZYPP_THROW( PluginScriptException( "Bad file descriptor" ) );
362 
363  ::clearerr( filep );
364  std::string data;
365  {
366  PluginDebugBuffer _debug( data ); // dump receive buffer if PLUGIN_DEBUG
367  PluginDumpStderr _dump( *_cmd ); // dump scripts stderr before leaving
368  do {
369  int ch = fgetc( filep );
370  if ( ch != EOF )
371  {
372  data.push_back( ch );
373  if ( ch == '\0' )
374  break;
375  }
376  else if ( ::feof( filep ) )
377  {
378  WAR << "Unexpected EOF" << endl;
379  ZYPP_THROW( PluginScriptDiedUnexpectedly( "Receive: script died unexpectedly", str::Str() << Errno() ) );
380  }
381  else if ( errno != EINTR )
382  {
383  if ( errno == EWOULDBLOCK )
384  {
385  // wait a while for fd to become ready for reading...
386  fd_set rfds;
387  FD_ZERO( &rfds );
388  FD_SET( fd, &rfds );
389 
390  struct timeval tv;
391  tv.tv_sec = _receiveTimeout;
392  tv.tv_usec = 0;
393 
394  int retval = select( fd+1, &rfds, NULL, NULL, &tv );
395  if ( retval > 0 ) // FD_ISSET( fd, &rfds ) will be true.
396  {
397  ::clearerr( filep );
398  }
399  else if ( retval == 0 )
400  {
401  WAR << "Not ready to read within timeout." << endl;
402  ZYPP_THROW( PluginScriptReceiveTimeout( "Not ready to read within timeout." ) );
403  }
404  else // ( retval == -1 )
405  {
406  if ( errno != EINTR )
407  {
408  ERR << "select(): " << Errno() << endl;
409  ZYPP_THROW( PluginScriptException( "Error waiting on file descriptor", str::Str() << Errno() ) );
410  }
411  }
412  }
413  else
414  {
415  ERR << "read(): " << Errno() << endl;
416  ZYPP_THROW( PluginScriptException( "Receive: receive error", str::Str() << Errno() ) );
417  }
418  }
419  } while ( true );
420  }
421  // DBG << " <-read " << data.size() << endl;
422  std::istringstream datas( data );
423  PluginFrame ret( datas );
424  DBG << "<-" << ret << endl;
425  return ret;
426  }
427 
429  //
430  // CLASS NAME : PluginScript
431  //
433 
434  const pid_t PluginScript::NotConnected( -1 );
435 
437  { return Impl::_defaultSendTimeout; }
438 
440  { return Impl::_defaultReceiveTimeout; }
441 
442  void PluginScript::defaultSendTimeout( long newval_r )
443  { Impl::_defaultSendTimeout = newval_r > 0 ? newval_r : 0; }
444 
446  { Impl::_defaultReceiveTimeout = newval_r > 0 ? newval_r : 0; }
447 
449  { return _pimpl->_sendTimeout; }
450 
452  { return _pimpl->_receiveTimeout; }
453 
454  void PluginScript::sendTimeout( long newval_r )
455  { _pimpl->_sendTimeout = newval_r > 0 ? newval_r : 0; }
456 
457  void PluginScript::receiveTimeout( long newval_r )
458  { _pimpl->_receiveTimeout = newval_r > 0 ? newval_r : 0; }
459 
461  : _pimpl( new Impl )
462  {}
463 
464  PluginScript::PluginScript( const Pathname & script_r )
465  : _pimpl( new Impl( script_r ) )
466  {}
467 
468  PluginScript::PluginScript( const Pathname & script_r, const Arguments & args_r )
469  : _pimpl( new Impl( script_r, args_r ) )
470  {}
471 
472  const Pathname & PluginScript::script() const
473  { return _pimpl->script(); }
474 
476  { return _pimpl->args(); }
477 
478  bool PluginScript::isOpen() const
479  { return _pimpl->isOpen(); }
480 
481  pid_t PluginScript::getPid() const
482  { return _pimpl->getPid(); }
483 
485  { return _pimpl->lastReturn(); }
486 
487  const std::string & PluginScript::lastExecError() const
488  { return _pimpl->lastExecError(); }
489 
491  { _pimpl->open( _pimpl->script(), _pimpl->args() ); }
492 
493  void PluginScript::open( const Pathname & script_r )
494  { _pimpl->open( script_r ); }
495 
496  void PluginScript::open( const Pathname & script_r, const Arguments & args_r )
497  { _pimpl->open( script_r, args_r ); }
498 
500  { return _pimpl->close(); }
501 
502  void PluginScript::send( const PluginFrame & frame_r ) const
503  { _pimpl->send( frame_r ); }
504 
506  { return _pimpl->receive(); }
507 
509 
510  std::ostream & operator<<( std::ostream & str, const PluginScript & obj )
511  { return str << *obj._pimpl; }
512 
514 } // namespace zypp
Base class for PluginScript Exception.
const Arguments & args() const
Return the script arguments if set.
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition: Exception.h:320
ExternalProgramWithStderr & _prog
Definition: PluginScript.cc:74
Convenience errno wrapper.
Definition: Errno.h:25
Command frame for communication with PluginScript.
Definition: PluginFrame.h:40
std::vector< std::string > Arguments
Commandline arguments passed to a script on open.
Definition: PluginScript.h:68
PluginFrame receive() const
long sendTimeout() const
Local default timeout (sec.) when sending data.
pid_t getPid() const
Return a connected scripts pid or NotConnected.
const Arguments & args() const
const std::string & command() const
Return the frame command.
Definition: PluginFrame.cc:256
PluginScript implementation.
ExternalProgram extended to offer reading programs stderr.
std::ostream & copyIndent(std::istream &from_r, std::ostream &to_r, const std::string &indent_r="> ")
Copy istream to ostream, prefixing each line with indent_r (default &quot;&gt; &quot; ).
Definition: IOStream.h:64
PluginScript()
Default ctor.
static const pid_t NotConnected
pid_t(-1) constant indicating no connection.
Definition: PluginScript.h:71
const std::string & lastExecError() const
#define ERR
Definition: Logger.h:49
void open(const Pathname &script_r=Pathname(), const Arguments &args_r=Arguments())
bool write(const Pathname &path_r, const std::string &key_r, const std::string &val_r, const std::string &newcomment_r)
Add or change a value in sysconfig file path_r.
Definition: Sysconfig.cc:80
static long _defaultSendTimeout
int lastReturn() const
Remembers a scripts return value after close until next open.
static long _defaultReceiveTimeout
Convenient building of std::string via std::ostream::operator&lt;&lt;.
Definition: String.h:193
RW_pointer< Impl > _pimpl
Pointer to implementation.
Definition: PluginScript.h:185
std::ostream & operator<<(std::ostream &str, const Exception &obj)
Definition: Exception.cc:120
const std::string & _buffer
Definition: PluginScript.cc:59
const Pathname & script() const
Return the script path if set.
static long defaultSendTimeout()
Global default timeout (sec.) when sending data.
long receiveTimeout() const
Local default timeout (sec.) when receiving data.
#define WAR
Definition: Logger.h:48
bool isAckCommand() const
Convenience to identify an ACK command.
Definition: PluginFrame.h:92
static long defaultReceiveTimeout()
Global default timeout (sec.) when receiving data.
void send(const PluginFrame &frame_r) const
void open()
Setup connection and execute script.
_It strtonum(const C_Str &str)
Parsing numbers from string.
Definition: String.h:304
PluginFrame receive() const
Receive a PluginFrame.
std::ostream & dumpRangeLine(std::ostream &str, _Iterator begin, _Iterator end)
Print range defined by iterators (single line style).
Definition: LogTools.h:114
const Pathname & script() const
bool isOpen() const
Whether we are connected to a script.
#define _WAR(GROUP)
Definition: Logger.h:57
const std::string & lastExecError() const
Remembers a scripts execError string after close until next open.
DefaultIntegral< int, 0 > _lastReturn
Impl(const Pathname &script_r=Pathname(), const Arguments &args_r=Arguments())
int close()
Close any open connection.
Exception safe signal handler save/restore.
Definition: Signal.h:25
DefaultIntegral & reset()
Reset to the defined initial value.
std::ostream & writeTo(std::ostream &stream_r) const
Write frame to stream.
Definition: PluginFrame.cc:271
Interface to pluigin scripts using a Stomp inspired communication protocol.
Definition: PluginScript.h:62
#define _DBG(GROUP)
Definition: Logger.h:55
void send(const PluginFrame &frame_r) const
Send a PluginFrame.
const std::string & getHeaderNT(const std::string &key_r, const std::string &default_r=std::string()) const
Not throwing version returing one of the matching header values or default_r string.
Definition: PluginFrame.cc:286
scoped_ptr< ExternalProgramWithStderr > _cmd
#define DBG
Definition: Logger.h:46
const std::string & body() const
Return the frame body.
Definition: PluginFrame.cc:262