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