diff options
Diffstat (limited to 'lib/common/NamedLock.cpp')
-rw-r--r-- | lib/common/NamedLock.cpp | 223 |
1 files changed, 176 insertions, 47 deletions
diff --git a/lib/common/NamedLock.cpp b/lib/common/NamedLock.cpp index f96f80b5..8e672ff5 100644 --- a/lib/common/NamedLock.cpp +++ b/lib/common/NamedLock.cpp @@ -21,8 +21,9 @@ #include <sys/file.h> #endif -#include "NamedLock.h" #include "CommonException.h" +#include "NamedLock.h" +#include "Utils.h" #include "MemLeakFindOn.h" @@ -35,7 +36,11 @@ // // -------------------------------------------------------------------------- NamedLock::NamedLock() - : mFileDescriptor(-1) +#ifdef WIN32 +: mFileDescriptor(INVALID_HANDLE_VALUE) +#else +: mFileDescriptor(-1) +#endif { } @@ -49,7 +54,11 @@ NamedLock::NamedLock() // -------------------------------------------------------------------------- NamedLock::~NamedLock() { +#ifdef WIN32 + if(mFileDescriptor != INVALID_HANDLE_VALUE) +#else if(mFileDescriptor != -1) +#endif { ReleaseLock(); } @@ -68,76 +77,151 @@ NamedLock::~NamedLock() bool NamedLock::TryAndGetLock(const std::string& rFilename, int mode) { // Check +#ifdef WIN32 + if(mFileDescriptor != INVALID_HANDLE_VALUE) +#else if(mFileDescriptor != -1) +#endif { THROW_EXCEPTION(CommonException, NamedLockAlreadyLockingSomething) } + mFileName = rFilename; + // See if the lock can be got + int flags = O_WRONLY | O_CREAT | O_TRUNC; + #if HAVE_DECL_O_EXLOCK - int fd = ::open(rFilename.c_str(), - O_WRONLY | O_NONBLOCK | O_CREAT | O_TRUNC | O_EXLOCK, mode); - if(fd != -1) - { - // Got a lock, lovely - mFileDescriptor = fd; - return true; - } - - // Failed. Why? - if(errno != EWOULDBLOCK) - { - // Not the expected error - THROW_EXCEPTION(CommonException, OSFileError) - } + flags |= O_NONBLOCK | O_EXLOCK; + BOX_TRACE("Trying to create lockfile " << rFilename << " using O_EXLOCK"); +#elif defined BOX_OPEN_LOCK + flags |= BOX_OPEN_LOCK; + BOX_TRACE("Trying to create lockfile " << rFilename << " using BOX_OPEN_LOCK"); +#elif !HAVE_DECL_F_SETLK && !defined HAVE_FLOCK + // We have no other way to get a lock, so all we can do is fail if + // the file already exists, and take the risk of stale locks. + flags |= O_EXCL; + BOX_TRACE("Trying to create lockfile " << rFilename << " using O_EXCL"); +#else + BOX_TRACE("Trying to create lockfile " << rFilename << " without special flags"); +#endif - return false; +#ifdef WIN32 + HANDLE fd = openfile(rFilename.c_str(), flags, mode); + if(fd == INVALID_HANDLE_VALUE) #else - int fd = ::open(rFilename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode); + int fd = ::open(rFilename.c_str(), flags, mode); if(fd == -1) - { - BOX_WARNING("Failed to open lockfile: " << rFilename); - THROW_EXCEPTION(CommonException, OSFileError) - } - -#ifdef HAVE_FLOCK - if(::flock(fd, LOCK_EX | LOCK_NB) != 0) - { - ::close(fd); +#endif +#if HAVE_DECL_O_EXLOCK + { // if() if(errno == EWOULDBLOCK) { + // Lockfile already exists, and we tried to open it + // exclusively, which means we failed to lock it. + BOX_NOTICE("Failed to lock lockfile with O_EXLOCK: " << rFilename + << ": already locked by another process?"); return false; } else { - THROW_EXCEPTION(CommonException, OSFileError) + THROW_SYS_FILE_ERROR("Failed to open lockfile with O_EXLOCK", + rFilename, CommonException, OSFileError); } } -#elif HAVE_DECL_F_SETLK - struct flock desc; - desc.l_type = F_WRLCK; - desc.l_whence = SEEK_SET; - desc.l_start = 0; - desc.l_len = 0; - if(::fcntl(fd, F_SETLK, &desc) != 0) - { - ::close(fd); - if(errno == EAGAIN) +#else // !HAVE_DECL_O_EXLOCK + { // if() +# if defined BOX_OPEN_LOCK + if(errno == EBUSY) +# else // !BOX_OPEN_LOCK + if(errno == EEXIST && (flags & O_EXCL)) +# endif { + // Lockfile already exists, and we tried to open it + // exclusively, which means we failed to lock it. + BOX_NOTICE("Failed to lock lockfile with O_EXCL: " << rFilename + << ": already locked by another process?"); return false; } else { - THROW_EXCEPTION(CommonException, OSFileError) + THROW_SYS_FILE_ERROR("Failed to open lockfile with O_EXCL", + rFilename, CommonException, OSFileError); } } -#endif + + try + { +# ifdef HAVE_FLOCK + BOX_TRACE("Trying to lock lockfile " << rFilename << " using flock()"); + if(::flock(fd, LOCK_EX | LOCK_NB) != 0) + { + if(errno == EWOULDBLOCK) + { + ::close(fd); + BOX_NOTICE("Failed to lock lockfile with flock(): " << rFilename + << ": already locked by another process"); + return false; + } + else + { + THROW_SYS_FILE_ERROR("Failed to lock lockfile with flock()", + rFilename, CommonException, OSFileError); + } + } +# elif HAVE_DECL_F_SETLK + struct flock desc; + desc.l_type = F_WRLCK; + desc.l_whence = SEEK_SET; + desc.l_start = 0; + desc.l_len = 0; + BOX_TRACE("Trying to lock lockfile " << rFilename << " using fcntl()"); + if(::fcntl(fd, F_SETLK, &desc) != 0) + { + if(errno == EAGAIN) + { + ::close(fd); + BOX_NOTICE("Failed to lock lockfile with fcntl(): " << rFilename + << ": already locked by another process"); + return false; + } + else + { + THROW_SYS_FILE_ERROR("Failed to lock lockfile with fcntl()", + rFilename, CommonException, OSFileError); + } + } +# endif + } + catch(BoxException &e) + { +# ifdef WIN32 + CloseHandle(fd); +# else + ::close(fd); +# endif + BOX_NOTICE("Failed to lock lockfile " << rFilename << ": " << e.what()); + throw; + } +#endif // HAVE_DECL_O_EXLOCK + + if(!FileExists(rFilename)) + { + BOX_ERROR("Locked lockfile " << rFilename << ", but lockfile no longer " + "exists, bailing out"); +# ifdef WIN32 + CloseHandle(fd); +# else + ::close(fd); +# endif + return false; + } // Success mFileDescriptor = fd; + BOX_TRACE("Successfully locked lockfile " << rFilename); return true; -#endif } // -------------------------------------------------------------------------- @@ -151,20 +235,65 @@ bool NamedLock::TryAndGetLock(const std::string& rFilename, int mode) void NamedLock::ReleaseLock() { // Got a lock? +#ifdef WIN32 + if(mFileDescriptor == INVALID_HANDLE_VALUE) +#else if(mFileDescriptor == -1) +#endif { THROW_EXCEPTION(CommonException, NamedLockNotHeld) } - + +#ifndef WIN32 + // Delete the file. We need to do this before closing the filehandle, + // if we used flock() or fcntl() to lock it, otherwise someone could + // acquire the lock, release and delete it between us closing (and + // hence releasing) and deleting it, and we'd fail when it came to + // deleting the file. This happens in tests much more often than + // you'd expect! + // + // This doesn't apply on systems using plain lockfile locking, such as + // Windows, and there we need to close the file before deleting it, + // otherwise the system won't let us delete it. + + if(::unlink(mFileName.c_str()) != 0) + { + THROW_EMU_ERROR( + BOX_FILE_MESSAGE(mFileName, "Failed to delete lockfile"), + CommonException, OSFileError); + } +#endif // !WIN32 + // Close the file +# ifdef WIN32 + if(!CloseHandle(mFileDescriptor)) +# else if(::close(mFileDescriptor) != 0) +# endif { - THROW_EXCEPTION(CommonException, OSFileError) + THROW_EMU_ERROR( + BOX_FILE_MESSAGE(mFileName, "Failed to close lockfile"), + CommonException, OSFileError); } - // Mark as unlocked - mFileDescriptor = -1; -} + // Mark as unlocked, so we don't try to close it again if the unlink() fails. +#ifdef WIN32 + mFileDescriptor = INVALID_HANDLE_VALUE; +#else + mFileDescriptor = -1; +#endif +#ifdef WIN32 + // On Windows we need to close the file before deleting it, otherwise + // the system won't let us delete it. + if(::unlink(mFileName.c_str()) != 0) + { + THROW_EMU_ERROR( + BOX_FILE_MESSAGE(mFileName, "Failed to delete lockfile"), + CommonException, OSFileError); + } +#endif // WIN32 + BOX_TRACE("Released lock and deleted lockfile " << mFileName); +} |