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