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