/** \file zypp/ZYppFactory.cc
*
*/
+extern "C"
+{
+#include <sys/file.h>
+}
#include <iostream>
+#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"
+
+#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;
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") : "/"; }
+ }
+
///////////////////////////////////////////////////////////////////
- //
- // CLASS NAME : ZYppFactory
- //
+ namespace zypp_readonly_hack
+ { /////////////////////////////////////////////////////////////////
+
+ static bool active = getenv("ZYPP_READONLY_HACK");
+
+ void IWantIt()
+ {
+ active = true;
+ MIL << "ZYPP_READONLY promised." << endl;
+ }
+
+ bool IGotIt()
+ {
+ return active;
+ }
+
+ /////////////////////////////////////////////////////////////////
+ } // namespace zypp_readonly_hack
+ ///////////////////////////////////////////////////////////////////
+
+ ///////////////////////////////////////////////////////////////////
+ /// \class ZYppGlobalLock
+ /// \brief Our broken global lock
+ ///
+ ///////////////////////////////////////////////////////////////////
+ class ZYppGlobalLock
+ {
+ public:
+ ZYppGlobalLock()
+ : _zyppLockFilePath( env::ZYPP_LOCKFILE_ROOT() / "/var/run/zypp.pid" )
+ , _zyppLockFile( NULL )
+ , _lockerPid( 0 )
+ , _cleanLock( false )
+ {
+ filesystem::assert_dir(_zyppLockFilePath.dirname() );
+ }
+
+ ~ZYppGlobalLock()
+ {
+ 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.
+ }
+
+ pid_t lockerPid() const
+ { return _lockerPid; }
+
+ const std::string & lockerName() const
+ { return _lockerName; }
+
+ const Pathname & zyppLockFilePath() const
+ { return _zyppLockFilePath; }
+
+
+ private:
+ Pathname _zyppLockFilePath;
+ file_lock _zyppLockFileLock;
+ FILE * _zyppLockFile;
+
+ pid_t _lockerPid;
+ std::string _lockerName;
+ bool _cleanLock;
+
+ private:
+ typedef shared_ptr<void> ScopedGuard;
+
+ /** Exception safe access to the lockfile.
+ * \code
+ * // Exception safe access to the lockfile.
+ * ScopedGuard closeOnReturn( accessLockFile() );
+ * \endcode
+ */
+ ScopedGuard accessLockFile()
+ {
+ _openLockFile();
+ return ScopedGuard( static_cast<void*>(0),
+ bind( mem_fun_ref( &ZYppGlobalLock::_closeLockFile ), ref(*this) ) );
+ }
+
+ /** Use \ref accessLockFile. */
+ void _openLockFile()
+ {
+ 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;
+ }
+
+ /** Use \ref accessLockFile. */
+ void _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_r )
+ {
+ // 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 readLockFile()
+ {
+ 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;
+ }
+
+ 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()
+ {
+ if ( geteuid() != 0 )
+ return false; // no lock as non-root
+
+ // Exception safe access to the lockfile.
+ ScopedGuard closeOnReturn( accessLockFile() );
+ {
+ 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;
+ }
+
+ };
+
+ ///////////////////////////////////////////////////////////////////
+ namespace
+ {
+ static weak_ptr<ZYpp> _theZYppInstance;
+ static scoped_ptr<ZYppGlobalLock> _theGlobalLock; // on/off in sync with _theZYppInstance
+
+ ZYppGlobalLock & globalLock()
+ {
+ if ( !_theGlobalLock )
+ _theGlobalLock.reset( new ZYppGlobalLock );
+ return *_theGlobalLock;
+ }
+ } //namespace
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
//
- // METHOD NAME : ZYppFactory::instance
- // METHOD TYPE : ZYppFactory
+ // CLASS NAME : ZYpp
//
- 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()
{}
//
ZYpp::Ptr ZYppFactory::getZYpp() const
{
- static ZYpp::Ptr _instance( new ZYpp( ZYpp::Impl_Ptr(new ZYpp::Impl) ) );
+ ZYpp::Ptr _instance = _theZYppInstance.lock();
+ if ( ! _instance )
+ {
+ if ( geteuid() != 0 )
+ {
+ MIL << "Running as user. Skip creating " << globalLock().zyppLockFilePath() << std::endl;
+ }
+ else if ( zypp_readonly_hack::active )
+ {
+ 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<<