libzypp 17.32.5
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>
29#include <zypp/PathInfo.h>
30
31using std::endl;
32
33#undef ZYPP_BASE_LOGGER_LOGGROUP
34#define ZYPP_BASE_LOGGER_LOGGROUP "zypp::plugin"
35
37namespace 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:
132
133 Impl(const Impl &) = delete;
134 Impl(Impl &&) = delete;
135 Impl &operator=(const Impl &) = delete;
136 Impl &operator=(Impl &&) = delete;
137
139 try {
140 close();
141 } catch (...) {
142 }
143 }
144
145 public:
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:
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
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 {
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
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() );
231
232 // Be protected against full pipe, etc.
233 setNonBlocking( _cmd->outputFile() );
234 setNonBlocking( _cmd->inputFile() );
235
236 // store running scripts data
238 _args = args_r;
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" ) );
255 if ( ret.isAckCommand() )
256 {
257 doKill = false;
258 str::strtonum( ret.getHeaderNT( "exit" ), _lastReturn.get() );
259 _lastExecError = ret.body();
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
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
313 const char * buffer = data.c_str();
314 ssize_t buffsize = data.size();
315 do {
317 watchFd.fd = fd;
318 watchFd.events = G_IO_OUT | G_IO_ERR;
319 watchFd.revents = 0;
320
321 int retval = g_poll( &watchFd, 1, _sendTimeout * 1000 );
322 if ( retval > 0 ) // FD_ISSET( fd, &wfds ) will be true.
323 {
324 //DBG << "Ready to write..." << endl;
325 ssize_t ret = ::write( fd, buffer, buffsize );
326 if ( ret == buffsize )
327 {
328 //DBG << "::write(" << buffsize << ") -> " << ret << endl;
329 ::fflush( filep );
330 break; // -> done
331 }
332 else if ( ret > 0 )
333 {
334 //WAR << "::write(" << buffsize << ") -> " << ret << " INCOMPLETE..." << endl;
335 ::fflush( filep );
336 buffsize -= ret;
337 buffer += ret; // -> continue
338 }
339 else // ( retval == -1 )
340 {
341 if ( errno != EINTR )
342 {
343 ERR << "write(): " << Errno() << endl;
344 if ( errno == EPIPE )
345 ZYPP_THROW( PluginScriptDiedUnexpectedly( "Send: script died unexpectedly", str::Str() << Errno() ) );
346 else
347 ZYPP_THROW( PluginScriptException( "Send: send error", str::Str() << Errno() ) );
348 }
349 }
350 }
351 else if ( retval == 0 )
352 {
353 WAR << "Not ready to write within timeout." << endl;
354 ZYPP_THROW( PluginScriptSendTimeout( "Not ready to write within timeout." ) );
355 }
356 else // ( retval == -1 )
357 {
358 if ( errno != EINTR )
359 {
360 ERR << "select(): " << Errno() << endl;
361 ZYPP_THROW( PluginScriptException( "Error waiting on file descriptor", str::Str() << Errno() ) );
362 }
363 }
364 } while( true );
365 }
366 }
367
369 {
370 if ( !_cmd )
371 ZYPP_THROW( PluginScriptNotConnected( "Not connected", str::Str() << *this ) );
372
373 // try reading the pipe....
374 FILE * filep = _cmd->inputFile();
375 if ( ! filep )
376 ZYPP_THROW( PluginScriptException( "Bad file pointer." ) );
377
378 int fd = ::fileno( filep );
379 if ( fd == -1 )
380 ZYPP_THROW( PluginScriptException( "Bad file descriptor" ) );
381
382 ::clearerr( filep );
383 std::string data;
384 {
385 PluginDebugBuffer _debug( data ); // dump receive buffer if PLUGIN_DEBUG
386 PluginDumpStderr _dump( *_cmd ); // dump scripts stderr before leaving
387 do {
388 int ch = fgetc( filep );
389 if ( ch != EOF )
390 {
391 data.push_back( ch );
392 if ( ch == '\0' )
393 break;
394 }
395 else if ( ::feof( filep ) )
396 {
397 WAR << "Unexpected EOF" << endl;
398 ZYPP_THROW( PluginScriptDiedUnexpectedly( "Receive: script died unexpectedly", str::Str() << Errno() ) );
399 }
400 else if ( errno != EINTR )
401 {
402 if ( errno == EWOULDBLOCK )
403 {
404 // wait a while for fd to become ready for reading...
405 GPollFD rfd;
406 rfd.fd = fd;
407 rfd.events = G_IO_IN | G_IO_HUP | G_IO_ERR;
408 rfd.revents = 0;
409
410 int retval = g_poll( &rfd, 1, _receiveTimeout * 1000 );
411 if ( retval > 0 ) // rfd.revents was filled
412 {
413 ::clearerr( filep );
414 }
415 else if ( retval == 0 )
416 {
417 WAR << "Not ready to read within timeout." << endl;
418 ZYPP_THROW( PluginScriptReceiveTimeout( "Not ready to read within timeout." ) );
419 }
420 else // ( retval == -1 )
421 {
422 if ( errno != EINTR )
423 {
424 ERR << "select(): " << Errno() << endl;
425 ZYPP_THROW( PluginScriptException( "Error waiting on file descriptor", str::Str() << Errno() ) );
426 }
427 }
428 }
429 else
430 {
431 ERR << "read(): " << Errno() << endl;
432 ZYPP_THROW( PluginScriptException( "Receive: receive error", str::Str() << Errno() ) );
433 }
434 }
435 } while ( true );
436 }
437 // DBG << " <-read " << data.size() << endl;
438 std::istringstream datas( data );
440 DBG << *this << " <-" << ret << endl;
441 return ret;
442 }
443
445 //
446 // CLASS NAME : PluginScript
447 //
449
450 const pid_t PluginScript::NotConnected( -1 );
451
454
457
460
463
465 { return _pimpl->_sendTimeout; }
466
468 { return _pimpl->_receiveTimeout; }
469
471 { _pimpl->_sendTimeout = newval_r > 0 ? newval_r : 0; }
472
474 { _pimpl->_receiveTimeout = newval_r > 0 ? newval_r : 0; }
475
479
481 : _pimpl( new Impl( std::move(script_r) ) )
482 {}
483
487
489 { return _pimpl->script(); }
490
492 { return _pimpl->args(); }
493
495 { return _pimpl->isOpen(); }
496
498 { return _pimpl->getPid(); }
499
501 { return _pimpl->lastReturn(); }
502
503 const std::string & PluginScript::lastExecError() const
504 { return _pimpl->lastExecError(); }
505
507 { _pimpl->open( _pimpl->script(), _pimpl->args() ); }
508
510 { _pimpl->open( script_r ); }
511
513 { _pimpl->open( script_r, args_r ); }
514
516 { return _pimpl->close(); }
517
519 { _pimpl->send( frame_r ); }
520
522 { return _pimpl->receive(); }
523
525
526 std::ostream & operator<<( std::ostream & str, const PluginScript & obj )
527 { return str << *obj._pimpl; }
528
530} // namespace zypp
ExternalProgramWithStderr & _prog
const std::string & _buffer
Reference counted access to a Tp object calling a custom Dispose function when the last AutoDispose h...
Definition AutoDispose.h:95
void swap(AutoDispose &rhs) noexcept
Exchange the contents of two AutoDispose objects.
void reset()
Reset to default Ctor values.
shared_ptr< Impl > _pimpl
DefaultIntegral & reset()
Reset to the defined initial value.
Convenience errno wrapper.
Definition Errno.h:26
ExternalProgram extended to offer reading programs stderr.
Command frame for communication with PluginScript.
Definition PluginFrame.h:41
Base class for PluginScript Exception.
Interface to plugin scripts using a Stomp inspired communication protocol.
PluginFrame receive() const
Receive a PluginFrame.
std::vector< std::string > Arguments
Commandline arguments passed to a script on open.
long sendTimeout() const
Local default timeout (sec.) when sending data.
void send(const PluginFrame &frame_r) const
Send a PluginFrame.
PluginScript()
Default ctor.
RW_pointer< Impl > _pimpl
Pointer to implementation.
int lastReturn() const
Remembers a scripts return value after close until next open.
static long defaultReceiveTimeout()
Global default timeout (sec.) when receiving data.
static const pid_t NotConnected
pid_t(-1) constant indicating no connection.
int close()
Close any open connection.
const std::string & lastExecError() const
Remembers a scripts execError string after close until next open.
pid_t getPid() const
Return a connected scripts pid or NotConnected.
void open()
Setup connection and execute script.
const Pathname & script() const
Return the script path if set.
static long defaultSendTimeout()
Global default timeout (sec.) when sending data.
bool isOpen() const
Whether we are connected to a script.
long receiveTimeout() const
Local default timeout (sec.) when receiving data.
const Arguments & args() const
Return the script arguments if set.
Exception safe signal handler save/restore.
Definition Signal.h:26
Wrapper class for stat/lstat.
Definition PathInfo.h:222
Definition Arch.h:364
String related utilities and Regular expression matching.
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
TInt strtonum(const C_Str &str)
Parsing numbers from string.
Easy-to use interface to the ZYPP dependency resolver.
std::ostream & dumpRangeLine(std::ostream &str, TIterator begin, TIterator end)
Print range defined by iterators (single line style).
Definition LogTools.h:142
std::ostream & operator<<(std::ostream &str, const SerialNumber &obj)
PluginScript implementation.
PluginFrame receive() const
Impl(Impl &&)=delete
Impl(const Impl &)=delete
DefaultIntegral< int, 0 > _lastReturn
Impl & operator=(const Impl &)=delete
const Pathname & script() const
scoped_ptr< ExternalProgramWithStderr > _cmd
void open(const Pathname &script_r=Pathname(), const Arguments &args_r=Arguments())
void send(const PluginFrame &frame_r) const
Impl & operator=(Impl &&)=delete
static long _defaultReceiveTimeout
const std::string & lastExecError() const
Impl(Pathname &&script_r=Pathname(), Arguments &&args_r=Arguments())
const Arguments & args() const
Convenient building of std::string via std::ostringstream Basically a std::ostringstream autoconverti...
Definition String.h:212
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition Exception.h:429
#define DBG
Definition Logger.h:95
#define ERR
Definition Logger.h:98
#define L_WAR(GROUP)
Definition Logger.h:106
#define WAR
Definition Logger.h:97
#define L_DBG(GROUP)
Definition Logger.h:104