Imported Upstream version 16.3.2
[platform/upstream/libzypp.git] / zypp / ZYppFactory.cc
index 27afee8..03ea897 100644 (file)
@@ -9,36 +9,64 @@
 /** \file      zypp/ZYppFactory.cc
  *
 */
-
+extern "C"
+{
 #include <sys/file.h>
-#include <cstdio>
-#include <unistd.h>
-#include <fstream>
+}
 #include <iostream>
-#include <sstream>
+#include <fstream>
+#include <signal.h>
 
 #include "zypp/base/Logger.h"
 #include "zypp/base/Gettext.h"
+#include "zypp/base/IOStream.h"
+#include "zypp/base/Functional.h"
+#include "zypp/base/Backtrace.h"
 #include "zypp/PathInfo.h"
 
 #include "zypp/ZYppFactory.h"
 #include "zypp/zypp_detail/ZYppImpl.h"
 #include "zypp/zypp_detail/ZYppReadOnlyHack.h"
 
-#define ZYPP_LOCK_FILE "/var/run/zypp.pid"
+#include <boost/interprocess/sync/file_lock.hpp>
+#include <boost/interprocess/sync/scoped_lock.hpp>
+#include <boost/interprocess/sync/sharable_lock.hpp>
+
+using boost::interprocess::file_lock;
+using boost::interprocess::scoped_lock;
+using boost::interprocess::sharable_lock;
 
 using std::endl;
-using namespace std;
 
 ///////////////////////////////////////////////////////////////////
 namespace zypp
 { /////////////////////////////////////////////////////////////////
 
+  namespace
+  {
+    void sigsegvHandler( int sig );
+    ::sighandler_t lastSigsegvHandler = ::signal( SIGSEGV, sigsegvHandler );
+
+    /** SIGSEGV handler to log stack trace */
+    void sigsegvHandler( int sig )
+    {
+      INT << "Error: signal " << sig << endl << dumpBacktrace << endl;
+      ::signal( SIGSEGV, lastSigsegvHandler );
+    }
+  }
+
+  namespace env
+  {
+    /** Hack to circumvent the currently poor --root support. */
+    inline Pathname ZYPP_LOCKFILE_ROOT()
+    { return getenv("ZYPP_LOCKFILE_ROOT") ? getenv("ZYPP_LOCKFILE_ROOT") : "/"; }
+  }
+
   ///////////////////////////////////////////////////////////////////
   namespace zypp_readonly_hack
   { /////////////////////////////////////////////////////////////////
 
-    static bool active = false;
+    static bool active = getenv("ZYPP_READONLY_HACK");
 
     void IWantIt()
     {
@@ -46,270 +74,272 @@ namespace zypp
       MIL << "ZYPP_READONLY promised." <<  endl;
     }
 
+    bool IGotIt()
+    {
+      return active;
+    }
+
     /////////////////////////////////////////////////////////////////
   } // namespace zypp_readonly_hack
   ///////////////////////////////////////////////////////////////////
 
   ///////////////////////////////////////////////////////////////////
-  //
-  //    CLASS NAME : ZYppGlobalLock
-  //
+  /// \class ZYppGlobalLock
+  /// \brief Our broken global lock
+  ///
   ///////////////////////////////////////////////////////////////////
-
   class ZYppGlobalLock
   {
-    public:
-
-      ZYppGlobalLock()
-      : _clean_lock(false)
-      , _zypp_lockfile(0)
-    {}
+  public:
+    ZYppGlobalLock()
+    : _zyppLockFilePath( env::ZYPP_LOCKFILE_ROOT() / "/var/run/zypp.pid" )
+    , _zyppLockFile( NULL )
+    , _lockerPid( 0 )
+    , _cleanLock( false )
+    {
+      filesystem::assert_dir(_zyppLockFilePath.dirname() );
+    }
 
     ~ZYppGlobalLock()
     {
-      try
-        {
-          pid_t curr_pid = getpid();
-          if ( _zypp_lockfile )
-            {
-              Pathname lock_file = Pathname(ZYPP_LOCK_FILE);
-              unLockFile();
-              closeLockFile();
-
-              if ( _clean_lock )
-              {
-                MIL << "Cleaning lock file. (" << curr_pid << ")" << std::endl;
-                if ( filesystem::unlink(lock_file) == 0 )
-                  MIL << "Lockfile cleaned. (" << curr_pid << ")" << std::endl;
-                else
-                  ERR << "Cant clean lockfile. (" << curr_pid << ")" << std::endl;
-              }
-            }
-        }
-      catch(...) {} // let no exception escape.
+       if ( _cleanLock )
+       try {
+         // Exception safe access to the lockfile.
+         ScopedGuard closeOnReturn( accessLockFile() );
+         {
+           scoped_lock<file_lock> flock( _zyppLockFileLock );  // aquire write lock
+           // Truncate the file rather than deleting it. Other processes may
+           // still use it to synchronsize.
+           ftruncate( fileno(_zyppLockFile), 0 );
+         }
+         MIL << "Cleanned lock file. (" << getpid() << ")" << std::endl;
+       }
+       catch(...) {} // let no exception escape.
     }
 
-    bool _clean_lock;
+    pid_t lockerPid() const
+    { return _lockerPid; }
 
-    private:
-    FILE *_zypp_lockfile;
+    const std::string & lockerName() const
+    { return _lockerName; }
 
-    void openLockFile(const char *mode)
-    {
-      Pathname lock_file = Pathname(ZYPP_LOCK_FILE);
-      _zypp_lockfile = fopen(lock_file.asString().c_str(), mode);
-      if (_zypp_lockfile == 0)
-        ZYPP_THROW (Exception( "Cant open " + lock_file.asString() + " in mode " + std::string(mode) ) );
-    }
+    const Pathname & zyppLockFilePath() const
+    { return _zyppLockFilePath; }
 
-    void closeLockFile()
-    {
-      fclose(_zypp_lockfile);
-    }
 
-    void shLockFile()
-    {
-      int fd = fileno(_zypp_lockfile);
-      int lock_error = flock(fd, LOCK_SH);
-      if (lock_error != 0)
-        ZYPP_THROW (Exception( "Cant get shared lock"));
-      else
-        MIL << "locked (shared)" << std::endl;
-    }
+  private:
+    Pathname   _zyppLockFilePath;
+    file_lock  _zyppLockFileLock;
+    FILE *     _zyppLockFile;
 
-    void exLockFile()
-    {
-      int fd = fileno(_zypp_lockfile);
-    // lock access to the file
-      int lock_error = flock(fd, LOCK_EX);
-      if (lock_error != 0)
-        ZYPP_THROW (Exception( "Cant get exclusive lock" ));
-      else
-        MIL << "locked (exclusive)" << std::endl;
-    }
+    pid_t      _lockerPid;
+    std::string _lockerName;
+    bool       _cleanLock;
+
+  private:
+    typedef shared_ptr<void> ScopedGuard;
 
-    void unLockFile()
+    /** Exception safe access to the lockfile.
+     * \code
+     *   // Exception safe access to the lockfile.
+     *   ScopedGuard closeOnReturn( accessLockFile() );
+     * \endcode
+     */
+    ScopedGuard accessLockFile()
     {
-      int fd = fileno(_zypp_lockfile);
-    // lock access to the file
-      int lock_error = flock(fd, LOCK_UN);
-      if (lock_error != 0)
-        ZYPP_THROW (Exception( "Cant release lock" ));
-      else
-        MIL << "unlocked" << std::endl;
+      _openLockFile();
+      return ScopedGuard( static_cast<void*>(0),
+                         bind( mem_fun_ref( &ZYppGlobalLock::_closeLockFile ), ref(*this) ) );
     }
 
-    bool lockFileExists()
+    /** Use \ref accessLockFile. */
+    void _openLockFile()
     {
-      Pathname lock_file = Pathname(ZYPP_LOCK_FILE);
-    // check if the file already existed.
-      bool exists = PathInfo(lock_file).isExist();
-      return exists;
+      if ( _zyppLockFile != NULL )
+       return; // is open
+
+      // open pid file rw so we are sure it exist when creating the flock
+      _zyppLockFile = fopen( _zyppLockFilePath.c_str(), "a+" );
+      if ( _zyppLockFile == NULL )
+       ZYPP_THROW( Exception( "Cant open " + _zyppLockFilePath.asString() ) );
+      _zyppLockFileLock = _zyppLockFilePath.c_str();
+      MIL << "Open lockfile " << _zyppLockFilePath << endl;
     }
 
-    void createLockFile()
+    /** Use \ref accessLockFile. */
+    void _closeLockFile()
     {
-      pid_t curr_pid = getpid();
-      openLockFile("w");
-      exLockFile();
-      fprintf(_zypp_lockfile, "%ld\n", (long) curr_pid);
-      fflush(_zypp_lockfile);
-      unLockFile();
-      MIL << "written lockfile with pid " << curr_pid << std::endl;
-      closeLockFile();
+      if ( _zyppLockFile == NULL )
+       return; // is closed
+
+      clearerr( _zyppLockFile );
+      fflush( _zyppLockFile );
+      // http://www.boost.org/doc/libs/1_50_0/doc/html/interprocess/synchronization_mechanisms.html
+      // If you are using a std::fstream/native file handle to write to the file
+      // while using file locks on that file, don't close the file before releasing
+      // all the locks of the file.
+      _zyppLockFileLock = file_lock();
+      fclose( _zyppLockFile );
+      _zyppLockFile = NULL;
+      MIL << "Close lockfile " << _zyppLockFilePath << endl;
     }
 
-    bool isProcessRunning(pid_t pid)
+
+    bool isProcessRunning( pid_t pid_r )
     {
-    // it is another program, not me, see if it is still running
-      stringstream ss;
-      ss << "/proc/" << pid << "/status";
-      Pathname procfile = Pathname(ss.str());
-      MIL << "Checking " << procfile << " to determine if pid is running: " << pid << std::endl;
-      bool still_running = PathInfo(procfile).isExist();
-      return still_running;
+      // it is another program, not me, see if it is still running
+      Pathname procdir( "/proc"/str::numstring(pid_r) );
+      PathInfo status( procdir );
+      MIL << "Checking " <<  status << endl;
+
+      if ( ! status.isDir() )
+      {
+       DBG << "No such process." << endl;
+       return false;
+      }
+
+      static char buffer[513];
+      buffer[0] = buffer[512] = 0;
+      // man proc(5): /proc/[pid]/cmdline is empty if zombie.
+      if ( std::ifstream( (procdir/"cmdline").c_str() ).read( buffer, 512 ).gcount() > 0 )
+      {
+       _lockerName = buffer;
+       DBG << "Is running: " <<  _lockerName << endl;
+       return true;
+      }
+
+      DBG << "In zombie state." << endl;
+      return false;
     }
 
-    pid_t lockerPid()
+    pid_t readLockFile()
     {
-      pid_t curr_pid = getpid();
-      pid_t locked_pid = 0;
+      clearerr( _zyppLockFile );
+      fseek( _zyppLockFile, 0, SEEK_SET );
       long readpid = 0;
+      fscanf( _zyppLockFile, "%ld", &readpid );
+      MIL << "read: Lockfile " << _zyppLockFilePath << " has pid " << readpid << " (our pid: " << getpid() << ") "<< std::endl;
+      return (pid_t)readpid;
+    }
 
-      fscanf(_zypp_lockfile, "%ld", &readpid);
-      MIL << "read: Lockfile " << ZYPP_LOCK_FILE << " has pid " << readpid << " (our pid: " << curr_pid << ") "<< std::endl;
-      locked_pid = (pid_t) readpid;
-      return locked_pid;
+    void writeLockFile()
+    {
+      clearerr( _zyppLockFile );
+      fseek( _zyppLockFile, 0, SEEK_SET );
+      ftruncate( fileno(_zyppLockFile), 0 );
+      fprintf(_zyppLockFile, "%ld\n", (long)getpid() );
+      fflush( _zyppLockFile );
+      _cleanLock = true; // cleanup on exit
+      MIL << "write: Lockfile " << _zyppLockFilePath << " got pid " <<  getpid() << std::endl;
     }
 
   public:
 
+    /** Try to aquire a lock.
+     * \return \c true if zypp is already locked by another process.
+     */
     bool zyppLocked()
     {
-      pid_t curr_pid = getpid();
-      Pathname lock_file = Pathname(ZYPP_LOCK_FILE);
+      if ( geteuid() != 0 )
+       return false;   // no lock as non-root
 
-      if ( lockFileExists() )
+      // Exception safe access to the lockfile.
+      ScopedGuard closeOnReturn( accessLockFile() );
       {
-        MIL << "found lockfile " << lock_file << std::endl;
-        openLockFile("r");
-        shLockFile();
-
-        pid_t locker_pid = lockerPid();
-        if ( locker_pid == curr_pid )
-        {
-        // alles ok, we are requesting the instance again
-          //MIL << "Lockfile found, but it is myself. Assuming same process getting zypp instance again." << std::endl;
-          return false;
-        }
-        else
-        {
-          if ( isProcessRunning(locker_pid) )
-          {
-            if ( geteuid() == 0 )
-            {
-              // i am root
-              MIL << locker_pid << " is running and has a ZYpp lock. Sorry" << std::endl;
-              return true;
-            }
-            else
-            {
-              MIL << locker_pid << " is running and has a ZYpp lock. Access as normal user allowed." << std::endl;
-              return false;
-            }
-          }
-          else
-          {
-            if ( geteuid() == 0 )
-            {
-              MIL << locker_pid << " has a ZYpp lock, but process is not running. Cleaning lock file." << std::endl;
-              if ( filesystem::unlink(lock_file) == 0 )
-              {
-                createLockFile();
-              // now open it for reading
-                openLockFile("r");
-                shLockFile();
-                return false;
-              }
-              else
-              {
-                ERR << "Can't clean lockfile. Sorry, can't create a new lock. Zypp still locked." << std::endl;
-                return true;
-              }
-            }
-            else
-            {
-              MIL << locker_pid << " is running and has a ZYpp lock. Access as normal user allowed." << std::endl;
-              return false;
-            }
-          }
-        }
-      }
-      else
-      {
-        MIL << "no lockfile " << lock_file << " found" << std::endl;
-        if ( geteuid() == 0 )
-        {
-          MIL << "running as root. Will attempt to create " << lock_file << std::endl;
-          createLockFile();
-        // now open it for reading
-          openLockFile("r");
-          shLockFile();
-        }
-        else
-        {
-          MIL << "running as user. Skipping creating " << lock_file << std::endl;
-        }
-        return false;
+       scoped_lock<file_lock> flock( _zyppLockFileLock );      // aquire write lock
+
+       _lockerPid = readLockFile();
+       if ( _lockerPid == 0 )
+       {
+         // no or empty lock file
+         writeLockFile();
+         return false;
+       }
+       else if ( _lockerPid == getpid() )
+       {
+         // keep my own lock
+         return false;
+       }
+       else
+       {
+         // a foreign pid in lock
+         if ( isProcessRunning( _lockerPid ) )
+         {
+           WAR << _lockerPid << " is running and has a ZYpp lock. Sorry." << std::endl;
+           return true;
+         }
+         else
+         {
+           MIL << _lockerPid << " is dead. Taking the lock file." << std::endl;
+           writeLockFile();
+           return false;
+         }
+       }
       }
+      INT << "Oops! We should not be here!" << std::endl;
       return true;
     }
 
   };
 
-  static ZYppGlobalLock globalLock;
-
-  ///////////////////////////////////////////////////////////////////
-  //
-  //   CLASS NAME : ZYppFactoryException
-  //
   ///////////////////////////////////////////////////////////////////
+  namespace
+  {
+    static weak_ptr<ZYpp>              _theZYppInstance;
+    static scoped_ptr<ZYppGlobalLock>  _theGlobalLock;         // on/off in sync with _theZYppInstance
 
-  ZYppFactoryException::ZYppFactoryException( const std::string & msg_r )
-  : Exception(N_("Cannot aquire zypp lock."))
-  {}
+    ZYppGlobalLock & globalLock()
+    {
+      if ( !_theGlobalLock )
+       _theGlobalLock.reset( new ZYppGlobalLock );
+      return *_theGlobalLock;
+    }
+  } //namespace
+  ///////////////////////////////////////////////////////////////////
 
   ///////////////////////////////////////////////////////////////////
   //
-  //   CLASS NAME : ZYppFactory
+  //   CLASS NAME : ZYpp
   //
   ///////////////////////////////////////////////////////////////////
 
-  ///////////////////////////////////////////////////////////////////
-  //
-  //   METHOD NAME : ZYppFactory::instance
-  //   METHOD TYPE : ZYppFactory
-  //
-  ZYppFactory ZYppFactory::instance()
+  ZYpp::ZYpp( const Impl_Ptr & impl_r )
+  : _pimpl( impl_r )
   {
-    return ZYppFactory();
+  }
+
+  ZYpp::~ZYpp()
+  {
+    _theGlobalLock.reset();
   }
 
   ///////////////////////////////////////////////////////////////////
   //
-  //   METHOD NAME : ZYppFactory::ZYppFactory
-  //   METHOD TYPE : Ctor
+  //   CLASS NAME : ZYppFactoryException
   //
-  ZYppFactory::ZYppFactory()
-  {
+  ///////////////////////////////////////////////////////////////////
 
-  }
+  ZYppFactoryException::ZYppFactoryException( const std::string & msg_r, pid_t lockerPid_r, const std::string & lockerName_r )
+    : Exception( msg_r )
+    , _lockerPid( lockerPid_r )
+    , _lockerName( lockerName_r )
+  {}
+
+  ZYppFactoryException::~ZYppFactoryException() throw ()
+  {}
 
   ///////////////////////////////////////////////////////////////////
   //
-  //   METHOD NAME : ZYppFactory::~ZYppFactory
-  //   METHOD TYPE : Dtor
+  //   CLASS NAME : ZYppFactory
   //
+  ///////////////////////////////////////////////////////////////////
+
+  ZYppFactory ZYppFactory::instance()
+  { return ZYppFactory(); }
+
+  ZYppFactory::ZYppFactory()
+  {}
+
   ZYppFactory::~ZYppFactory()
   {}
 
@@ -317,32 +347,74 @@ namespace zypp
   //
   ZYpp::Ptr ZYppFactory::getZYpp() const
   {
-    static ZYpp::Ptr _instance;
-
+    ZYpp::Ptr _instance = _theZYppInstance.lock();
     if ( ! _instance )
     {
-      /*--------------------------------------------------*/
-      if ( zypp_readonly_hack::active )
-        {
-          _instance = new ZYpp( ZYpp::Impl_Ptr(new ZYpp::Impl) );
-          MIL << "ZYPP_READONLY active." << endl;
-          return _instance;
-        }
-      /*--------------------------------------------------*/
-      if ( globalLock.zyppLocked() )
+      if ( geteuid() != 0 )
       {
-        ZYPP_THROW( ZYppFactoryException(N_("Cannot aquire zypp lock.")) );
+       MIL << "Running as user. Skip creating " << globalLock().zyppLockFilePath() << std::endl;
       }
-      else
+      else if ( zypp_readonly_hack::active )
       {
-        _instance = new ZYpp( ZYpp::Impl_Ptr(new ZYpp::Impl) );
-        globalLock._clean_lock = true;
+       MIL << "ZYPP_READONLY active." << endl;
       }
+      else if ( globalLock().zyppLocked() )
+      {
+       bool failed = true;
+       const long LOCK_TIMEOUT = str::strtonum<long>( getenv( "ZYPP_LOCK_TIMEOUT" ) );
+       if ( LOCK_TIMEOUT > 0 )
+       {
+         MIL << "Waiting whether pid " << globalLock().lockerPid() << " ends within $LOCK_TIMEOUT=" << LOCK_TIMEOUT << " sec." << endl;
+         unsigned delay = 1;
+         Pathname procdir( "/proc"/str::numstring(globalLock().lockerPid()) );
+         for ( long i = 0; i < LOCK_TIMEOUT; i += delay )
+         {
+           if ( PathInfo( procdir ).isDir() )  // wait for /proc/pid to disapear
+             sleep( delay );
+           else
+           {
+             MIL << "Retry after " << i << " sec." << endl;
+             failed = globalLock().zyppLocked();
+             if ( failed )
+             {
+               // another proc locked faster. maybe it ends fast as well....
+               MIL << "Waiting whether pid " << globalLock().lockerPid() << " ends within " << (LOCK_TIMEOUT-i) << " sec." << endl;
+               procdir = Pathname( "/proc"/str::numstring(globalLock().lockerPid()) );
+             }
+             else
+             {
+               MIL << "Finally got the lock!" << endl;
+               break;  // gotcha
+             }
+           }
+         }
+       }
+       if ( failed )
+       {
+         std::string t = str::form(_("System management is locked by the application with pid %d (%s).\n"
+                                     "Close this application before trying again."),
+                                     globalLock().lockerPid(),
+                                     globalLock().lockerName().c_str()
+                                   );
+         ZYPP_THROW(ZYppFactoryException(t, globalLock().lockerPid(), globalLock().lockerName() ));
+       }
+      }
+      // Here we go...
+      static ZYpp::Impl_Ptr _theImplInstance;  // for now created once
+      if ( !_theImplInstance )
+       _theImplInstance.reset( new ZYpp::Impl );
+      _instance.reset( new ZYpp( _theImplInstance ) );
+      _theZYppInstance = _instance;
     }
 
     return _instance;
   }
 
+  ///////////////////////////////////////////////////////////////////
+  //
+  bool ZYppFactory::haveZYpp() const
+  { return !_theZYppInstance.expired(); }
+
   /******************************************************************
   **
   **   FUNCTION NAME : operator<<