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