}
#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"
-#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;
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()
{
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)
- , _locker_pid(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.
}
- pid_t locker_pid() const
- { return _locker_pid; }
+ pid_t lockerPid() const
+ { return _lockerPid; }
- const std::string & locker_name() const
- { return _locker_name; }
+ const std::string & lockerName() const
+ { return _lockerName; }
+ const Pathname & zyppLockFilePath() const
+ { return _zyppLockFilePath; }
- bool _clean_lock;
- private:
- FILE *_zypp_lockfile;
- pid_t _locker_pid;
- std::string _locker_name;
-
- 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) ) );
- }
+ private:
+ Pathname _zyppLockFilePath;
+ file_lock _zyppLockFileLock;
+ FILE * _zyppLockFile;
- void closeLockFile()
- {
- fclose(_zypp_lockfile);
- }
+ pid_t _lockerPid;
+ std::string _lockerName;
+ bool _cleanLock;
- 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:
+ typedef shared_ptr<void> ScopedGuard;
- void exLockFile()
+ /** 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_EX);
- if (lock_error != 0)
- ZYPP_THROW (Exception( "Cant get exclusive lock" ));
- else
- MIL << "locked (exclusive)" << std::endl;
+ _openLockFile();
+ return ScopedGuard( static_cast<void*>(0),
+ bind( mem_fun_ref( &ZYppGlobalLock::_closeLockFile ), ref(*this) ) );
}
- void unLockFile()
+ /** Use \ref accessLockFile. */
+ void _openLockFile()
{
- 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;
+ 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;
}
- bool lockFileExists()
+ /** Use \ref accessLockFile. */
+ void _closeLockFile()
{
- 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 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;
}
- void createLockFile()
- {
- 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();
- }
- bool isProcessRunning(pid_t pid_r)
+ 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) );
// man proc(5): /proc/[pid]/cmdline is empty if zombie.
if ( std::ifstream( (procdir/"cmdline").c_str() ).read( buffer, 512 ).gcount() > 0 )
{
- _locker_name = buffer;
- DBG << "Is running: " << _locker_name << endl;
+ _lockerName = buffer;
+ DBG << "Is running: " << _lockerName << endl;
return true;
}
return false;
}
- pid_t lockerPid()
+ pid_t readLockFile()
{
- pid_t curr_pid = getpid();
- pid_t locker_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;
- locker_pid = (pid_t) readpid;
- return locker_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() )
- {
- MIL << "found lockfile " << lock_file << std::endl;
- openLockFile("r");
- shLockFile();
-
- pid_t locker_pid = lockerPid();
- _locker_pid = locker_pid;
- 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
+ // Exception safe access to the lockfile.
+ ScopedGuard closeOnReturn( accessLockFile() );
{
- 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;
+ namespace
+ {
+ static ZYppGlobalLock & globalLock()
+ {
+ static ZYppGlobalLock lock;
+ return lock;
+ }
+ bool _haveZYpp = false;
+ }
///////////////////////////////////////////////////////////////////
//
//
///////////////////////////////////////////////////////////////////
- ZYppFactoryException::ZYppFactoryException( const std::string & msg_r, pid_t locker_pid )
- : Exception(msg_r),
- _locker_pid (locker_pid)
+ 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 ()
{}
///////////////////////////////////////////////////////////////////
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 )
{
- std::string t = str::form(_("System management is locked by the application with pid %d (%s).\n"
- "Close this application before trying again."),
- globalLock.locker_pid(),
- globalLock.locker_name().c_str()
- );
- ZYPP_THROW(ZYppFactoryException(t, globalLock.locker_pid()));
+ 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...
+ _instance = new ZYpp( ZYpp::Impl_Ptr(new ZYpp::Impl) );
+ if ( _instance )
+ _haveZYpp = true;
}
return _instance;
}
+ ///////////////////////////////////////////////////////////////////
+ //
+ bool ZYppFactory::haveZYpp() const
+ { return _haveZYpp; }
+
/******************************************************************
**
** FUNCTION NAME : operator<<