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