Imported Upstream version 16.3.2
[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 #undef  ZYPP_BASE_LOGGER_LOGGROUP
31 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::plugin"
32
33 ///////////////////////////////////////////////////////////////////
34 namespace zypp
35 { /////////////////////////////////////////////////////////////////
36
37   namespace
38   {
39     const char * PLUGIN_DEBUG = getenv( "ZYPP_PLUGIN_DEBUG" );
40
41     /** Dump buffer string if PLUGIN_DEBUG is on.
42      * \ingroup g_RAII
43      */
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
65     /** Dump programms stderr.
66      * \ingroup g_RAII
67      */
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
107   ///////////////////////////////////////////////////////////////////
108   //
109   //    CLASS NAME : PluginScript::Impl
110   //
111   /** \ref PluginScript implementation. */
112   struct PluginScript::Impl
113   {
114     public:
115       Impl( const Pathname & script_r = Pathname(), const Arguments & args_r = Arguments() )
116         : _sendTimeout( _defaultSendTimeout )
117         , _receiveTimeout( _defaultReceiveTimeout )
118         , _script( script_r )
119         , _args( args_r )
120       {}
121
122       ~ Impl()
123       { try { close(); } catch(...) {} }
124
125     public:
126       static long _defaultSendTimeout;
127       static long _defaultReceiveTimeout;
128
129       long _sendTimeout;
130       long _receiveTimeout;
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:
161       Pathname _script;
162       Arguments _args;
163       scoped_ptr<ExternalProgramWithStderr> _cmd;
164       DefaultIntegral<int,0> _lastReturn;
165       std::string _lastExecError;
166   };
167   ///////////////////////////////////////////////////////////////////
168
169   /** \relates PluginScrip::Impl Stream output */
170   inline std::ostream & operator<<( std::ostream & str, const PluginScript::Impl & obj )
171   {
172     return str << "PluginScript[" << obj.getPid() << "] " << obj.script();
173   }
174
175   ///////////////////////////////////////////////////////////////////
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
189   ///////////////////////////////////////////////////////////////////
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
206     Arguments args;
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;
219     _lastReturn.reset();
220     _lastExecError.clear();
221
222     dumpRangeLine( DBG << *this, _args.begin(), _args.end() ) << endl;
223   }
224
225   int PluginScript::Impl::close()
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
351   PluginFrame PluginScript::Impl::receive() const
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
430   ///////////////////////////////////////////////////////////////////
431   //
432   //    CLASS NAME : PluginScript
433   //
434   ///////////////////////////////////////////////////////////////////
435
436   const pid_t PluginScript::NotConnected( -1 );
437
438   long PluginScript::defaultSendTimeout()
439   { return Impl::_defaultSendTimeout; }
440
441   long PluginScript::defaultReceiveTimeout()
442   { return Impl::_defaultReceiveTimeout; }
443
444   void PluginScript::defaultSendTimeout( long newval_r )
445   { Impl::_defaultSendTimeout = newval_r > 0 ? newval_r : 0; }
446
447   void PluginScript::defaultReceiveTimeout( long newval_r )
448   { Impl::_defaultReceiveTimeout = newval_r > 0 ? newval_r : 0; }
449
450   long PluginScript::sendTimeout() const
451   { return _pimpl->_sendTimeout; }
452
453   long PluginScript::receiveTimeout() const
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
462   PluginScript::PluginScript()
463     : _pimpl( new Impl )
464   {}
465
466   PluginScript::PluginScript( const Pathname & script_r )
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
474   const Pathname & PluginScript::script() const
475   { return _pimpl->script(); }
476
477   const PluginScript::Arguments & PluginScript::args() const
478   { return _pimpl->args(); }
479
480   bool PluginScript::isOpen() const
481   { return _pimpl->isOpen(); }
482
483   pid_t PluginScript::getPid() const
484   { return _pimpl->getPid(); }
485
486   int PluginScript::lastReturn() const
487   { return _pimpl->lastReturn(); }
488
489   const std::string & PluginScript::lastExecError() const
490   { return _pimpl->lastExecError(); }
491
492   void PluginScript::open()
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
501   int PluginScript::close()
502   { return _pimpl->close(); }
503
504   void PluginScript::send( const PluginFrame & frame_r ) const
505   { _pimpl->send( frame_r ); }
506
507   PluginFrame PluginScript::receive() const
508   { return _pimpl->receive(); }
509
510   ///////////////////////////////////////////////////////////////////
511
512   std::ostream & operator<<( std::ostream & str, const PluginScript & obj )
513   { return str << *obj._pimpl; }
514
515   /////////////////////////////////////////////////////////////////
516 } // namespace zypp
517 ///////////////////////////////////////////////////////////////////