// Copyright (c) 2013-2015 IONU Security, Inc. All rights reserved. // Copyright (c) 2016 Sequence Logic, Inc. All rights reserved. // // File locking functions and classes #include #include #include #include #include #include #include #ifdef WIN32 #include #include #include #ifdef WIN64 typedef __int64 ssize_t; #else typedef __int32 ssize_t; #endif #else #include #include #endif #include "eyeconstants.h" #include "eyejson.h" #include "eyelock.h" #include "eyelog.h" #include "eyetime.h" #include "eyeutils.h" using namespace std; using namespace sequencelogic; namespace sequencelogic { pthread_mutex_t ionu_filelock_mutex = PTHREAD_MUTEX_INITIALIZER; #ifdef LOCK_STRATEGY_MEMORY EyeFileLocker* EyeFileLocker::_instance = nullptr; std::mutex EyeFileLocker::_threadLocker; #endif static std::string LastEyeLockMessage = ""; #ifdef WIN32 TCHAR IONU_MASTER_LOCK_SHARED[] = TEXT ("Local\\EyeFileLockMappingObject"); HANDLE hMapFile = INVALID_HANDLE_VALUE; LPTSTR pBuf = nullptr; #endif } #define IONU_MASTER_LOCK_FILE ".IONU_eyefile_lock.lk" #define IONU_FILE_LOCK_MAX_SIZE 16384 #define IONU_FILE_LOCK_ATTEMPTS 3 // Number of tries before returning timed out #define IONU_FILE_LOCK_WAIT 1000 // Number of micro seconds for timeout interval #define IONU_FILE_LOCK_WRITE_BIAS 3 // Number of write attempts vs. read #define IONU_FILE_LOCK_EXPIRE 3600 // Number of seconds before lock expires const std::string sequencelogic::GetLastEyeLockMessage() { return LastEyeLockMessage; } // Release a lock without respect to existing owners, called after shred and delete of .ionu void sequencelogic::ReleaseEyeLock (const std::string filename) { #ifdef LOCK_STRATEGY_DOTLK // Remove any old locks std::string lockfile = sequencelogic::TempFilename (filename, ".lk"); #ifdef WIN32 DeleteFile (lockfile.c_str()); #else remove (lockfile.c_str()); #endif #elif defined (LOCK_STRATEGY_MASTER) sequencelogic::LockFile (filename, "", IONU_FILE_LOCK_REMOVE); #else #endif } // Helper functions to ensure all data is read/written #ifdef WIN32 bool sequencelogic::ReadLockFile (HANDLE fd, const char* buf, size_t bytes) { DWORD numread; if (ReadFile (fd, (char*)buf, (DWORD)bytes, &numread, NULL) && numread == bytes) { return true; } else { return false; } } #else bool sequencelogic::ReadLockFile (int fd, const char* buf, size_t bytes) { size_t toread, nread = 0; ssize_t result; do { toread = bytes - nread; result = read (fd, (char*)buf + nread, toread); if (result >= 0) nread += result; else if (errno != EINTR) return false; } while (nread < bytes); return nread > 0; } #endif #ifdef WIN32 bool sequencelogic::WriteLockFile (HANDLE fd, const char* buf, size_t bytes) { if (SetFilePointer (fd, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { return false; } DWORD numwrite; if (WriteFile (fd, (char*)buf, (DWORD)bytes, &numwrite, NULL) && numwrite == bytes) { return true; } else { return false; } } #else bool sequencelogic::WriteLockFile (int fd, const char* buf, size_t bytes) { size_t towrite, nwrite = 0; ssize_t result; if (lseek (fd, 0, SEEK_SET) != 0) return false; do { towrite = bytes - nwrite; result = write (fd, (char*)buf + nwrite, towrite); if (result >= 0) nwrite += result; else if (errno != EINTR) return false; } while (nwrite < bytes); return true; } #endif // Create a new lock file #ifdef WIN32 IONU_FILE_LOCK_STATUS sequencelogic::CreateLockFile (HANDLE fd, const std::string& lockfile, const std::string&strpid, const std::string& id, const std::string& rwlock, const std::string& time) #else IONU_FILE_LOCK_STATUS sequencelogic::CreateLockFile (int fd, const std::string& lockfile, const std::string&strpid, const std::string& id, const std::string& rwlock, const std::string& time) #endif { IONU_FILE_LOCK_STATUS rc = IONU_FILE_LOCK_OK; bool removeit = false; if (rwlock == "U") removeit = true; else { EyeJSONObject* locks = new EyeJSONObject (NULL, JSON_ARRAY, NULL); if (locks) { EyeJSONObject* lock = new EyeJSONObject (NULL, JSON_OBJECT, locks); locks->AddMember (lock); if (lock) { lock->AddMember (new EyeJSONScalar ("pid", strpid.c_str(), JSON_STRING)); lock->AddMember (new EyeJSONScalar ("id", id.c_str(), JSON_STRING)); lock->AddMember (new EyeJSONScalar ("type", rwlock.c_str(), JSON_STRING)); lock->AddMember (new EyeJSONScalar ("time", time.c_str(), JSON_STRING)); } std::string json; locks->GetJSONString (json); //cout << "creating lock file: " << json << std::endl; delete locks; if (json.length() > 24) { // Always include the null termination as we are overwriting if (!WriteLockFile (fd, json.c_str(), json.length() + 1)) { IONUERROR ("sequencelogic::LockFile() - unable to write lock file: %d", errno); rc = IONU_FILE_LOCK_ERROR; } } } else rc = IONU_FILE_LOCK_ERROR; } #ifdef WIN32 CloseHandle (fd); #else close (fd); #endif if (removeit) sequencelogic::RemoveFile (lockfile); return rc; } // Update an existing lock file #ifdef WIN32 IONU_FILE_LOCK_STATUS sequencelogic::UpdateLockFile (HANDLE fd, std::string& lockfile, EyeJSONObject* locks) #else IONU_FILE_LOCK_STATUS sequencelogic::UpdateLockFile (int fd, std::string& lockfile, EyeJSONObject* locks) #endif { IONU_FILE_LOCK_STATUS rc = IONU_FILE_LOCK_OK; std::string json; bool removeit = false; //cout << "Updating lock file: " << json << std::endl; // No more locks so remove the lock file if (!locks || locks->GetNumMembers() == 0) { WriteLockFile (fd, "", 1); //cout << "sequencelogic::LockFile(" << lockfile << ") - removed lock file" << std::endl; removeit = true; } else { locks->GetJSONString (json); // Always include the null termination as we are overwriting if (!WriteLockFile (fd, json.c_str(), json.length() + 1)) { IONUERROR ("sequencelogic::LockFile(%s) - unable to write lock file: %d", lockfile.c_str(), errno); rc = IONU_FILE_LOCK_ERROR; } } #ifdef WIN32 CloseHandle (fd); #else close (fd); #endif if (removeit) sequencelogic::RemoveFile (lockfile); return rc; } bool sequencelogic::CheckActiveProcess (const std::string pid) { bool active = true; stringstream ss; ss << pid; #ifdef WIN32 DWORD lpid = 0; ss >> lpid; // Get the list of process identifiers. DWORD aProcesses[1024], cbNeeded, cProcesses; if (!EnumProcesses (aProcesses, sizeof(aProcesses), &cbNeeded)) { // Failed, try the OpenProcess option, which won't work on privileged processes HANDLE processHandle = OpenProcess (PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, lpid); if (processHandle == 0) { // Process is no longer running, delete all of it's locks active = false; } else CloseHandle(processHandle); } else { // Calculate how many process identifiers were returned. cProcesses = cbNeeded / sizeof(DWORD); active = false; for (unsigned int i = 0; i < cProcesses; ++i) { if (aProcesses[i] == lpid) { active = true; break; } } } #else pid_t lpid = 0; ss >> lpid; if (kill (lpid, 0) == -1) { if (errno != ESRCH) { IONUDEBUG ("sequencelogic::LockFile() - unable to find lock owner: %d", errno); } active = false; } #endif return active; } void sequencelogic::CleanLocks (EyeJSONObject* locks, const std::string& strpid, const std::string& id, IONU_FILE_LOCK_TYPE ltype) { if (!locks) return; EyeTime lktime; // Remove all stale locks for (int index = (int)locks->GetNumMembers() - 1; index >= 0; index--) { EyeJSONObject* lock = static_cast(locks->GetMember (index)); if (lock) { EyeJSONScalar* lockpid = static_cast(lock->GetMember("pid")); EyeJSONScalar* lockid = static_cast(lock->GetMember("id")); EyeJSONScalar* locktype = static_cast(lock->GetMember("type")); EyeJSONScalar* locktime = static_cast(lock->GetMember("time")); if (!lockpid || !lockid || !locktype || !locktime) { return; } lktime.SetTime (locktime->GetValue()); // Check if the lock has expired if (lktime.SecondsFromNow() > IONU_FILE_LOCK_EXPIRE) { IONUDEBUG ("sequencelogic::LockFile() - removing stale lock (%f') for pid: %s", lktime.SecondsFromNow(), lockpid->GetValue()); locks->RemoveMember (index); continue; } // Does not belong to this process, if hosts match check if process is running else if (strpid.compare (lockpid->GetValue()) != 0) { std::vector lockedPID = sequencelogic::SplitString (lockpid->GetValue(), ':'); std::vector currentPID = sequencelogic::SplitString (strpid, ':'); if (lockedPID.size() != 2) { IONUDEBUG ("sequencelogic::LockFile() - removing invalid lock for pid: %s", lockpid->GetValue()); locks->RemoveMember (index); } // Check if locked on this host and if the process is still running else if (lockedPID[0] == currentPID[0] && !CheckActiveProcess (lockedPID[1])) { locks->RemoveMember (index); IONUDEBUG ("sequencelogic::LockFile() - removed old lock: %s", lockpid->GetValue()); } } // Never block on a clear else if (ltype == IONU_FILE_LOCK_CLEAR) { if (strpid.compare (lockpid->GetValue()) == 0 && id.compare (lockid->GetValue()) == 0) { //IONUDEBUG ("sequencelogic::LockFile() - popping lock %s for %s:%s", locktype->GetValue(), lockpid->GetValue(), lockid->GetValue()); char* val = locktype->GetValue(); size_t len = sequencelogic::StrLen (val, 64); if (len > 1) { val[len-1] = '\0'; locktype->ReplaceValue (val); } else { locks->RemoveMember (index); } } } } } } #ifdef LOCK_STRATEGY_DOTLK IONU_FILE_LOCK_STATUS sequencelogic::LockFile (const std::string& filename, const std::string& id, IONU_FILE_LOCK_TYPE ltype) { if (!sequencelogic::IsValidFilename (filename) || id.size() == 0) { IONUERROR ("sequencelogic::LockFile() - invalid filename or id"); return IONU_FILE_LOCK_ERROR; } if (sequencelogic::IsLockFile (filename)) { IONUERROR ("sequencelogic::LockFile(%s) - can't lock a lockfile", filename.c_str()); return IONU_FILE_LOCK_ERROR; } IONU_FILE_LOCK_STATUS rc = IONU_FILE_LOCK_ERROR; const char* rwlock = (ltype == IONU_FILE_LOCK_READ ? "R" : (ltype == IONU_FILE_LOCK_WRITE ? "W" : "U")); std::string lockfile = sequencelogic::TempFilename (filename, ".lk"); #ifdef WIN32 HANDLE fd; #else int fd; #endif int attempt; bool result = false; bool waiting = false; std::string json; // Get the host:pid identifier char hostname[32]; #ifdef WIN32 DWORD pid = GetCurrentProcessId(); #else pid_t pid = getpid(); #endif stringstream sspid; if (gethostname (hostname, 31) == 0) sspid << hostname << ":" << pid; else sspid << pid; std::string strpid = sspid.str(); EyeTime now; EyeJSONObject* locks = nullptr; EyeJSONObject* lock = nullptr; char* buffer = nullptr; //IONUDEBUG ("lock %s:%s:%s", strpid, id, rwlock); // Set mutex lock while checking file locks to keep it all thread safe pthread_mutex_lock (&ionu_filelock_mutex); for (attempt = 0; attempt < IONU_FILE_LOCK_ATTEMPTS; ++attempt) { #ifdef WIN32 bool fexists = false; if (_access (lockfile.c_str(), 0) == 0) // File exists fexists = true; if ((fd = CreateFile (lockfile.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_HIDDEN, NULL)) == INVALID_HANDLE_VALUE) { if (::GetLastError() == ERROR_ACCESS_DENIED && ltype != IONU_FILE_LOCK_WRITE) { pthread_mutex_unlock (&ionu_filelock_mutex); if (fexists) { // Access error, file exists, trying to unlock, sleep and retry if (ltype == IONU_FILE_LOCK_CLEAR) { std::this_thread::sleep_for(std::chrono::microseconds(IONU_FILE_LOCK_WAIT)); pthread_mutex_lock (&ionu_filelock_mutex); waiting = true; continue; } // Read lock in readonly directory else if (ltype == IONU_FILE_LOCK_READ) return IONU_FILE_LOCK_OK; } } else { IONUERROR ("sequencelogic::LockFile(%s) - unable to open or create lock file: %s", filename.c_str(), sequencelogic::GetLastErrorMessage().c_str()); pthread_mutex_unlock (&ionu_filelock_mutex); return IONU_FILE_LOCK_ERROR; } } if (fexists == true) { DWORD wfsize = GetFileSize (fd, NULL); if (wfsize == INVALID_FILE_SIZE) { CloseHandle (fd); IONUERROR ("sequencelogic::LockFile(%s) - GetFileSize failed on lock file: %d", filename.c_str(), errno); pthread_mutex_unlock (&ionu_filelock_mutex); return IONU_FILE_LOCK_ERROR; } size_t fsize = (size_t)wfsize; #else // Test for existance and create if not if ((fd = open (lockfile.c_str(), O_RDWR | O_CREAT | O_EXCL, S_IRWXU)) == -1) { // Read only directory and not requesting a write lock is success if (errno == EACCES && ltype != IONU_FILE_LOCK_WRITE) { pthread_mutex_unlock (&ionu_filelock_mutex); return (access (lockfile.c_str(), 0) == 0) ? IONU_FILE_LOCK_ERROR : IONU_FILE_LOCK_OK; } else if (errno != EEXIST) { IONUERROR ("sequencelogic::LockFile(%s) - unable to create lock file: %d", filename.c_str(), errno); pthread_mutex_unlock (&ionu_filelock_mutex); return IONU_FILE_LOCK_ERROR; } // File exists, open it if ((fd = open (lockfile.c_str(), O_RDWR)) == -1) { IONUERROR ("sequencelogic::LockFile(%s) - unable to open lock file: %d", filename.c_str(), errno); pthread_mutex_unlock (&ionu_filelock_mutex); return IONU_FILE_LOCK_ERROR; } struct stat fileStat; if (fstat (fd, &fileStat) < 0) { close (fd); IONUERROR ("sequencelogic::LockFile(%s) - unable to stat lock file: %d", filename.c_str(), errno); pthread_mutex_unlock (&ionu_filelock_mutex); return IONU_FILE_LOCK_ERROR; } size_t fsize = (size_t)fileStat.st_size; #endif buffer = (char*) sequencelogic::New (fsize); if (!buffer) { IONUERROR ("sequencelogic::LockFile(%s) - unable to allocate buffer", filename.c_str()); pthread_mutex_unlock (&ionu_filelock_mutex); return IONU_FILE_LOCK_ERROR; } result = ReadLockFile (fd, buffer, fsize); if (result && fsize > 24) { // Read the existing lock file into memory //IONUDEBUG ("%s.lk - %s %s:%s", filename, buffer, strpid.c_str(), id.c_str()); if (locks) delete locks; locks = new EyeJSONObject (buffer); delete[] buffer; buffer = nullptr; if (!locks) { IONUERROR ("sequencelogic::LockFile(%s) - unable to allocate locks object", filename.c_str()); pthread_mutex_unlock (&ionu_filelock_mutex); return IONU_FILE_LOCK_ERROR; } // Remove any stale locks, and process unlock request CleanLocks (locks, strpid, id, ltype); if (ltype == IONU_FILE_LOCK_CLEAR) { rc = UpdateLockFile (fd, lockfile, locks); delete locks; locks = nullptr; pthread_mutex_unlock (&ionu_filelock_mutex); return rc; } // Loop through the existing locks and process the requested lock for (size_t index = 0; index < locks->GetNumMembers(); ++index) { lock = static_cast(locks->GetMember (index)); if (lock) { EyeJSONScalar* lockpid = static_cast(lock->GetMember("pid")); EyeJSONScalar* lockid = static_cast(lock->GetMember("id")); EyeJSONScalar* locktype = static_cast(lock->GetMember("type")); EyeJSONScalar* locktime = static_cast(lock->GetMember("time")); if (!lockpid || !lockid || !locktype || !locktime) { delete locks; locks = nullptr; #ifdef WIN32 CloseHandle (fd); #else close (fd); #endif sequencelogic::RemoveFile (lockfile); IONUERROR ("sequencelogic::LockFile(%s) - invalid lock file", filename.c_str()); pthread_mutex_unlock (&ionu_filelock_mutex); return IONU_FILE_LOCK_ERROR; } // Check if the lock belongs to this process else if (strpid.compare (lockpid->GetValue()) == 0) { // Check if the lock belongs to this thread/id if (id.compare (lockid->GetValue()) != 0) { // Existing or requested W lock, wait and retry if (ltype == IONU_FILE_LOCK_WRITE || strchr (locktype->GetValue(), 'W')) { //IONUDEBUG ("sequencelogic::LockFile(%s) - thread %s waiting for %s lock on %s, attempt %d", filename, id.c_str(), rwlock, lockid->GetValue(), attempt); #ifdef WIN32 CloseHandle (fd); #else close (fd); #endif pthread_mutex_unlock (&ionu_filelock_mutex); if (ltype == IONU_FILE_LOCK_WRITE) std::this_thread::sleep_for(std::chrono::microseconds(IONU_FILE_LOCK_WAIT / IONU_FILE_LOCK_WRITE_BIAS)); else std::this_thread::sleep_for(std::chrono::microseconds(IONU_FILE_LOCK_WAIT)); pthread_mutex_lock (&ionu_filelock_mutex); LastEyeLockMessage = (strcmp (locktype->GetValue(), "W") == 0) ? "Write lock at " : "Read lock at "; LastEyeLockMessage += locktime->GetValue(); LastEyeLockMessage += " by: "; LastEyeLockMessage += lockid->GetValue(); waiting = true; break; } } else { if (ltype == IONU_FILE_LOCK_WRITE && locks->GetNumMembers() > 1) { //IONUDEBUG ("sequencelogic::LockFile(%s) - thread %s waiting for %s lock on %s, attempt %d", filename, id.c_str(), rwlock, lockid->GetValue(), attempt); #ifdef WIN32 CloseHandle (fd); #else close (fd); #endif pthread_mutex_unlock (&ionu_filelock_mutex); std::this_thread::sleep_for(std::chrono::microseconds (IONU_FILE_LOCK_WAIT)); pthread_mutex_lock (&ionu_filelock_mutex); LastEyeLockMessage = (strcmp (locktype->GetValue(), "W") == 0) ? "Write lock at " : "Read lock at "; LastEyeLockMessage += locktime->GetValue(); LastEyeLockMessage += " by: "; LastEyeLockMessage += lockid->GetValue(); waiting = true; break; } else { locktype->ReplaceValue (rwlock); locktime->ReplaceValue (now.GetISO8601Time()); rc = UpdateLockFile (fd, lockfile, locks); delete locks; locks = nullptr; pthread_mutex_unlock (&ionu_filelock_mutex); return rc; } } } else if (ltype == IONU_FILE_LOCK_WRITE || strchr (locktype->GetValue(), 'W')) { //IONUDEBUG ("sequencelogic::LockFile(%s) - process %s waiting for %s lock on %s, attempt %d", filename.c_str(), strpid.c_str(), rwlock, lockpid->GetValue(), attempt); #ifdef WIN32 CloseHandle (fd); #else close (fd); #endif pthread_mutex_unlock (&ionu_filelock_mutex); if (ltype == IONU_FILE_LOCK_WRITE) std::this_thread::sleep_for(std::chrono::microseconds(IONU_FILE_LOCK_WAIT / IONU_FILE_LOCK_WRITE_BIAS)); else std::this_thread::sleep_for(std::chrono::microseconds(IONU_FILE_LOCK_WAIT)); pthread_mutex_lock (&ionu_filelock_mutex); LastEyeLockMessage = (strcmp (locktype->GetValue(), "W") == 0) ? "Write lock at " : "Read lock at "; LastEyeLockMessage += locktime->GetValue(); LastEyeLockMessage += " by: "; LastEyeLockMessage += lockid->GetValue(); waiting = true; break; } } } // end for // Need to add a new lock if (locks && !waiting) { lock = new EyeJSONObject (NULL, JSON_OBJECT, locks); locks->AddMember (lock); lock->AddMember (new EyeJSONScalar ("pid", strpid.c_str(), JSON_STRING)); lock->AddMember (new EyeJSONScalar ("id", id.c_str(), JSON_STRING)); lock->AddMember (new EyeJSONScalar ("type", rwlock, JSON_STRING)); lock->AddMember (new EyeJSONScalar ("time", now.GetISO8601Time(), JSON_STRING)); rc = UpdateLockFile (fd, lockfile, locks); delete locks; locks = nullptr; pthread_mutex_unlock (&ionu_filelock_mutex); return rc; } else if (locks) { delete locks; locks = nullptr; } waiting = false; continue; } else { if (buffer) { delete[] buffer; buffer = nullptr; } rc = CreateLockFile (fd, lockfile, strpid, id, rwlock, now.GetISO8601Time()); pthread_mutex_unlock (&ionu_filelock_mutex); return rc; } } // Create new lock file, add requested lock else { rc = CreateLockFile (fd, lockfile, strpid, id, rwlock, now.GetISO8601Time()); pthread_mutex_unlock (&ionu_filelock_mutex); return rc; } } if (attempt >= IONU_FILE_LOCK_ATTEMPTS) { IONUDEBUG ("sequencelogic::LockFile(%s) - timeout trying to obtain %s lock for %s", filename.c_str(), rwlock, id.c_str()); pthread_mutex_unlock (&ionu_filelock_mutex); return IONU_FILE_LOCK_TIMEOUT; } if (locks) { delete locks; locks = nullptr; } pthread_mutex_unlock (&ionu_filelock_mutex); return IONU_FILE_LOCK_OK; } EyeFileLock::EyeFileLock(IONU_FILE_LOCK_TYPE ltype, const char* filename, const char* id) :_id(id ? id : ""), _filename(filename), _ltype(ltype), _status(IONU_FILE_LOCK_ERROR) { if (_id.length() == 0) { //Get a thread id, however your platform has to do that #if defined(__MACH__) mach_port_t tid = pthread_mach_thread_np(pthread_self()); #elif defined(WIN32) int tid = ::GetCurrentThreadId(); #else pthread_t tid = pthread_self(); #endif std::stringstream tmpStr; tmpStr << tid; _id = tmpStr.str(); } _status = LockFile(_filename.c_str(), _id.c_str(), _ltype); } EyeFileLock::~EyeFileLock() { LockFile(_filename.c_str(), _id.c_str(), IONU_FILE_LOCK_CLEAR); } bool EyeFileLock::convertToWrite() { //only state change from read to write allowed so we confirm read if (_ltype != IONU_FILE_LOCK_READ) { return false; } return acquire (IONU_FILE_LOCK_WRITE); } bool EyeFileLock::convertToRead() { // Only allow state change from write to read if (_ltype != IONU_FILE_LOCK_WRITE) { return false; } return acquire (IONU_FILE_LOCK_READ); } bool EyeFileLock::acquire (IONU_FILE_LOCK_TYPE ltype) { _status = LockFile(_filename.c_str(), _id.c_str(), ltype); if (_status == IONU_FILE_LOCK_OK) { _ltype = ltype; return true; } else { return false; } } bool EyeFileLock::release() { return acquire (IONU_FILE_LOCK_CLEAR); } sequencelogic::EyeFileReadLock::EyeFileReadLock(const char* filename, const char* id) :EyeFileLock(IONU_FILE_LOCK_READ, filename, id) { } sequencelogic::EyeFileWriteLock::EyeFileWriteLock(const char* filename, const char* id) :EyeFileLock(IONU_FILE_LOCK_WRITE, filename, id) { } #endif #ifdef LOCK_STRATEGY_MEMORY EyeFileLock::EyeFileLock(IONU_FILE_LOCK_TYPE ltype, const std::string& filename, const std::string& id) :_id(id), _filename(filename), _ltype(ltype), _status(IONU_FILE_LOCK_ERROR) { if (_id.length() == 0) { //Get a thread id, however your platform has to do that #if defined(__MACH__) mach_port_t tid = pthread_mach_thread_np(pthread_self()); #elif defined(WIN32) int tid = ::GetCurrentThreadId(); #else pthread_t tid = pthread_self(); #endif std::stringstream tmpStr; tmpStr << tid; _id = tmpStr.str(); } _status = EyeFileLocker::GetInstance()->LockFile (_ltype, _filename, _id); } sequencelogic::EyeFileLock::~EyeFileLock() { EyeFileLocker::GetInstance()->LockFile (IONU_FILE_LOCK_CLEAR, _filename, _id); } bool EyeFileLock::convertToRead() { // Only allow state change from write to read if (_ltype != IONU_FILE_LOCK_WRITE) { return false; } return acquire (IONU_FILE_LOCK_READ); } bool EyeFileLock::convertToWrite() { // Only allow state change from read to write if (_ltype != IONU_FILE_LOCK_READ) { return false; } return acquire (IONU_FILE_LOCK_WRITE); } bool EyeFileLock::release() { return acquire (IONU_FILE_LOCK_CLEAR); } bool EyeFileLock::acquire (IONU_FILE_LOCK_TYPE ltype) { _status = EyeFileLocker::GetInstance()->LockFile (ltype, _filename, _id); if (_status == IONU_FILE_LOCK_OK) { _ltype = ltype; return true; } else { return false; } } EyeFileReadLock::EyeFileReadLock (const std::string& filename, const std::string& id) :EyeFileLock (IONU_FILE_LOCK_READ, filename, id) { } EyeFileWriteLock::EyeFileWriteLock (const std::string& filename, const std::string& id) :EyeFileLock (IONU_FILE_LOCK_WRITE, filename, id) { } IONU_FILE_LOCK_STATUS sequencelogic::LockFile (const std::string& filename, const std::string& id, IONU_FILE_LOCK_TYPE rwlock) { return EyeFileLocker::GetInstance()->LockFile (rwlock, filename, id); } EyeFileLocker::EyeFileLocker() { _instance = nullptr; } EyeFileLocker::~EyeFileLocker() { } EyeFileLocker* EyeFileLocker::GetInstance () { // Construct singleton if needed if (_instance == nullptr) { std::lock_guard lock(_threadLocker); // Check again in case instance was created while waiting for mutex if (_instance == nullptr) { _instance = new EyeFileLocker(); } } return _instance; } IONU_FILE_LOCK_STATUS EyeFileLocker::LockFile (IONU_FILE_LOCK_TYPE ltype, const std::string& filename, const std::string& id) { if (sequencelogic::IsLockFile (filename)) { IONUERROR ("sequencelogic::LockFile(%s) - can't lock a lockfile", filename.c_str()); return IONU_FILE_LOCK_ERROR; } // Try more attempts in the same time for write locks to give them a better chance size_t retries = (ltype == IONU_FILE_LOCK_READ ? IONU_FILE_LOCK_ATTEMPTS : IONU_FILE_LOCK_ATTEMPTS * IONU_FILE_LOCK_WRITE_BIAS); LastEyeLockMessage = ""; for (size_t attempt = 0; attempt < retries; ++attempt) { switch (LockFileAttempt (filename, id, ltype)) { case IONU_FILE_LOCK_OK: return IONU_FILE_LOCK_OK; break; case IONU_FILE_LOCK_ERROR: return IONU_FILE_LOCK_ERROR; break; case IONU_FILE_LOCK_TIMEOUT: if (ltype == IONU_FILE_LOCK_WRITE) std::this_thread::sleep_for(std::chrono::microseconds (IONU_FILE_LOCK_WAIT / IONU_FILE_LOCK_WRITE_BIAS)); else std::this_thread::sleep_for(std::chrono::microseconds (IONU_FILE_LOCK_WAIT)); break; default: return IONU_FILE_LOCK_ERROR; break; } } return IONU_FILE_LOCK_TIMEOUT; } // Attempt to acquire the requested lock, if valid but unable to obtain return IONU_FILE_LOCK_TIMEOUT for retry IONU_FILE_LOCK_STATUS EyeFileLocker::LockFileAttempt (const std::string& filename, const std::string& id, IONU_FILE_LOCK_TYPE ltype) { std::string rwlock = (ltype == IONU_FILE_LOCK_READ ? "R" : (ltype == IONU_FILE_LOCK_WRITE ? "W" : "U")); std::lock_guard lock(_threadLocker); auto search = _lockmap.find (filename); if (search != _lockmap.end ()) { if (ltype == IONU_FILE_LOCK_REMOVE) { _lockmap.erase (search); //cout << "removed " << endl; return IONU_FILE_LOCK_OK; } EyeJSONObject lock (search->second.c_str()); // Multiple locks on this file if (lock.GetType() == JSON_ARRAY) { if (ltype == IONU_FILE_LOCK_WRITE) { //cout << "waiting on array " << endl; EyeJSONObject* elock = static_cast(lock.GetMember ((size_t)0)); EyeJSONScalar* lockid = static_cast(elock->GetMember("id")); LastEyeLockMessage = "Read lock by: "; LastEyeLockMessage += (lockid ? lockid->GetValue() : "unknown"); return IONU_FILE_LOCK_TIMEOUT; } else { for (size_t i = 0; i < lock.GetNumMembers(); ++i) { EyeJSONObject* elock = static_cast(lock.GetMember (i)); EyeJSONScalar* lockid = static_cast(elock->GetMember("id")); EyeJSONScalar* locktype = static_cast(elock->GetMember("type")); if (lockid && locktype) { if (id.compare (lockid->GetValue()) == 0) { if (ltype == IONU_FILE_LOCK_CLEAR) { lock.RemoveMember(i); if (lock.GetNumMembers() > 1) search->second = lock.Stringify(); else if (lock.GetNumMembers() == 1) { elock = static_cast(lock.GetMember ((size_t)0)); search->second = elock->Stringify(); } //cout << "clear " << search->second << endl; return IONU_FILE_LOCK_OK; } else if (ltype == IONU_FILE_LOCK_READ && strcmp (locktype->GetValue(), "R") == 0) { return IONU_FILE_LOCK_OK; } else { LastEyeLockMessage = (ltype == IONU_FILE_LOCK_READ ? "Write lock by: " : "Read lock by: "); LastEyeLockMessage += lockid->GetValue(); return IONU_FILE_LOCK_ERROR; } } } } // Add a new read lock if (ltype == IONU_FILE_LOCK_READ) { EyeJSONObject* alock = new EyeJSONObject (nullptr, JSON_OBJECT, nullptr); alock->AddMember (new EyeJSONScalar ("id", id.c_str(), JSON_STRING)); alock->AddMember (new EyeJSONScalar ("type", rwlock.c_str(), JSON_STRING)); lock.AddMember (alock); search->second = lock.Stringify(); //cout << "add " << search->second << endl; return IONU_FILE_LOCK_OK; } else { return IONU_FILE_LOCK_ERROR; } } } // Single lock on this file else if (lock.GetType() == JSON_OBJECT) { EyeJSONScalar* lockid = static_cast(lock.GetMember("id")); EyeJSONScalar* locktype = static_cast(lock.GetMember("type")); if (lockid && locktype) { if (id.compare (lockid->GetValue()) == 0) { if (ltype == IONU_FILE_LOCK_CLEAR) { _lockmap.erase (search); //cout << "cleared " << endl; return IONU_FILE_LOCK_OK; } // Already have the requested lock type else if (rwlock.compare (locktype->GetValue()) == 0) { return IONU_FILE_LOCK_OK; } // Convert existing lock from read to write, or vice verse else { locktype->ReplaceValue (rwlock.c_str()); search->second = lock.Stringify(); //cout << "convert " << search->second << endl; return IONU_FILE_LOCK_OK; } } // Another thread has a read lock and requested lock is also a read lock, add both locks to array else if (ltype == IONU_FILE_LOCK_READ && strcmp (locktype->GetValue(), "R") == 0) { EyeJSONObject locks (nullptr, JSON_ARRAY, nullptr); EyeJSONObject* elock = new EyeJSONObject (nullptr, JSON_OBJECT, nullptr); elock->AddMember (new EyeJSONScalar ("id", lockid->GetValue(), JSON_STRING)); elock->AddMember (new EyeJSONScalar ("type", locktype->GetValue(), JSON_STRING)); locks.AddMember (elock); elock = new EyeJSONObject (nullptr, JSON_OBJECT, nullptr); elock->AddMember (new EyeJSONScalar ("id", id.c_str(), JSON_STRING)); elock->AddMember (new EyeJSONScalar ("type", rwlock.c_str(), JSON_STRING)); locks.AddMember (elock); search->second = locks.Stringify(); //cout << "added " << search->second << endl; return IONU_FILE_LOCK_OK; } // Clear request, but no lock by us else if (ltype == IONU_FILE_LOCK_CLEAR) { return IONU_FILE_LOCK_OK; } // Need to wait and retry else { //cout << "waiting on lock " << endl; LastEyeLockMessage = (ltype == IONU_FILE_LOCK_READ ? "Write lock by: " : "Read lock by: "); LastEyeLockMessage += lockid->GetValue(); return IONU_FILE_LOCK_TIMEOUT; } } else { return IONU_FILE_LOCK_ERROR; } } else { return IONU_FILE_LOCK_ERROR; } } else if (ltype != IONU_FILE_LOCK_CLEAR) { EyeJSONObject lock (nullptr, JSON_OBJECT, nullptr); lock.AddMember (new EyeJSONScalar ("id", id.c_str(), JSON_STRING)); lock.AddMember (new EyeJSONScalar ("type", rwlock.c_str(), JSON_STRING)); _lockmap.insert (std::pair(filename, lock.Stringify())); //cout << "first " << lock.Stringify() << endl; return IONU_FILE_LOCK_OK; } return ltype == IONU_FILE_LOCK_CLEAR ? IONU_FILE_LOCK_OK : IONU_FILE_LOCK_ERROR; } #endif #ifdef LOCK_STRATEGY_MASTER IONU_FILE_LOCK_STATUS sequencelogic::LockFile (const std::string& filename, const std::string& id, IONU_FILE_LOCK_TYPE ltype) { if (sequencelogic::IsLockFile (filename)) { IONUERROR ("sequencelogic::LockFile(%s) - can't lock a lockfile", filename.c_str()); return IONU_FILE_LOCK_ERROR; } // Deal with case, separator, 8.3, relative, subst, network drive, long names, UNC... std::string path = sequencelogic::Canonicalise (filename); // Try more attempts in the same time for write locks to give them a better chance size_t retries = (ltype == IONU_FILE_LOCK_READ ? IONU_FILE_LOCK_ATTEMPTS : IONU_FILE_LOCK_ATTEMPTS * IONU_FILE_LOCK_WRITE_BIAS); for (size_t attempt = 0; attempt < retries; ++attempt) { LastEyeLockMessage = ""; switch (LockFileAttempt (path, id, ltype)) { case IONU_FILE_LOCK_OK: return IONU_FILE_LOCK_OK; break; case IONU_FILE_LOCK_ERROR: return IONU_FILE_LOCK_ERROR; break; case IONU_FILE_LOCK_TIMEOUT: if (ltype == IONU_FILE_LOCK_WRITE) std::this_thread::sleep_for(std::chrono::microseconds (IONU_FILE_LOCK_WAIT / IONU_FILE_LOCK_WRITE_BIAS)); else std::this_thread::sleep_for(std::chrono::microseconds (IONU_FILE_LOCK_WAIT)); break; default: return IONU_FILE_LOCK_ERROR; break; } } return IONU_FILE_LOCK_TIMEOUT; } EyeFileLock::EyeFileLock(IONU_FILE_LOCK_TYPE ltype, const std::string& filename, const std::string& id) :_id(id), _filename(sequencelogic::Canonicalise(filename)), _ltype(ltype), _status(IONU_FILE_LOCK_ERROR) { if (_id.length() == 0) { //Get a thread id, however your platform has to do that #if defined(__MACH__) mach_port_t tid = pthread_mach_thread_np(pthread_self()); #elif defined(WIN32) int tid = ::GetCurrentThreadId(); #else pthread_t tid = pthread_self(); #endif std::stringstream tmpStr; tmpStr << tid; _id = tmpStr.str(); } acquire (ltype); } EyeFileLock::~EyeFileLock() { bool released = release(); // We *really* don't want to fail, so try for a while if lock is not released for (int i = 1; !released && i <= IONU_FILE_LOCK_ATTEMPTS; ++i) { std::this_thread::sleep_for(std::chrono::microseconds(i * IONU_FILE_LOCK_WAIT)); released = release(); } // TODO: If result is false, the lock is now orphaned // This is really bad! Consider recording the orphaned lock and keep // trying to release it? if (!released) { IONUDEBUG("EyeFileLock::~EyeFileLock() Failed to release lock on %s", _filename.c_str()); } } bool EyeFileLock::convertToRead() { // Only allow state change from write to read if (_ltype != IONU_FILE_LOCK_WRITE) { pthread_mutex_lock (&ionu_filelock_mutex); LastEyeLockMessage = "No write lock by: "; LastEyeLockMessage += _id.c_str(); pthread_mutex_unlock (&ionu_filelock_mutex); IONUDEBUG ("EyeFileLock::convertToRead() %s", LastEyeLockMessage.c_str()); return false; } return acquire (IONU_FILE_LOCK_READ); } bool EyeFileLock::convertToWrite() { // Only allow state change from read to write if (_ltype != IONU_FILE_LOCK_READ) { pthread_mutex_lock (&ionu_filelock_mutex); LastEyeLockMessage = "No read lock by: "; LastEyeLockMessage += _id.c_str(); pthread_mutex_unlock (&ionu_filelock_mutex); IONUDEBUG ("EyeFileLock::convertToRead() %s", LastEyeLockMessage.c_str()); return false; } return acquire (IONU_FILE_LOCK_WRITE); } bool EyeFileLock::release() { return acquire (IONU_FILE_LOCK_CLEAR); } bool EyeFileLock::acquire(IONU_FILE_LOCK_TYPE ltype) { _status = LockFile (_filename, _id, ltype); if (_status == IONU_FILE_LOCK_OK) { _ltype = ltype; return true; } else { return false; } } #ifdef WIN32 ////////////////////////////////////////////////////////////////////////////////////////// // Following code lifted from http://stackoverflow.com/questions/910528/how-to-change-the-acls-from-c #include #include static bool SetFilePermission(LPCSTR FileName) { PSID pEveryoneSID = NULL; PACL pACL = NULL; EXPLICIT_ACCESS ea[1]; SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY; // Create a well-known SID for the Everyone group. if (!AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pEveryoneSID)) return false; // Initialize an EXPLICIT_ACCESS structure for an ACE. // The ACE will allow Everyone read access to the key. ZeroMemory(&ea, 1 * sizeof(EXPLICIT_ACCESS)); ea[0].grfAccessPermissions = 0xFFFFFFFF; ea[0].grfAccessMode = GRANT_ACCESS; ea[0].grfInheritance= NO_INHERITANCE; ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; ea[0].Trustee.ptstrName = (LPTSTR) pEveryoneSID; // Create a new ACL that contains the new ACEs. SetLastError(SetEntriesInAcl(1, ea, NULL, &pACL)); // Function actually returns a windows error code if (GetLastError() != ERROR_SUCCESS) return false; // Initialize a security descriptor. PSECURITY_DESCRIPTOR pSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); if (!InitializeSecurityDescriptor(pSD,SECURITY_DESCRIPTOR_REVISION)) return false; // Add the ACL to the security descriptor. if (!SetSecurityDescriptorDacl(pSD, TRUE, // bDaclPresent flag pACL, FALSE)) // not a default DACL return false; //Change the security attributes if (!SetFileSecurity(FileName, DACL_SECURITY_INFORMATION, pSD)) return false; if (pEveryoneSID) FreeSid(pEveryoneSID); if (pACL) LocalFree(pACL); if (pSD) LocalFree(pSD); return true; } #endif IONU_FILE_LOCK_STATUS sequencelogic::LockFileAttempt(const std::string& filename, const std::string& id,IONU_FILE_LOCK_TYPE ltype) { bool addLock = true; bool modified = false; IONU_FILE_LOCK_STATUS gotLock = IONU_FILE_LOCK_OK; EyeJSONObject* lock = nullptr; EyeJSONScalar* lockid = nullptr; EyeJSONScalar* lockpid = nullptr; EyeJSONScalar* locktype = nullptr; EyeJSONScalar* locktime = nullptr; char* buffer = nullptr; EyeTime now; // Get the pid #ifdef WIN32 DWORD pid = GetCurrentProcessId(); #else pid_t pid = getpid(); #endif stringstream sspid; sspid << pid; std::string strpid = sspid.str(); // Set mutex lock while checking file locks to keep it all thread safe pthread_mutex_lock (&ionu_filelock_mutex); LastEyeLockMessage = ""; #ifdef SHARED_MEMORY // Open or create shared memory page for locks the first time in this process if (hMapFile == INVALID_HANDLE_VALUE) { hMapFile = OpenFileMapping( FILE_MAP_ALL_ACCESS, // read/write access FALSE, // do not inherit the name IONU_MASTER_LOCK_SHARED); // name of mapping object // Open failed so create shared memory page for locks if (hMapFile == NULL) hMapFile = CreateFileMapping( INVALID_HANDLE_VALUE, // use paging file NULL, // default security PAGE_READWRITE, // read/write access 0, // maximum object size (high-order DWORD) IONU_FILE_LOCK_MAX_SIZE, // maximum object size (low-order DWORD) IONU_MASTER_LOCK_SHARED); // name of mapping object // Open and Create both failed if (hMapFile == NULL) { LastEyeLockMessage = "CreateFileMapping() failed "; LastEyeLockMessage += sequencelogic::GetLastErrorMessage(); IONUDEBUG ("EyeFileLock::acquire() %s", LastEyeLockMessage.c_str()); return IONU_FILE_LOCK_ERROR; } pBuf = (LPTSTR) MapViewOfFile (hMapFile, // handle to map object FILE_MAP_ALL_ACCESS, // read/write permission 0, // file offset high-order 0, // file offset low-order IONU_FILE_LOCK_MAX_SIZE); if (pBuf == NULL) { LastEyeLockMessage = "MapViewOfFile() failed "; LastEyeLockMessage += sequencelogic::GetLastErrorMessage(); IONUDEBUG ("EyeFileLock::acquire() %s", LastEyeLockMessage.c_str()); CloseHandle (hMapFile); hMapFile = INVALID_HANDLE_VALUE; return IONU_FILE_LOCK_ERROR; } // Initialize with null string else { CopyMemory ((PVOID)pBuf, "", 1); } } buffer = pBuf; if (buffer) { #elif defined(WIN32) // Read/Create the master lock file std::string tempPath = getenv("windir"); if (tempPath == "") { pthread_mutex_unlock (&ionu_filelock_mutex); LastEyeLockMessage = "Failed to get temporary path - "; LastEyeLockMessage += sequencelogic::GetLastErrorMessage(); IONUDEBUG ("EyeFileLock::acquire() %s", LastEyeLockMessage.c_str()); return IONU_FILE_LOCK_ERROR; } std::string masterLockFile(tempPath); masterLockFile += "\\temp\\"; masterLockFile += IONU_MASTER_LOCK_FILE; HANDLE handle = CreateFile (masterLockFile.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); DWORD createFileError = GetLastError(); if (createFileError != ERROR_ALREADY_EXISTS) { if (!SetFilePermission(masterLockFile.c_str())) { // Flag this in a way that's unambiguously bad! DWORD error = GetLastError(); char lastError[5000]; FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) lastError, sizeof(lastError), NULL ); MessageBox(0, lastError, "sequencelogic::LockFileAttempt() unable to set file permissions for Master Lock File", MB_OK); if (handle != INVALID_HANDLE_VALUE) { CloseHandle(handle); handle = INVALID_HANDLE_VALUE; } } else if (createFileError == ERROR_SHARING_VIOLATION) { // We expect sharing violations to occur because different processes use the lock file // Since we were able to set file permissions, we can retry later and expect eventual success. LastEyeLockMessage = "Master lock file sharing violation"; pthread_mutex_unlock (&ionu_filelock_mutex); IONUDEBUG ("EyeFileLock::acquire() %s", LastEyeLockMessage.c_str()); return IONU_FILE_LOCK_TIMEOUT; } } if (handle != INVALID_HANDLE_VALUE) { BY_HANDLE_FILE_INFORMATION fileInfo = { 0 }; GetFileInformationByHandle (handle, &fileInfo); DWORD nread = 0; if (fileInfo.nFileSizeLow > 0) { buffer = (char*)sequencelogic::New ((size_t)fileInfo.nFileSizeLow + 1); if (buffer == nullptr) { LastEyeLockMessage = "Allocation failed"; pthread_mutex_unlock (&ionu_filelock_mutex); IONUDEBUG ("EyeFileLock::acquire() %s", LastEyeLockMessage.c_str()); return IONU_FILE_LOCK_ERROR; } if (ReadFile (handle, buffer, fileInfo.nFileSizeLow, &nread, NULL) != TRUE) { LastEyeLockMessage = "Read failed - "; LastEyeLockMessage += sequencelogic::GetLastErrorMessage(); sequencelogic::RemoveFile (masterLockFile); pthread_mutex_unlock (&ionu_filelock_mutex); CloseHandle (handle); IONUDEBUG ("EyeFileLock::acquire() ReadFile failed %s", LastEyeLockMessage.c_str()); delete[] buffer; return IONU_FILE_LOCK_ERROR; } buffer[nread] = '\0'; } #else std::string masterLockFile ("/tmp/"); masterLockFile += IONU_MASTER_LOCK_FILE; fstream file (masterLockFile, ios::in | ios::out | ios::binary | ios::ate); if (file.is_open()) { size_t bytes = (size_t)file.tellg(); file.seekg (0, ios::beg); buffer = (char*)sequencelogic::New (bytes + 1); if (buffer == nullptr) { LastEyeLockMessage = "Allocation failed"; pthread_mutex_unlock (&ionu_filelock_mutex); IONUDEBUG ("EyeFileLock::acquire() %s", LastEyeLockMessage.c_str()); return IONU_FILE_LOCK_ERROR; } buffer[bytes] = '\0'; if (bytes > 0) { file.read (buffer, bytes); if (file.bad()) { LastEyeLockMessage = "Read failed - "; LastEyeLockMessage += sequencelogic::GetLastErrorMessage(); sequencelogic::RemoveFile (masterLockFile); pthread_mutex_unlock (&ionu_filelock_mutex); file.close(); IONUDEBUG ("EyeFileLock::acquire() read failed %s", LastEyeLockMessage.c_str()); delete[] buffer; return IONU_FILE_LOCK_ERROR; } } } else { file.open (masterLockFile, ios::out | ios::binary | ios::trunc); } if (file.is_open()) { #endif EyeJSONObject locks (buffer); if (locks.GetNumMembers() > 0) { for (int i = (int)locks.GetNumMembers() - 1; i >= 0; --i) { lock = dynamic_cast(locks.GetMember (i)); if (lock && lock->GetKey()) { // Just in case there are no locks if (lock->GetNumMembers() == 0) { locks.RemoveMember (i); modified = true; continue; } if (filename.compare (lock->GetKey()) == 0) { if (ltype == IONU_FILE_LOCK_REMOVE) { locks.RemoveMember (i); modified = true; addLock = false; break; } for (int j = (int)lock->GetNumMembers() - 1; j >= 0; j--) { EyeJSONObject* elock = dynamic_cast(lock->GetMember (j)); lockpid = static_cast(elock->GetMember("pid")); lockid = static_cast(elock->GetMember("id")); locktype = static_cast(elock->GetMember("type")); locktime = static_cast(elock->GetMember("time")); if (lockpid && lockid && locktype && CheckActiveProcess (lockpid->GetValue())) { if (id.compare (lockid->GetValue()) == 0 && strpid.compare (lockpid->GetValue()) == 0) { // Clear lock if (ltype == IONU_FILE_LOCK_CLEAR) { if (lock->GetNumMembers() == 1) locks.RemoveMember (i); else lock->RemoveMember(j); modified = true; addLock = false; break; } // Already have read lock else if (ltype == IONU_FILE_LOCK_READ && strcmp (locktype->GetValue(), "R") == 0) { addLock = false; break; } // Already have write lock else if (ltype == IONU_FILE_LOCK_WRITE && strcmp (locktype->GetValue(), "W") == 0) { addLock = false; break; } // Convert to read lock else if (ltype == IONU_FILE_LOCK_READ && strcmp (locktype->GetValue(), "W") == 0) { locktype->ReplaceValue ("R"); modified = true; addLock = false; break; } // Convert to write lock, if there are no other locks besides ours else if (lock->GetNumMembers() == 1 && ltype == IONU_FILE_LOCK_WRITE && strcmp (locktype->GetValue(), "R") == 0) { locktype->ReplaceValue ("W"); modified = true; addLock = false; break; } // Can't get the requested lock else { gotLock = IONU_FILE_LOCK_TIMEOUT; if (strcmp (locktype->GetValue(), "R") == 0) LastEyeLockMessage = "Read lock at "; else LastEyeLockMessage = "Write lock at "; LastEyeLockMessage += (locktime ? locktime->GetValue() : "?"); LastEyeLockMessage += " by: "; LastEyeLockMessage += lockpid->GetValue(); LastEyeLockMessage += ":"; LastEyeLockMessage += lockid->GetValue(); addLock = false; break; } } // Another owner has a write lock or we want a write lock else if (ltype == IONU_FILE_LOCK_WRITE || strcmp (locktype->GetValue(), "W") == 0) { gotLock = IONU_FILE_LOCK_TIMEOUT; if (strcmp (locktype->GetValue(), "R") == 0) LastEyeLockMessage = "Read lock at "; else LastEyeLockMessage = "Write lock at "; LastEyeLockMessage += (locktime ? locktime->GetValue() : "?"); LastEyeLockMessage += " by: "; LastEyeLockMessage += lockpid->GetValue(); LastEyeLockMessage += ":"; LastEyeLockMessage += lockid->GetValue(); addLock = false; break; } } // Invalid lock or owner process not running else { lock->RemoveMember(j); modified = true; } } // Add a new read lock to lock array if (addLock && gotLock == IONU_FILE_LOCK_OK && ltype == IONU_FILE_LOCK_READ) { EyeJSONObject* alock = new EyeJSONObject (nullptr, JSON_OBJECT, lock); alock->AddMember (new EyeJSONScalar ("pid", strpid.c_str(), JSON_STRING)); alock->AddMember (new EyeJSONScalar ("id", id.c_str(), JSON_STRING)); alock->AddMember (new EyeJSONScalar ("type", "R", JSON_STRING)); alock->AddMember (new EyeJSONScalar ("time", now.GetISO8601Time(), JSON_STRING)); lock->AddMember (alock); modified = true; addLock = false; } } } } } // No existing lock, add ours if (addLock && (ltype == IONU_FILE_LOCK_READ || ltype == IONU_FILE_LOCK_WRITE)) { EyeJSONObject* array = new EyeJSONObject (filename.c_str(), JSON_ARRAY, &locks); EyeJSONObject* alock = new EyeJSONObject (nullptr, JSON_OBJECT, array); alock->AddMember (new EyeJSONScalar ("pid", strpid.c_str(), JSON_STRING)); alock->AddMember (new EyeJSONScalar ("id", id.c_str(), JSON_STRING)); alock->AddMember (new EyeJSONScalar ("type", ltype == IONU_FILE_LOCK_READ ? "R" : "W", JSON_STRING)); alock->AddMember (new EyeJSONScalar ("time", now.GetISO8601Time(), JSON_STRING)); array->AddMember (alock); locks.AddMember (array); modified = true; } if (modified && locks.GetNumMembers() >= 1) { std::string json = locks.Stringify(); //cout << json << endl; #ifdef SHARED_MEMORY // Copy lock data to shared memory CopyMemory ((PVOID)pBuf, json.c_str(), json.size() + 1); buffer = nullptr; //UnmapViewOfFile (pBuf); //CloseHandle (hMapFile); #elif defined (WIN32) DWORD nwritten; SetFilePointer (handle, 0, NULL, FILE_BEGIN); SetEndOfFile (handle); if (WriteFile (handle, json.c_str(), (DWORD)(json.size() + 1), &nwritten, NULL) != TRUE || nwritten != (DWORD)(json.size() + 1)) { LastEyeLockMessage = "Write failed - "; LastEyeLockMessage += sequencelogic::GetLastErrorMessage(); IONUDEBUG ("EyeFileLock::acquire() %s", LastEyeLockMessage.c_str()); sequencelogic::RemoveFile (masterLockFile); gotLock = IONU_FILE_LOCK_ERROR; } if (CloseHandle (handle) == 0) { LastEyeLockMessage = "Close failed - "; LastEyeLockMessage += sequencelogic::GetLastErrorMessage(); IONUDEBUG ("EyeFileLock::acquire() %s", LastEyeLockMessage.c_str()); gotLock = IONU_FILE_LOCK_ERROR; } #else file.seekp (0, ios::beg); file.write (json.c_str(), json.size() + 1); if (file.bad()) { LastEyeLockMessage = "Write failed - "; LastEyeLockMessage += sequencelogic::GetLastErrorMessage(); IONUDEBUG ("EyeFileLock::acquire() %s", LastEyeLockMessage.c_str()); sequencelogic::RemoveFile (masterLockFile); gotLock = IONU_FILE_LOCK_ERROR; } file.close(); #endif } // No more locks or unchanged else { #ifdef SHARED_MEMORY // Done with shared memory, write null string if (modified) CopyMemory ((PVOID)pBuf, "", 1); //UnmapViewOfFile (pBuf); //CloseHandle (hMapFile); buffer = nullptr; #elif defined (WIN32) // On Windows we keep the file around if (modified) { SetFilePointer (handle, 0, NULL, FILE_BEGIN); SetEndOfFile (handle); } CloseHandle (handle); #else file.close(); if (modified) sequencelogic::RemoveFile (masterLockFile); #endif } } else { LastEyeLockMessage = "Open failed - "; LastEyeLockMessage += sequencelogic::GetLastErrorMessage(); IONUDEBUG ("EyeFileLock::acquire() %s", LastEyeLockMessage.c_str()); gotLock = IONU_FILE_LOCK_ERROR; } pthread_mutex_unlock (&ionu_filelock_mutex); if (buffer) delete[] buffer; return gotLock; } EyeFileReadLock::EyeFileReadLock (const std::string& filename, const std::string& id) :EyeFileLock (IONU_FILE_LOCK_READ, filename, id) { } EyeFileWriteLock::EyeFileWriteLock (const std::string& filename, const std::string& id) :EyeFileLock (IONU_FILE_LOCK_WRITE, filename, id) { } #endif