Imported Upstream version 17.13.0
[platform/upstream/libzypp.git] / zypp / ZYppFactory.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file       zypp/ZYppFactory.cc
10  *
11 */
12 extern "C"
13 {
14 #include <sys/file.h>
15 }
16 #include <iostream>
17 #include <fstream>
18 #include <signal.h>
19
20 #include "zypp/base/Logger.h"
21 #include "zypp/base/Gettext.h"
22 #include "zypp/base/IOStream.h"
23 #include "zypp/base/Functional.h"
24 #include "zypp/base/Backtrace.h"
25 #include "zypp/PathInfo.h"
26
27 #include "zypp/ZYppFactory.h"
28 #include "zypp/zypp_detail/ZYppImpl.h"
29 #include "zypp/zypp_detail/ZYppReadOnlyHack.h"
30
31 #include <boost/interprocess/sync/file_lock.hpp>
32 #include <boost/interprocess/sync/scoped_lock.hpp>
33 #include <boost/interprocess/sync/sharable_lock.hpp>
34
35 using boost::interprocess::file_lock;
36 using boost::interprocess::scoped_lock;
37 using boost::interprocess::sharable_lock;
38
39 using std::endl;
40
41 namespace zyppintern { void repoVariablesReset(); }     // upon re-acquiring the lock...
42
43 ///////////////////////////////////////////////////////////////////
44 namespace zypp
45 { /////////////////////////////////////////////////////////////////
46
47   namespace
48   {
49     void sigsegvHandler( int sig );
50     ::sighandler_t lastSigsegvHandler = ::signal( SIGSEGV, sigsegvHandler );
51
52     /** SIGSEGV handler to log stack trace */
53     void sigsegvHandler( int sig )
54     {
55       INT << "Error: signal " << sig << endl << dumpBacktrace << endl;
56       ::signal( SIGSEGV, lastSigsegvHandler );
57     }
58   }
59
60   namespace env
61   {
62     /** Hack to circumvent the currently poor --root support. */
63     inline Pathname ZYPP_LOCKFILE_ROOT()
64     { return getenv("ZYPP_LOCKFILE_ROOT") ? getenv("ZYPP_LOCKFILE_ROOT") : "/"; }
65   }
66
67   ///////////////////////////////////////////////////////////////////
68   namespace zypp_readonly_hack
69   { /////////////////////////////////////////////////////////////////
70
71     static bool active = getenv("ZYPP_READONLY_HACK");
72
73     void IWantIt()
74     {
75       active = true;
76       MIL << "ZYPP_READONLY promised." <<  endl;
77     }
78
79     bool IGotIt()
80     {
81       return active;
82     }
83
84     /////////////////////////////////////////////////////////////////
85   } // namespace zypp_readonly_hack
86   ///////////////////////////////////////////////////////////////////
87
88   ///////////////////////////////////////////////////////////////////
89   /// \class ZYppGlobalLock
90   /// \brief Our broken global lock
91   ///
92   ///////////////////////////////////////////////////////////////////
93   class ZYppGlobalLock
94   {
95   public:
96     ZYppGlobalLock()
97     : _zyppLockFilePath( env::ZYPP_LOCKFILE_ROOT() / "/var/run/zypp.pid" )
98     , _zyppLockFile( NULL )
99     , _lockerPid( 0 )
100     , _cleanLock( false )
101     {
102       filesystem::assert_dir(_zyppLockFilePath.dirname() );
103     }
104
105     ~ZYppGlobalLock()
106     {
107         if ( _cleanLock )
108         try {
109           // Exception safe access to the lockfile.
110           ScopedGuard closeOnReturn( accessLockFile() );
111           {
112             scoped_lock<file_lock> flock( _zyppLockFileLock );  // aquire write lock
113             // Truncate the file rather than deleting it. Other processes may
114             // still use it to synchronsize.
115             ftruncate( fileno(_zyppLockFile), 0 );
116           }
117           MIL << "Cleanned lock file. (" << getpid() << ")" << std::endl;
118         }
119         catch(...) {} // let no exception escape.
120     }
121
122     pid_t lockerPid() const
123     { return _lockerPid; }
124
125     const std::string & lockerName() const
126     { return _lockerName; }
127
128     const Pathname & zyppLockFilePath() const
129     { return _zyppLockFilePath; }
130
131
132   private:
133     Pathname    _zyppLockFilePath;
134     file_lock   _zyppLockFileLock;
135     FILE *      _zyppLockFile;
136
137     pid_t       _lockerPid;
138     std::string _lockerName;
139     bool        _cleanLock;
140
141   private:
142     typedef shared_ptr<void> ScopedGuard;
143
144     /** Exception safe access to the lockfile.
145      * \code
146      *   // Exception safe access to the lockfile.
147      *   ScopedGuard closeOnReturn( accessLockFile() );
148      * \endcode
149      */
150     ScopedGuard accessLockFile()
151     {
152       _openLockFile();
153       return ScopedGuard( static_cast<void*>(0),
154                           bind( mem_fun_ref( &ZYppGlobalLock::_closeLockFile ), ref(*this) ) );
155     }
156
157     /** Use \ref accessLockFile. */
158     void _openLockFile()
159     {
160       if ( _zyppLockFile != NULL )
161         return; // is open
162
163       // open pid file rw so we are sure it exist when creating the flock
164       _zyppLockFile = fopen( _zyppLockFilePath.c_str(), "a+" );
165       if ( _zyppLockFile == NULL )
166         ZYPP_THROW( Exception( "Cant open " + _zyppLockFilePath.asString() ) );
167       _zyppLockFileLock = _zyppLockFilePath.c_str();
168       MIL << "Open lockfile " << _zyppLockFilePath << endl;
169     }
170
171     /** Use \ref accessLockFile. */
172     void _closeLockFile()
173     {
174       if ( _zyppLockFile == NULL )
175         return; // is closed
176
177       clearerr( _zyppLockFile );
178       fflush( _zyppLockFile );
179       // http://www.boost.org/doc/libs/1_50_0/doc/html/interprocess/synchronization_mechanisms.html
180       // If you are using a std::fstream/native file handle to write to the file
181       // while using file locks on that file, don't close the file before releasing
182       // all the locks of the file.
183       _zyppLockFileLock = file_lock();
184       fclose( _zyppLockFile );
185       _zyppLockFile = NULL;
186       MIL << "Close lockfile " << _zyppLockFilePath << endl;
187     }
188
189
190     bool isProcessRunning( pid_t pid_r )
191     {
192       // it is another program, not me, see if it is still running
193       Pathname procdir( "/proc"/str::numstring(pid_r) );
194       PathInfo status( procdir );
195       MIL << "Checking " <<  status << endl;
196
197       if ( ! status.isDir() )
198       {
199         DBG << "No such process." << endl;
200         return false;
201       }
202
203       static char buffer[513];
204       buffer[0] = buffer[512] = 0;
205       // man proc(5): /proc/[pid]/cmdline is empty if zombie.
206       if ( std::ifstream( (procdir/"cmdline").c_str() ).read( buffer, 512 ).gcount() > 0 )
207       {
208         _lockerName = buffer;
209         DBG << "Is running: " <<  _lockerName << endl;
210         return true;
211       }
212
213       DBG << "In zombie state." << endl;
214       return false;
215     }
216
217     pid_t readLockFile()
218     {
219       clearerr( _zyppLockFile );
220       fseek( _zyppLockFile, 0, SEEK_SET );
221       long readpid = 0;
222       fscanf( _zyppLockFile, "%ld", &readpid );
223       MIL << "read: Lockfile " << _zyppLockFilePath << " has pid " << readpid << " (our pid: " << getpid() << ") "<< std::endl;
224       return (pid_t)readpid;
225     }
226
227     void writeLockFile()
228     {
229       clearerr( _zyppLockFile );
230       fseek( _zyppLockFile, 0, SEEK_SET );
231       ftruncate( fileno(_zyppLockFile), 0 );
232       fprintf(_zyppLockFile, "%ld\n", (long)getpid() );
233       fflush( _zyppLockFile );
234       _cleanLock = true; // cleanup on exit
235       MIL << "write: Lockfile " << _zyppLockFilePath << " got pid " <<  getpid() << std::endl;
236     }
237
238   public:
239
240     /** Try to aquire a lock.
241      * \return \c true if zypp is already locked by another process.
242      */
243     bool zyppLocked()
244     {
245       if ( geteuid() != 0 )
246         return false;   // no lock as non-root
247
248       // Exception safe access to the lockfile.
249       ScopedGuard closeOnReturn( accessLockFile() );
250       {
251         scoped_lock<file_lock> flock( _zyppLockFileLock );      // aquire write lock
252
253         _lockerPid = readLockFile();
254         if ( _lockerPid == 0 )
255         {
256           // no or empty lock file
257           writeLockFile();
258           return false;
259         }
260         else if ( _lockerPid == getpid() )
261         {
262           // keep my own lock
263           return false;
264         }
265         else
266         {
267           // a foreign pid in lock
268           if ( isProcessRunning( _lockerPid ) )
269           {
270             WAR << _lockerPid << " is running and has a ZYpp lock. Sorry." << std::endl;
271             return true;
272           }
273           else
274           {
275             MIL << _lockerPid << " is dead. Taking the lock file." << std::endl;
276             writeLockFile();
277             return false;
278           }
279         }
280       }
281       INT << "Oops! We should not be here!" << std::endl;
282       return true;
283     }
284
285   };
286
287   ///////////////////////////////////////////////////////////////////
288   namespace
289   {
290     static weak_ptr<ZYpp>               _theZYppInstance;
291     static scoped_ptr<ZYppGlobalLock>   _theGlobalLock;         // on/off in sync with _theZYppInstance
292
293     ZYppGlobalLock & globalLock()
294     {
295       if ( !_theGlobalLock )
296         _theGlobalLock.reset( new ZYppGlobalLock );
297       return *_theGlobalLock;
298     }
299   } //namespace
300   ///////////////////////////////////////////////////////////////////
301
302   ///////////////////////////////////////////////////////////////////
303   //
304   //    CLASS NAME : ZYpp
305   //
306   ///////////////////////////////////////////////////////////////////
307
308   ZYpp::ZYpp( const Impl_Ptr & impl_r )
309   : _pimpl( impl_r )
310   {
311     ::zyppintern::repoVariablesReset(); // upon re-acquiring the lock...
312     MIL << "ZYpp is on..." << endl;
313   }
314
315   ZYpp::~ZYpp()
316   {
317     _theGlobalLock.reset();
318     MIL << "ZYpp is off..." << endl;
319   }
320
321   ///////////////////////////////////////////////////////////////////
322   //
323   //    CLASS NAME : ZYppFactoryException
324   //
325   ///////////////////////////////////////////////////////////////////
326
327   ZYppFactoryException::ZYppFactoryException( const std::string & msg_r, pid_t lockerPid_r, const std::string & lockerName_r )
328     : Exception( msg_r )
329     , _lockerPid( lockerPid_r )
330     , _lockerName( lockerName_r )
331   {}
332
333   ZYppFactoryException::~ZYppFactoryException() throw ()
334   {}
335
336   ///////////////////////////////////////////////////////////////////
337   //
338   //    CLASS NAME : ZYppFactory
339   //
340   ///////////////////////////////////////////////////////////////////
341
342   ZYppFactory ZYppFactory::instance()
343   { return ZYppFactory(); }
344
345   ZYppFactory::ZYppFactory()
346   {}
347
348   ZYppFactory::~ZYppFactory()
349   {}
350
351   ///////////////////////////////////////////////////////////////////
352   //
353   ZYpp::Ptr ZYppFactory::getZYpp() const
354   {
355     ZYpp::Ptr _instance = _theZYppInstance.lock();
356     if ( ! _instance )
357     {
358       if ( geteuid() != 0 )
359       {
360         MIL << "Running as user. Skip creating " << globalLock().zyppLockFilePath() << std::endl;
361       }
362       else if ( zypp_readonly_hack::active )
363       {
364         MIL << "ZYPP_READONLY active." << endl;
365       }
366       else if ( globalLock().zyppLocked() )
367       {
368         bool failed = true;
369         const long LOCK_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_LOCK_TIMEOUT" ) );
370         if ( LOCK_TIMEOUT > 0 )
371         {
372           MIL << "Waiting whether pid " << globalLock().lockerPid() << " ends within $LOCK_TIMEOUT=" << LOCK_TIMEOUT << " sec." << endl;
373           unsigned delay = 1;
374           Pathname procdir( "/proc"/str::numstring(globalLock().lockerPid()) );
375           for ( long i = 0; i < LOCK_TIMEOUT; i += delay )
376           {
377             if ( PathInfo( procdir ).isDir() )  // wait for /proc/pid to disapear
378               sleep( delay );
379             else
380             {
381               MIL << "Retry after " << i << " sec." << endl;
382               failed = globalLock().zyppLocked();
383               if ( failed )
384               {
385                 // another proc locked faster. maybe it ends fast as well....
386                 MIL << "Waiting whether pid " << globalLock().lockerPid() << " ends within " << (LOCK_TIMEOUT-i) << " sec." << endl;
387                 procdir = Pathname( "/proc"/str::numstring(globalLock().lockerPid()) );
388               }
389               else
390               {
391                 MIL << "Finally got the lock!" << endl;
392                 break;  // gotcha
393               }
394             }
395           }
396         }
397         if ( failed )
398         {
399           std::string t = str::form(_("System management is locked by the application with pid %d (%s).\n"
400                                       "Close this application before trying again."),
401                                       globalLock().lockerPid(),
402                                       globalLock().lockerName().c_str()
403                                     );
404           ZYPP_THROW(ZYppFactoryException(t, globalLock().lockerPid(), globalLock().lockerName() ));
405         }
406       }
407       // Here we go...
408       static ZYpp::Impl_Ptr _theImplInstance;   // for now created once
409       if ( !_theImplInstance )
410         _theImplInstance.reset( new ZYpp::Impl );
411       _instance.reset( new ZYpp( _theImplInstance ) );
412       _theZYppInstance = _instance;
413     }
414
415     return _instance;
416   }
417
418   ///////////////////////////////////////////////////////////////////
419   //
420   bool ZYppFactory::haveZYpp() const
421   { return !_theZYppInstance.expired(); }
422
423   /******************************************************************
424   **
425   **    FUNCTION NAME : operator<<
426   **    FUNCTION TYPE : std::ostream &
427   */
428   std::ostream & operator<<( std::ostream & str, const ZYppFactory & obj )
429   {
430     return str << "ZYppFactory";
431   }
432
433   /////////////////////////////////////////////////////////////////
434 } // namespace zypp
435 ///////////////////////////////////////////////////////////////////