libzypp 17.31.28
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
30using std::endl;
31
32#undef ZYPP_BASE_LOGGER_LOGGROUP
33#define ZYPP_BASE_LOGGER_LOGGROUP "zypp::plugin"
34
36namespace 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
124 ~ Impl()
125 { try { close(); } catch(...) {} }
126
127 public:
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
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;
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
439
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
477 { return _pimpl->isOpen(); }
478
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
ExternalProgramWithStderr & _prog
Definition: PluginScript.cc:79
const std::string & _buffer
Definition: PluginScript.cc:64
Integral type with defined initial value when default constructed.
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
const std::string & body() const
Return the frame body.
Definition: PluginFrame.cc:292
const std::string & command() const
Return the frame command.
Definition: PluginFrame.cc:286
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
bool isAckCommand() const
Convenience to identify an ACK command.
Definition: PluginFrame.h:106
std::ostream & writeTo(std::ostream &stream_r) const
Write frame to stream.
Definition: PluginFrame.cc:301
Base class for PluginScript Exception.
Interface to plugin scripts using a Stomp inspired communication protocol.
Definition: PluginScript.h:63
PluginFrame receive() const
Receive a PluginFrame.
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.
Definition: PluginScript.h:188
std::vector< std::string > Arguments
Commandline arguments passed to a script on open.
Definition: PluginScript.h:68
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.
Definition: PluginScript.h:71
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:221
const std::string & asString() const
String representation.
Definition: Pathname.h:91
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:64
TInt strtonum(const C_Str &str)
Parsing numbers from string.
Easy-to use interface to the ZYPP dependency resolver.
Definition: CodePitfalls.doc:2
std::ostream & dumpRangeLine(std::ostream &str, TIterator begin, TIterator end)
Print range defined by iterators (single line style).
Definition: LogTools.h:115
std::ostream & operator<<(std::ostream &str, const SerialNumber &obj)
Definition: SerialNumber.cc:52
PluginScript implementation.
PluginFrame receive() const
static long _defaultSendTimeout
DefaultIntegral< int, 0 > _lastReturn
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
static long _defaultReceiveTimeout
const std::string & lastExecError() const
Impl(const Pathname &script_r=Pathname(), const 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:428
#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