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