diff options
Diffstat (limited to 'lib/dir_lock.cpp')
-rw-r--r-- | lib/dir_lock.cpp | 407 |
1 files changed, 258 insertions, 149 deletions
diff --git a/lib/dir_lock.cpp b/lib/dir_lock.cpp index 87864ce4..402892c6 100644 --- a/lib/dir_lock.cpp +++ b/lib/dir_lock.cpp @@ -25,6 +25,8 @@ #include <tlhelp32.h> #include <zen/win.h> //includes "windows.h" #include <zen/long_path_prefix.h> +#include <Sddl.h> //login sid +#include <Lmcons.h> //UNLEN #elif defined FFS_LINUX #include <sys/stat.h> @@ -37,12 +39,12 @@ using namespace std::rel_ops; namespace { -const size_t EMIT_LIFE_SIGN_INTERVAL = 5000; //show life sign; unit [ms] -const size_t POLL_LIFE_SIGN_INTERVAL = 6000; //poll for life sign; unit [ms] -const size_t DETECT_EXITUS_INTERVAL = 30000; //assume abandoned lock; unit [ms] +const size_t EMIT_LIFE_SIGN_INTERVAL = 5000; //show life sign; unit: [ms] +const size_t POLL_LIFE_SIGN_INTERVAL = 4000; //poll for life sign; unit: [ms] +const size_t DETECT_ABANDONED_INTERVAL = 30000; //assume abandoned lock; unit: [ms] const char LOCK_FORMAT_DESCR[] = "FreeFileSync"; -const int LOCK_FORMAT_VER = 1; //lock file format version +const int LOCK_FORMAT_VER = 2; //lock file format version } //worker thread @@ -66,7 +68,7 @@ public: } catch (const std::exception& e) //exceptions must be catched per thread { - wxSafeShowMessage(wxString(_("An exception occurred!")) + wxT("(Dirlock)"), utf8CvrtTo<wxString>(e.what())); //simple wxMessageBox won't do for threads + wxSafeShowMessage(_("An exception occurred!") + L" (Dirlock)", utf8CvrtTo<wxString>(e.what())); //simple wxMessageBox won't do for threads } } @@ -97,9 +99,10 @@ public: return; DWORD bytesWritten = 0; + /*bool rv = */ ::WriteFile(fileHandle, //__in HANDLE hFile, buffer, //__out LPVOID lpBuffer, - 1, //__in DWORD nNumberOfBytesToRead, + 1, //__in DWORD nNumberOfBytesToWrite, &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten, nullptr); //__inout_opt LPOVERLAPPED lpOverlapped @@ -129,17 +132,17 @@ UInt64 getLockFileSize(const Zstring& filename) //throw FileError, ErrorNotExist if (searchHandle != INVALID_HANDLE_VALUE) { ::FindClose(searchHandle); - return zen::UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); + return UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); } #elif defined FFS_LINUX struct ::stat fileInfo = {}; if (::stat(filename.c_str(), &fileInfo) == 0) //follow symbolic links - return zen::UInt64(fileInfo.st_size); + return UInt64(fileInfo.st_size); #endif const ErrorCode lastError = getLastError(); - const std::wstring errorMessage = _("Error reading file attributes:") + L"\n\"" + filename + L"\"" + L"\n\n" + getLastErrorFormatted(lastError); + const std::wstring errorMessage = replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted(lastError); if (errorCodeForNotExisting(lastError)) throw ErrorNotExisting(errorMessage); @@ -158,73 +161,186 @@ Zstring deleteAbandonedLockName(const Zstring& lockfilename) //make sure to NOT } -namespace +class CheckedLockReader : public CheckedReader { -std::string getComputerId() //returns empty string on error +public: + CheckedLockReader(wxInputStream& stream, const Zstring& errorObjName) : CheckedReader(stream), errorObjName_(errorObjName) {} + virtual void throwException() const { throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(errorObjName_))); } + +private: + const Zstring errorObjName_; +}; + +class CheckedLockWriter : public CheckedWriter { - const wxString fhn = ::wxGetFullHostName(); - if (fhn.empty()) return std::string(); +public: + CheckedLockWriter(wxOutputStream& stream, const Zstring& errorObjName) : CheckedWriter(stream), errorObjName_(errorObjName) {} + virtual void throwException() const { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(errorObjName_))); } + +private: + const Zstring errorObjName_; +}; + + #ifdef FFS_WIN - return "Windows " + utf8CvrtTo<std::string>(fhn); -#elif defined FFS_LINUX - return "Linux " + utf8CvrtTo<std::string>(fhn); -#endif +std::wstring getLoginSid() //throw FileError +{ + HANDLE hToken = 0; + if (!::OpenProcessToken(::GetCurrentProcess(), //__in HANDLE ProcessHandle, + TOKEN_ALL_ACCESS, //__in DWORD DesiredAccess, + &hToken)) //__out PHANDLE TokenHandle + throw FileError(_("Cannot get process information.") + L"\n\n" + getLastErrorFormatted()); + ZEN_ON_SCOPE_EXIT(::CloseHandle(hToken)); + + DWORD bufferSize = 0; + ::GetTokenInformation(hToken, TokenGroups, nullptr, 0, &bufferSize); + + std::vector<char> buffer(bufferSize); + if (!::GetTokenInformation(hToken, //__in HANDLE TokenHandle, + TokenGroups, //__in TOKEN_INFORMATION_CLASS TokenInformationClass, + &buffer[0], //__out_opt LPVOID TokenInformation, + bufferSize, //__in DWORD TokenInformationLength, + &bufferSize)) //__out PDWORD ReturnLength + throw FileError(_("Cannot get process information.") + L"\n\n" + getLastErrorFormatted()); + + auto groups = reinterpret_cast<const TOKEN_GROUPS*>(&buffer[0]); + + for (DWORD i = 0; i < groups->GroupCount; ++i) + if ((groups->Groups[i].Attributes & SE_GROUP_LOGON_ID) != 0) + { + LPTSTR sidStr = nullptr; + if (!::ConvertSidToStringSid(groups->Groups[i].Sid, //__in PSID Sid, + &sidStr)) //__out LPTSTR *StringSid + throw FileError(_("Cannot get process information.") + L"\n\n" + getLastErrorFormatted()); + ZEN_ON_SCOPE_EXIT(::LocalFree(sidStr)); + + return sidStr; + } + throw FileError(_("Cannot get process information.") + L"\n\n" + getLastErrorFormatted() + L"(no login found)"); //shouldn't happen } +#endif + +class FromCurrentProcess {}; //tag -struct LockInformation +struct LockInformation //throw FileError { - LockInformation() - { - lockId = zen::generateGUID(); + explicit LockInformation(FromCurrentProcess) : + lockId(zen::generateGUID()), #ifdef FFS_WIN - procDescr.processId = ::GetCurrentProcessId(); -#elif defined FFS_LINUX - procDescr.processId = ::getpid(); -#endif - procDescr.computerId = getComputerId(); + sessionId(utf8CvrtTo<std::string>(getLoginSid())), //throw FileError + processId(::GetCurrentProcessId()) //never fails + { + DWORD bufferSize = 0; + ::GetComputerNameEx(ComputerNameDnsFullyQualified, nullptr, &bufferSize); //get required buffer size + + std::vector<wchar_t> buffer(bufferSize); + if (!GetComputerNameEx(ComputerNameDnsFullyQualified, //__in COMPUTER_NAME_FORMAT NameType, + &buffer[0], //__out LPTSTR lpBuffer, + &bufferSize)) //__inout LPDWORD lpnSize + throw FileError(_("Cannot get process information.") + L"\n\n" + getLastErrorFormatted()); + computerName = "Windows." + utf8CvrtTo<std::string>(&buffer[0]); + + bufferSize = UNLEN + 1; + buffer.resize(bufferSize); + if (!::GetUserName(&buffer[0], //__out LPTSTR lpBuffer, + &bufferSize)) //__inout LPDWORD lpnSize + throw FileError(_("Cannot get process information.") + L"\n\n" + getLastErrorFormatted()); + userId = utf8CvrtTo<std::string>(&buffer[0]); } - - LockInformation(wxInputStream& stream) //read +#elif defined FFS_LINUX + processId(::getpid()) //never fails { - char formatDescr[sizeof(LOCK_FORMAT_DESCR)] = {}; - stream.Read(formatDescr, sizeof(LOCK_FORMAT_DESCR)); //file format header - const int lockFileVersion = readPOD<boost::int32_t>(stream); // - (void)lockFileVersion; + std::vector<char> buffer(10000); + if (::gethostname(&buffer[0], buffer.size()) != 0) + throw FileError(_("Cannot get process information.") + L"\n\n" + getLastErrorFormatted()); - //some format checking here? + computerName += "Linux."; //distinguish linux/windows lock files + computerName += &buffer[0]; - lockId = readString<std::string>(stream); - procDescr.processId = static_cast<ProcessId>(readPOD<std::uint64_t>(stream)); //possible loss of precision (32/64 bit process) covered by buildId - procDescr.computerId = readString<std::string>(stream); + if (::getdomainname(&buffer[0], buffer.size()) != 0) + throw FileError(_("Cannot get process information.") + L"\n\n" + getLastErrorFormatted()); + computerName += "."; + computerName += &buffer[0]; + + uid_t userIdTmp = ::getuid(); //never fails + userId.assign(reinterpret_cast<const char*>(&userIdTmp), sizeof(userIdTmp)); + + pid_t sessionIdTmp = ::getsid(0); //new after each restart! + if (sessionIdTmp == -1) + throw FileError(_("Cannot get process information.") + L"\n\n" + getLastErrorFormatted()); + sessionId.assign(reinterpret_cast<const char*>(&sessionIdTmp), sizeof(sessionIdTmp)); } +#endif - void toStream(wxOutputStream& stream) const //write + explicit LockInformation(CheckedLockReader& reader) { - assert_static(sizeof(ProcessId) <= sizeof(std::uint64_t)); //ensure portability + char tmp[sizeof(LOCK_FORMAT_DESCR)] = {}; + reader.readArray(&tmp, sizeof(tmp)); //file format header + const int lockFileVersion = reader.readPOD<boost::int32_t>(); // + + if (!std::equal(std::begin(tmp), std::end(tmp), std::begin(LOCK_FORMAT_DESCR)) || + lockFileVersion != LOCK_FORMAT_VER) + reader.throwException(); + + reader.readString(lockId); + reader.readString(computerName); + reader.readString(userId); + reader.readString(sessionId); + processId = static_cast<decltype(processId)>(reader.readPOD<std::uint64_t>()); //[!] conversion + } + + void toStream(CheckedLockWriter& writer) const + { + writer.writeArray(LOCK_FORMAT_DESCR, sizeof(LOCK_FORMAT_DESCR)); + writer.writePOD<boost::int32_t>(LOCK_FORMAT_VER); - stream.Write(LOCK_FORMAT_DESCR, sizeof(LOCK_FORMAT_DESCR)); - writePOD<boost::int32_t>(stream, LOCK_FORMAT_VER); + assert_static(sizeof(processId) <= sizeof(std::uint64_t)); //ensure portability - writeString(stream, lockId); - writePOD<std::uint64_t>(stream, procDescr.processId); - writeString(stream, procDescr.computerId); + writer.writeString(lockId); + writer.writeString(computerName); + writer.writeString(userId); + writer.writeString(sessionId); + writer.writePOD<std::uint64_t>(processId); } + std::string lockId; //16 byte GUID - a universal identifier for this lock (no matter what the path is, considering symlinks, distributed network, etc.) + + std::string computerName; //format: HostName.DomainName + std::string userId; //non-readable! + std::string sessionId; // #ifdef FFS_WIN - typedef DWORD ProcessId; //same size on 32 and 64 bit windows! + DWORD processId; #elif defined FFS_LINUX - typedef pid_t ProcessId; + pid_t processId; #endif +}; - std::string lockId; //16 byte UUID - struct ProcessDescription - { - ProcessId processId; - std::string computerId; - } procDescr; -}; +//wxGetFullHostName() is a performance killer for some users, so don't touch! + + +void writeLockInfo(const Zstring& lockfilename) //throw FileError +{ + FileOutputStream stream(lockfilename); //throw FileError + CheckedLockWriter writer(stream, lockfilename); + LockInformation(FromCurrentProcess()).toStream(writer); //throw FileError +} + + +LockInformation retrieveLockInfo(const Zstring& lockfilename) //throw FileError, ErrorNotExisting +{ + FileInputStream stream(lockfilename); //throw FileError, ErrorNotExisting + CheckedLockReader reader(stream, lockfilename); + return LockInformation(reader); //throw FileError +} + + +inline +std::string retrieveLockId(const Zstring& lockfilename) //throw FileError, ErrorNotExisting +{ + return retrieveLockInfo(lockfilename).lockId; +} //true: process not available, false: cannot say anything @@ -235,20 +351,24 @@ enum ProcessStatus PROC_STATUS_ITS_US, PROC_STATUS_NO_IDEA }; -ProcessStatus getProcessStatus(const LockInformation::ProcessDescription& procDescr) +ProcessStatus getProcessStatus(const LockInformation& lockInfo) //throw FileError { - if (procDescr.computerId != getComputerId() || - procDescr.computerId.empty()) //both names are empty - return PROC_STATUS_NO_IDEA; //lock owned by different computer + const LockInformation localInfo((FromCurrentProcess())); //throw FileError -#ifdef FFS_WIN - if (procDescr.processId == ::GetCurrentProcessId()) //may seem obscure, but it's possible: deletion failed or a lock file is "stolen" and put back while the program is running + if (lockInfo.computerName != localInfo.computerName || + lockInfo.userId != localInfo.userId) //another user may run a session right now! + return PROC_STATUS_NO_IDEA; //lock owned by different computer in this network + + if (lockInfo.sessionId != localInfo.sessionId) + return PROC_STATUS_NOT_RUNNING; //different session but same user? there can be only one + + if (lockInfo.processId == localInfo.processId) //obscure, but possible: deletion failed or a lock file is "stolen" and put back while the program is running return PROC_STATUS_ITS_US; - //note: ::OpenProcess() is no option as it may successfully return for crashed processes! - HANDLE snapshot = ::CreateToolhelp32Snapshot( - TH32CS_SNAPPROCESS, //__in DWORD dwFlags, - 0); //__in DWORD th32ProcessID +#ifdef FFS_WIN + //note: ::OpenProcess() is no alternative as it may successfully return for crashed processes! -> remark: "WaitForSingleObject" may identify this case! + HANDLE snapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, //__in DWORD dwFlags, + 0); //__in DWORD th32ProcessID if (snapshot == INVALID_HANDLE_VALUE) return PROC_STATUS_NO_IDEA; ZEN_ON_SCOPE_EXIT(::CloseHandle(snapshot)); @@ -258,122 +378,104 @@ ProcessStatus getProcessStatus(const LockInformation::ProcessDescription& procDe if (!::Process32First(snapshot, //__in HANDLE hSnapshot, &processEntry)) //__inout LPPROCESSENTRY32 lppe - return PROC_STATUS_NO_IDEA; + return PROC_STATUS_NO_IDEA; //ERROR_NO_MORE_FILES not possible do { - if (processEntry.th32ProcessID == procDescr.processId) + if (processEntry.th32ProcessID == lockInfo.processId) return PROC_STATUS_RUNNING; //process still running } while (::Process32Next(snapshot, &processEntry)); + if (::GetLastError() != ERROR_NO_MORE_FILES) //yes, they call it "files" + return PROC_STATUS_NO_IDEA; return PROC_STATUS_NOT_RUNNING; #elif defined FFS_LINUX - if (procDescr.processId == ::getpid()) //may seem obscure, but it's possible: a lock file is "stolen" and put back while the program is running - return PROC_STATUS_ITS_US; - - if (procDescr.processId <= 0 || procDescr.processId >= 65536) + if (lockInfo.processId <= 0 || lockInfo.processId >= 65536) return PROC_STATUS_NO_IDEA; //invalid process id - return zen::dirExists(Zstr("/proc/") + zen::numberTo<Zstring>(procDescr.processId)) ? PROC_STATUS_RUNNING : PROC_STATUS_NOT_RUNNING; + return zen::dirExists("/proc/" + zen::numberTo<Zstring>(lockInfo.processId)) ? PROC_STATUS_RUNNING : PROC_STATUS_NOT_RUNNING; #endif } -void writeLockInfo(const Zstring& lockfilename) //throw FileError -{ - //write GUID at the beginning of the file: this ID is a universal identifier for this lock (no matter what the path is, considering symlinks, distributed network, etc.) - FileOutputStream lockFile(lockfilename); //throw FileError - LockInformation().toStream(lockFile); -} - - -LockInformation retrieveLockInfo(const Zstring& lockfilename) //throw FileError, ErrorNotExisting -{ - //read GUID from beginning of file - FileInputStream lockFile(lockfilename); //throw FileError, ErrorNotExisting - return LockInformation(lockFile); -} - - -std::string retrieveLockId(const Zstring& lockfilename) //throw FileError, ErrorNotExisting -{ - return retrieveLockInfo(lockfilename).lockId; -} -} - - void waitOnDirLock(const Zstring& lockfilename, DirLockCallback* callback) //throw FileError { - const std::wstring infoMsg = replaceCpy(_("Waiting while directory is locked (%x)..."), L"%x", std::wstring(L"\"") + lockfilename + L"\""); + const std::wstring infoMsg = replaceCpy(_("Waiting while directory is locked (%x)..."), L"%x", fmtFileName(lockfilename)); if (callback) callback->reportInfo(infoMsg); //--------------------------------------------------------------- try { - const LockInformation lockInfo = retrieveLockInfo(lockfilename); //throw FileError, ErrorNotExisting - - bool lockOwnderDead = false; //convenience optimization: if we know the owning process crashed, we needn't wait DETECT_EXITUS_INTERVAL sec - switch (getProcessStatus(lockInfo.procDescr)) + //convenience optimization only: if we know the owning process crashed, we needn't wait DETECT_ABANDONED_INTERVAL sec + bool lockOwnderDead = false; + std::string originalLockId; //empty if it cannot be retrieved + try { - case PROC_STATUS_ITS_US: //since we've already passed LockAdmin, the lock file seems abandoned ("stolen"?) although it's from this process - case PROC_STATUS_NOT_RUNNING: - lockOwnderDead = true; - break; - case PROC_STATUS_RUNNING: - case PROC_STATUS_NO_IDEA: - break; + const LockInformation& lockInfo = retrieveLockInfo(lockfilename); //throw FileError, ErrorNotExisting + originalLockId = lockInfo.lockId; + switch (getProcessStatus(lockInfo)) //throw FileError + { + case PROC_STATUS_ITS_US: //since we've already passed LockAdmin, the lock file seems abandoned ("stolen"?) although it's from this process + case PROC_STATUS_NOT_RUNNING: + lockOwnderDead = true; + break; + case PROC_STATUS_RUNNING: + case PROC_STATUS_NO_IDEA: + break; + } } + catch (FileError&) {} //logfile may be only partly written -> this is no error! - zen::UInt64 fileSizeOld; - wxLongLong lockSilentStart = wxGetLocalTimeMillis(); + UInt64 fileSizeOld; + wxMilliClock_t lastLifeSign = wxGetLocalTimeMillis(); while (true) { - const zen::UInt64 fileSizeNew = ::getLockFileSize(lockfilename); //throw FileError, ErrorNotExisting - wxLongLong currentTime = wxGetLocalTimeMillis(); + wxMilliClock_t currentTime = wxGetLocalTimeMillis(); + const UInt64 fileSizeNew = ::getLockFileSize(lockfilename); //throw FileError, ErrorNotExisting if (fileSizeNew != fileSizeOld) //received life sign from lock { - fileSizeOld = fileSizeNew; - lockSilentStart = currentTime; + fileSizeOld = fileSizeNew; + lastLifeSign = currentTime; } if (lockOwnderDead || //no need to wait any longer... - currentTime - lockSilentStart > DETECT_EXITUS_INTERVAL) + currentTime - lastLifeSign > DETECT_ABANDONED_INTERVAL) { DirLock dummy(deleteAbandonedLockName(lockfilename), callback); //throw FileError //now that the lock is in place check existence again: meanwhile another process may have deleted and created a new lock! - if (retrieveLockId(lockfilename) != lockInfo.lockId) //throw FileError, ErrorNotExisting - return; //another process has placed a new lock, leave scope: the wait for the old lock is technically over... + if (!originalLockId.empty()) + if (retrieveLockId(lockfilename) != originalLockId) //throw FileError, ErrorNotExisting -> since originalLockId is filled, we are not expecting errors! + return; //another process has placed a new lock, leave scope: the wait for the old lock is technically over... - if (getLockFileSize(lockfilename) != fileSizeOld) //throw FileError, ErrorNotExisting - continue; //belated lifesign + if (::getLockFileSize(lockfilename) != fileSizeOld) //throw FileError, ErrorNotExisting + continue; //late life sign removeFile(lockfilename); //throw FileError return; } //wait some time... - const size_t GUI_CALLBACK_INTERVAL = 100; + assert_static(POLL_LIFE_SIGN_INTERVAL % GUI_CALLBACK_INTERVAL == 0); for (size_t i = 0; i < POLL_LIFE_SIGN_INTERVAL / GUI_CALLBACK_INTERVAL; ++i) { if (callback) callback->requestUiRefresh(); wxMilliSleep(GUI_CALLBACK_INTERVAL); - //show some countdown on abandoned locks if (callback) { - if (currentTime - lockSilentStart > EMIT_LIFE_SIGN_INTERVAL) //one signal missed: it's likely this is an abandoned lock: + //one signal missed: it's likely this is an abandoned lock => show countdown + if (currentTime - lastLifeSign > EMIT_LIFE_SIGN_INTERVAL) { - long remainingSeconds = ((DETECT_EXITUS_INTERVAL - (wxGetLocalTimeMillis() - lockSilentStart)) / 1000).ToLong(); + long remainingSeconds = ((DETECT_ABANDONED_INTERVAL - (wxGetLocalTimeMillis() - lastLifeSign)) / 1000).ToLong(); remainingSeconds = std::max(0L, remainingSeconds); const std::wstring remSecMsg = replaceCpy(_P("1 sec", "%x sec", remainingSeconds), L"%x", numberTo<std::wstring>(remainingSeconds)); - callback->reportInfo(infoMsg + L" " + remSecMsg); } else @@ -411,30 +513,32 @@ bool tryLock(const Zstring& lockfilename) //throw FileError nullptr); if (fileHandle == INVALID_HANDLE_VALUE) { - if (::GetLastError() == ERROR_FILE_EXISTS) + const DWORD lastError = ::GetLastError(); + if (lastError == ERROR_FILE_EXISTS || //confirmed to be used + lastError == ERROR_ALREADY_EXISTS) //comment on msdn claims, this one is used on Windows Mobile 6 return false; else - throw FileError(_("Error setting directory lock:") + L"\n\"" + lockfilename + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot set directory lock %x."), L"%x", fmtFileName(lockfilename)) + L"\n\n" + getLastErrorFormatted()); } ::CloseHandle(fileHandle); #elif defined FFS_LINUX //O_EXCL contains a race condition on NFS file systems: http://linux.die.net/man/2/open - ::umask(0); //important! + ::umask(0); //important! -> why? const int fileHandle = ::open(lockfilename.c_str(), O_CREAT | O_WRONLY | O_EXCL, S_IRWXU | S_IRWXG | S_IRWXO); if (fileHandle == -1) { if (errno == EEXIST) return false; else - throw FileError(_("Error setting directory lock:") + L"\n\"" + lockfilename + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot set directory lock %x."), L"%x", fmtFileName(lockfilename)) + L"\n\n" + getLastErrorFormatted()); } ::close(fileHandle); #endif ScopeGuard guardLockFile = zen::makeGuard([&] { removeFile(lockfilename); }); - //write UUID at the beginning of the file: this ID is a universal identifier for this lock (no matter what the path is, considering symlinks, etc.) + //write housekeeping info: user, process info, lock GUID writeLockInfo(lockfilename); //throw FileError guardLockFile.dismiss(); //lockfile created successfully @@ -485,32 +589,32 @@ public: //create or retrieve a SharedDirLock std::shared_ptr<SharedDirLock> retrieve(const Zstring& lockfilename, DirLockCallback* callback) //throw FileError { - //optimization: check if there is an active(!) lock for "lockfilename" - FileToUuidMap::const_iterator iterUuid = fileToUuid.find(lockfilename); - if (iterUuid != fileToUuid.end()) - { - if (const std::shared_ptr<SharedDirLock>& activeLock = findActive(iterUuid->second)) //returns null-lock if not found + tidyUp(); + + //optimization: check if we already own a lock for this path + auto iterGuid = fileToGuid.find(lockfilename); + if (iterGuid != fileToGuid.end()) + if (const std::shared_ptr<SharedDirLock>& activeLock = getActiveLock(iterGuid->second)) //returns null-lock if not found return activeLock; //SharedDirLock is still active -> enlarge circle of shared ownership - } - try //actual check based on lock UUID, deadlock prevention: "lockfilename" may be an alternative name for an already active lock + try //check based on lock GUID, deadlock prevention: "lockfilename" may be an alternative name for a lock already owned by this process { const std::string lockId = retrieveLockId(lockfilename); //throw FileError, ErrorNotExisting - if (const std::shared_ptr<SharedDirLock>& activeLock = findActive(lockId)) //returns null-lock if not found + if (const std::shared_ptr<SharedDirLock>& activeLock = getActiveLock(lockId)) //returns null-lock if not found { - fileToUuid[lockfilename] = lockId; //perf-optimization: update relation + fileToGuid[lockfilename] = lockId; //found an alias for one of our active locks return activeLock; } } - catch (FileError&) {} //catch everything, let SharedDirLock constructor deal with errors, e.g. 0-sized/corrupted lock file + catch (FileError&) {} //catch everything, let SharedDirLock constructor deal with errors, e.g. 0-sized/corrupted lock files - //not yet in buffer, so create a new directory lock + //lock not owned by us => create a new one auto newLock = std::make_shared<SharedDirLock>(lockfilename, callback); //throw FileError - const std::string& newLockId = retrieveLockId(lockfilename); //throw FileError, ErrorNotExisting + const std::string& newLockGuid = retrieveLockId(lockfilename); //throw FileError, ErrorNotExisting //update registry - fileToUuid[lockfilename] = newLockId; //throw() - uuidToLock[newLockId] = newLock; // + fileToGuid[lockfilename] = newLockGuid; //throw() + guidToLock[newLockGuid] = newLock; // return newLock; } @@ -518,19 +622,24 @@ public: private: LockAdmin() {} - std::shared_ptr<SharedDirLock> findActive(const std::string& lockId) //returns null-lock if not found + typedef std::string UniqueId; + typedef std::map<Zstring, UniqueId, LessFilename> FileToGuidMap; //n:1 handle uppper/lower case correctly + typedef std::map<UniqueId, std::weak_ptr<SharedDirLock>> GuidToLockMap; //1:1 + + std::shared_ptr<SharedDirLock> getActiveLock(const UniqueId& lockId) //returns null if none found { - auto iterLock = uuidToLock.find(lockId); - return iterLock != uuidToLock.end() ? - iterLock->second.lock() : nullptr; //try to get shared_ptr; throw() + auto iterLock = guidToLock.find(lockId); + return iterLock != guidToLock.end() ? iterLock->second.lock() : nullptr; //try to get shared_ptr; throw() } - typedef std::string UniqueId; - typedef std::map<Zstring, UniqueId, LessFilename> FileToUuidMap; //n:1 handle uppper/lower case correctly - typedef std::map<UniqueId, std::weak_ptr<SharedDirLock>> UuidToLockMap; //1:1 + void tidyUp() //remove obsolete lock entries + { + map_remove_if(guidToLock, [ ](const GuidToLockMap::value_type& v) { return !v.second.lock(); }); + map_remove_if(fileToGuid, [&](const FileToGuidMap::value_type& v) { return guidToLock.find(v.second) == guidToLock.end(); }); + } - FileToUuidMap fileToUuid; //lockname |-> UUID; locks can be referenced by a lockfilename or alternatively a UUID - UuidToLockMap uuidToLock; //UUID |-> "shared lock ownership" + FileToGuidMap fileToGuid; //lockname |-> GUID; locks can be referenced by a lockfilename or alternatively a GUID + GuidToLockMap guidToLock; //GUID |-> "shared lock ownership" }; @@ -549,5 +658,5 @@ DirLock::DirLock(const Zstring& lockfilename, DirLockCallback* callback) //throw } #endif - sharedLock = LockAdmin::instance().retrieve(lockfilename, callback); + sharedLock = LockAdmin::instance().retrieve(lockfilename, callback); //throw FileError } |