libzypp  17.38.8
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 
21 #include <utility>
22 #include <zypp-core/base/DefaultIntegral>
23 #include <zypp-core/base/String.h>
24 #include <zypp/base/Signal.h>
26 
27 #include <zypp/PluginScript.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 
89  std::string take()
90  {
91  auto str = str::Str();
92  std::string line;
93  while ( _prog.stderrGetline( line ) ) {
94  L_WAR("PLUGIN") << "! " << line << endl;
95  str << line << endl;
96  }
97  return str;
98  }
99 
100  ExternalProgramWithStderr & _prog;
101  };
102 
103  inline void setBlocking( FILE * file_r, bool yesno_r = true )
104  {
105  if ( ! file_r )
106  ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
107 
108  int fd = ::fileno( file_r );
109  if ( fd == -1 )
110  ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
111 
112  int flags = ::fcntl( fd, F_GETFL );
113  if ( flags == -1 )
114  ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
115 
116  if ( ! yesno_r )
117  flags |= O_NONBLOCK;
118  else if ( flags & O_NONBLOCK )
119  flags ^= O_NONBLOCK;
120 
121  flags = ::fcntl( fd, F_SETFL, flags );
122  if ( flags == -1 )
123  ZYPP_THROW( PluginScriptException( "setNonBlocking" ) );
124  }
125 
126  inline void setNonBlocking( FILE * file_r, bool yesno_r = true )
127  { setBlocking( file_r, !yesno_r ); }
128  }
129 
131  //
132  // CLASS NAME : PluginScript::Impl
133  //
136  {
137  public:
138  Impl( Pathname &&script_r = Pathname(), Arguments &&args_r = Arguments() )
141  , _script(std::move( script_r ))
142  , _args(std::move( args_r ))
143  {}
144 
145  Impl(const Impl &) = delete;
146  Impl(Impl &&) = delete;
147  Impl &operator=(const Impl &) = delete;
148  Impl &operator=(Impl &&) = delete;
149 
150  ~Impl() {
151  try {
152  close();
153  } catch (...) {
154  }
155  }
156 
157  public:
158  static long _defaultSendTimeout;
160 
163 
164  public:
165  const Pathname & script() const
166  { return _script; }
167 
168  const Arguments & args() const
169  { return _args; }
170 
171  pid_t getPid() const
172  { return _cmd ? _cmd->getpid() : NotConnected; }
173 
175  { return _cmd ? _cmd->chroot() : Pathname(); }
176 
177  bool isOpen() const
178  { return _cmd != nullptr; }
179 
180  int lastReturn() const
181  { return _lastReturn; }
182 
183  const std::string & lastExecError() const
184  { return _lastExecError; }
185 
186  public:
187  void open( const Pathname & script_r = Pathname(), const Arguments & args_r = Arguments(), const Pathname & root_r = Pathname() );
188 
189  int close();
190 
191  void send( const PluginFrame & frame_r ) const;
192 
193  PluginFrame receive() const;
194 
195  private:
198  scoped_ptr<ExternalProgramWithStderr> _cmd;
200  std::string _lastExecError;
201  };
203 
205  inline std::ostream & operator<<( std::ostream & str, const PluginScript::Impl & obj )
206  {
207  return str << "PluginScript[" << obj.getPid() << "] " << Pathname::showRootIf( obj.getChroot(), obj.script() );
208  }
209 
211 
212  namespace
213  {
214  const long PLUGIN_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_TIMEOUT" ) );
215  const long PLUGIN_SEND_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_SEND_TIMEOUT" ) );
216  const long PLUGIN_RECEIVE_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_PLUGIN_RECEIVE_TIMEOUT" ) );
217  }
218 
219  long PluginScript::Impl::_defaultSendTimeout = ( PLUGIN_SEND_TIMEOUT > 0 ? PLUGIN_SEND_TIMEOUT
220  : ( PLUGIN_TIMEOUT > 0 ? PLUGIN_TIMEOUT : 30 ) );
221  long PluginScript::Impl::_defaultReceiveTimeout = ( PLUGIN_RECEIVE_TIMEOUT > 0 ? PLUGIN_RECEIVE_TIMEOUT
222  : ( PLUGIN_TIMEOUT > 0 ? PLUGIN_TIMEOUT : 30 ) );
223 
225 
226  void PluginScript::Impl::open( const Pathname & script_r, const Arguments & args_r, const Pathname & root_r )
227  {
228  dumpRangeLine( DBG << "Open " << Pathname::showRootIf( root_r, script_r ), args_r.begin(), args_r.end() ) << endl;
229 
230  if ( _cmd )
231  ZYPP_THROW( PluginScriptException( "Already connected", str::Str() << *this ) );
232 
233  {
234  PathInfo pi( script_r );
235  if ( ! ( pi.isFile() && pi.isX() ) )
236  ZYPP_THROW( PluginScriptException( "Script is not executable", str::Str() << pi ) );
237  }
238 
239  // go and launch script
240  // TODO: ExternalProgram::maybe use Stderr_To_FileDesc for script loging
241  Arguments args;
242  args.reserve( args_r.size()+1 );
243  args.push_back( script_r.asString() );
244  args.insert( args.end(), args_r.begin(), args_r.end() );
245  _cmd.reset( new ExternalProgramWithStderr( args, root_r ) );
246 
247  if ( not _cmd->running() ) {
248  const std::string execError( _cmd->execError() );
249  _cmd.reset();
250  ZYPP_THROW( PluginScriptException( str::sprint("Failed to run script",script_r), execError ) );
251  }
252 
253  // Be protected against full pipe, etc.
254  setNonBlocking( _cmd->outputFile() );
255  setNonBlocking( _cmd->inputFile() );
256 
257  // store running scripts data
258  _script = script_r;
259  _args = args_r;
260  _lastReturn.reset();
261  _lastExecError.clear();
262 
263  dumpRangeLine( DBG << *this, _args.begin(), _args.end() ) << endl;
264  }
265 
267  {
268  if ( _cmd )
269  {
270  DBG << "Close:" << *this << endl;
271  bool doKill = true;
272  if ( _cmd->running() ) {
273  try {
274  // do not kill script if _DISCONNECT is ACKed.
275  send( PluginFrame( "_DISCONNECT" ) );
276  PluginFrame ret( receive() );
277  if ( ret.isAckCommand() )
278  {
279  doKill = false;
280  str::strtonum( ret.getHeaderNT( "exit" ), _lastReturn.get() );
281  _lastExecError = ret.body().asString();
282  }
283  }
284  catch (...)
285  { /* NOP */ }
286  }
287  if ( doKill )
288  {
289  _cmd->kill();
290  _lastReturn = _cmd->close();
291  _lastExecError = _cmd->execError();
292  }
293  DBG << *this << " -> [" << _lastReturn << "] " << _lastExecError << endl;
294  _cmd.reset();
295  }
296  return _lastReturn;
297  }
298 
299  void PluginScript::Impl::send( const PluginFrame & frame_r ) const
300  {
301  if ( !_cmd )
302  ZYPP_THROW( PluginScriptNotConnected( "Not connected", str::Str() << *this ) );
303 
304  if ( frame_r.command().empty() )
305  WAR << "Send: No command in frame" << frame_r << endl;
306 
307  // prepare frame data to write
308  std::string data;
309  {
310  std::ostringstream datas;
311  frame_r.writeTo( datas );
312  datas.str().swap( data );
313  }
314  DBG << *this << " ->send " << frame_r << endl;
315 
316  if ( PLUGIN_DEBUG )
317  {
318  std::istringstream datas( data );
319  iostr::copyIndent( datas, L_DBG("PLUGIN") ) << endl;
320  }
321 
322  // try writing the pipe....
323  FILE * filep = _cmd->outputFile();
324  if ( ! filep )
325  ZYPP_THROW( PluginScriptException( "Bad file pointer." ) );
326 
327  int fd = ::fileno( filep );
328  if ( fd == -1 )
329  ZYPP_THROW( PluginScriptException( "Bad file descriptor" ) );
330 
331  //DBG << " ->[" << fd << " " << (::feof(filep)?'e':'_') << (::ferror(filep)?'F':'_') << "]" << endl;
332  {
333  PluginDumpStderr _dump( *_cmd ); // dump scripts stderr before leaving
334  SignalSaver sigsav( SIGPIPE, SIG_IGN );
335  const char * buffer = data.c_str();
336  ssize_t buffsize = data.size();
337  do {
338  GPollFD watchFd;
339  watchFd.fd = fd;
340  watchFd.events = G_IO_OUT | G_IO_ERR;
341  watchFd.revents = 0;
342 
343  errno = 0;
344  int retval = g_poll( &watchFd, 1, _sendTimeout * 1000 );
345  if ( retval > 0 ) // FD_ISSET( fd, &wfds ) will be true.
346  {
347  //DBG << "Ready to write..." << endl;
348  ssize_t ret = ::write( fd, buffer, buffsize );
349  if ( ret == buffsize )
350  {
351  //DBG << "::write(" << buffsize << ") -> " << ret << endl;
352  ::fflush( filep );
353  break; // -> done
354  }
355  else if ( ret > 0 )
356  {
357  //WAR << "::write(" << buffsize << ") -> " << ret << " INCOMPLETE..." << endl;
358  ::fflush( filep );
359  buffsize -= ret;
360  buffer += ret; // -> continue
361  }
362  else // ( retval == -1 )
363  {
364  if ( errno != EINTR )
365  {
366  ERR << "write(): " << Errno() << endl;
367  if ( errno == EPIPE )
368  ZYPP_THROW( PluginScriptDiedUnexpectedly( "Send: script died unexpectedly", _dump.take() ) );
369  else
370  ZYPP_THROW( PluginScriptException( "Send: send error", str::Str() << Errno() ) );
371  }
372  }
373  }
374  else if ( retval == 0 )
375  {
376  WAR << "Not ready to write within timeout." << endl;
377  ZYPP_THROW( PluginScriptSendTimeout( "Not ready to write within timeout." ) );
378  }
379  else // ( retval == -1 )
380  {
381  if ( errno != EINTR )
382  {
383  ERR << "select(): " << Errno() << endl;
384  ZYPP_THROW( PluginScriptException( "Error waiting on file descriptor", str::Str() << Errno() ) );
385  }
386  }
387  } while( true );
388  }
389  }
390 
392  {
393  if ( !_cmd )
394  ZYPP_THROW( PluginScriptNotConnected( "Not connected", str::Str() << *this ) );
395 
396  // try reading the pipe....
397  FILE * filep = _cmd->inputFile();
398  if ( ! filep )
399  ZYPP_THROW( PluginScriptException( "Bad file pointer." ) );
400 
401  int fd = ::fileno( filep );
402  if ( fd == -1 )
403  ZYPP_THROW( PluginScriptException( "Bad file descriptor" ) );
404 
405  ::clearerr( filep );
406  std::string data;
407  {
408  PluginDebugBuffer _debug( data ); // dump receive buffer if PLUGIN_DEBUG
409  PluginDumpStderr _dump( *_cmd ); // dump scripts stderr before leaving
410  do {
411  int ch = fgetc( filep );
412  if ( ch != EOF )
413  {
414  data.push_back( ch );
415  if ( ch == '\0' )
416  break;
417  }
418  else if ( ::feof( filep ) )
419  {
420  WAR << "Unexpected EOF" << endl;
421  ZYPP_THROW( PluginScriptDiedUnexpectedly( "Receive: script died unexpectedly", _dump.take() ) );
422  }
423  else if ( errno != EINTR )
424  {
425  if ( errno == EWOULDBLOCK )
426  {
427  // wait a while for fd to become ready for reading...
428  GPollFD rfd;
429  rfd.fd = fd;
430  rfd.events = G_IO_IN | G_IO_HUP | G_IO_ERR;
431  rfd.revents = 0;
432 
433  int retval = g_poll( &rfd, 1, _receiveTimeout * 1000 );
434  if ( retval > 0 ) // rfd.revents was filled
435  {
436  ::clearerr( filep );
437  }
438  else if ( retval == 0 )
439  {
440  WAR << "Not ready to read within timeout." << endl;
441  ZYPP_THROW( PluginScriptReceiveTimeout( "Not ready to read within timeout." ) );
442  }
443  else // ( retval == -1 )
444  {
445  if ( errno != EINTR )
446  {
447  ERR << "select(): " << Errno() << endl;
448  ZYPP_THROW( PluginScriptException( "Error waiting on file descriptor", str::Str() << Errno() ) );
449  }
450  }
451  }
452  else
453  {
454  ERR << "read(): " << Errno() << endl;
455  ZYPP_THROW( PluginScriptException( "Receive: receive error", str::Str() << Errno() ) );
456  }
457  }
458  } while ( true );
459  }
460  // DBG << " <-read " << data.size() << endl;
461  std::istringstream datas( data );
462  PluginFrame ret( datas );
463  DBG << *this << " <-" << ret << endl;
464  return ret;
465  }
466 
468  //
469  // CLASS NAME : PluginScript
470  //
472 
473  const pid_t PluginScript::NotConnected( -1 );
474 
476  { return Impl::_defaultSendTimeout; }
477 
479  { return Impl::_defaultReceiveTimeout; }
480 
481  void PluginScript::defaultSendTimeout( long newval_r )
482  { Impl::_defaultSendTimeout = newval_r > 0 ? newval_r : 0; }
483 
485  { Impl::_defaultReceiveTimeout = newval_r > 0 ? newval_r : 0; }
486 
488  { return _pimpl->_sendTimeout; }
489 
491  { return _pimpl->_receiveTimeout; }
492 
493  void PluginScript::sendTimeout( long newval_r )
494  { _pimpl->_sendTimeout = newval_r > 0 ? newval_r : 0; }
495 
496  void PluginScript::receiveTimeout( long newval_r )
497  { _pimpl->_receiveTimeout = newval_r > 0 ? newval_r : 0; }
498 
500  : _pimpl( new Impl )
501  {}
502 
504  : _pimpl( new Impl( std::move(script_r) ) )
505  {}
506 
508  : _pimpl( new Impl( std::move(script_r), std::move(args_r) ) )
509  {}
510 
512  { return _pimpl->script(); }
513 
515  { return _pimpl->args(); }
516 
517  bool PluginScript::isOpen() const
518  { return _pimpl->isOpen(); }
519 
520  pid_t PluginScript::getPid() const
521  { return _pimpl->getPid(); }
522 
524  { return _pimpl->getChroot(); }
525 
527  { return _pimpl->lastReturn(); }
528 
529  const std::string & PluginScript::lastExecError() const
530  { return _pimpl->lastExecError(); }
531 
533  { _pimpl->open( _pimpl->script(), _pimpl->args() ); }
534 
535  void PluginScript::open( const Pathname & script_r )
536  { _pimpl->open( script_r ); }
537 
538  void PluginScript::open( const Pathname & script_r, const Arguments & args_r )
539  { _pimpl->open( script_r, args_r ); }
540 
541  void PluginScript::open( const Pathname & script_r, const Pathname & chroot_r )
542  { _pimpl->open( script_r, Arguments(), chroot_r ); }
543 
544  void PluginScript::open( const Pathname & script_r, const Arguments & args_r, const Pathname & chroot_r )
545  { _pimpl->open( script_r, args_r, chroot_r ); }
546 
547  void PluginScript::openChrooted( const Pathname & chroot_r )
548  { _pimpl->open( _pimpl->script(), _pimpl->args(), chroot_r ); }
549 
551  { return _pimpl->close(); }
552 
553  void PluginScript::send( const PluginFrame & frame_r ) const
554  { _pimpl->send( frame_r ); }
555 
557  { return _pimpl->receive(); }
558 
560 
561  std::ostream & operator<<( std::ostream & str, const PluginScript & obj )
562  { return str << *obj._pimpl; }
563 
565 } // namespace zypp
Base class for PluginScript Exception.
std::ostream & writeTo(std::ostream &stream_r) const
Write frame to stream.
Definition: PluginFrame.cc:460
#define L_WAR(GROUP)
Definition: Logger.h:113
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition: Exception.h:459
ExternalProgramWithStderr & _prog
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:439
ExternalProgram extended to offer reading programs stderr.
const ByteArray & body() const
Return the frame body.
Definition: PluginFrame.cc:445
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
static std::string showRootIf(const Pathname &root_r, const Pathname &path_r)
String representation as "(root)/path", unless root is "/" or empty.
Definition: Pathname.cc:195
std::string asString() const
Definition: ByteArray.h:25
DefaultIntegral & reset()
Reset to the defined initial value.
String related utilities and Regular expression matching.
PluginScript()
Default ctor.
const Pathname & script() const
Return the script path if set.
Definition: ansi.h:854
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:105
const Pathname & script() const
struct _GPollFD GPollFD
Definition: ZYppImpl.h:26
static long _defaultSendTimeout
void open(const Pathname &script_r=Pathname(), const Arguments &args_r=Arguments(), const Pathname &root_r=Pathname())
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:212
RW_pointer< Impl > _pimpl
Pointer to implementation.
Definition: PluginScript.h:207
const std::string & _buffer
Definition: PluginScript.cc:69
const std::string & asString() const
String representation.
Definition: Pathname.h:94
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:104
static long defaultReceiveTimeout()
Global default timeout (sec.) when receiving data.
Pathname getChroot() const
Return a connected scripts chroot or an empty Pathname.
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:459
long sendTimeout() const
Local default timeout (sec.) when sending data.
void open()
Setup connection and execute script.
Pathname getChroot() const
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:111
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::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:475
std::ostream & dumpRangeLine(std::ostream &str, TIterator begin, TIterator end)
Print range defined by iterators (single line style).
Definition: LogTools.h:432
Wrapper class for ::stat/::lstat.
Definition: PathInfo.h:225
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
std::ostream & operator<<(std::ostream &str, const Capabilities &obj)
relates: Capabilities Stream output
Definition: Capabilities.cc:65
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
std::string sprint(Args &&... args)
Print words as string.
Definition: LogTools.h:266
scoped_ptr< ExternalProgramWithStderr > _cmd
bool isOpen() const
Whether we are connected to a script.
#define DBG
Definition: Logger.h:102
void openChrooted(const Pathname &chroot_r)
Like open() but runs the script chrooted into chroot_r.