summaryrefslogtreecommitdiff
path: root/lib/dir_lock.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dir_lock.cpp')
-rw-r--r--lib/dir_lock.cpp407
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
}
bgstack15