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>
24#include <zypp/base/Signal.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
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
151 try {
152 close();
153 } catch (...) {
154 }
155 }
156
157 public:
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:
200 std::string _lastExecError;
201 };
202
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
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
477
480
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
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
518 { return _pimpl->isOpen(); }
519
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
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition Exception.h:459
#define DBG
Definition Logger.h:102
#define ERR
Definition Logger.h:105
#define L_WAR(GROUP)
Definition Logger.h:113
#define WAR
Definition Logger.h:104
#define L_DBG(GROUP)
Definition Logger.h:111
struct _GPollFD GPollFD
Definition ZYppImpl.h:26
std::string asString() const
Definition ByteArray.h:25
Integral type with defined initial value when default constructed.
Convenience errno wrapper.
Definition Errno.h:26
ExternalProgram extended to offer reading programs stderr.
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
Command frame for communication with PluginScript.
Definition PluginFrame.h:42
const ByteArray & body() const
Return the frame body.
const std::string & command() const
Return the frame command.
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.
bool isAckCommand() const
Convenience to identify an ACK command.
std::ostream & writeTo(std::ostream &stream_r) const
Write frame to stream.
Base class for PluginScript Exception.
PluginFrame receive() const
Receive a PluginFrame.
std::vector< std::string > Arguments
Commandline arguments passed to a script on open.
Pathname getChroot() const
Return a connected scripts chroot or an empty Pathname.
void openChrooted(const Pathname &chroot_r)
Like open() but runs the script chrooted into chroot_r.
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:226
const std::string & asString() const
String representation.
Definition Pathname.h:94
Definition ansi.h:855
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
std::string sprint(Args &&... args)
Print words as string.
Definition LogTools.h:266
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:432
std::ostream & operator<<(std::ostream &str, const Capabilities &obj)
relates: Capabilities Stream output
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
Pathname getChroot() const
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
void open(const Pathname &script_r=Pathname(), const Arguments &args_r=Arguments(), const Pathname &root_r=Pathname())
Convenient building of std::string via std::ostringstream Basically a std::ostringstream autoconverti...
Definition String.h:213