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