Sleds/libeye/eyelock.cpp

1554 lines
61 KiB
C++

// 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 <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <cstring>
#include <sstream>
#include <chrono>
#include <thread>
#ifdef WIN32
#include <io.h>
#include <Windows.h>
#include <psapi.h>
#ifdef WIN64
typedef __int64 ssize_t;
#else
typedef __int32 ssize_t;
#endif
#else
#include <signal.h>
#include <unistd.h>
#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<EyeJSONObject*>(locks->GetMember (index));
if (lock) {
EyeJSONScalar* lockpid = static_cast<EyeJSONScalar*>(lock->GetMember("pid"));
EyeJSONScalar* lockid = static_cast<EyeJSONScalar*>(lock->GetMember("id"));
EyeJSONScalar* locktype = static_cast<EyeJSONScalar*>(lock->GetMember("type"));
EyeJSONScalar* locktime = static_cast<EyeJSONScalar*>(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<std::string> lockedPID = sequencelogic::SplitString (lockpid->GetValue(), ':');
std::vector<std::string> 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<EyeJSONObject*>(locks->GetMember (index));
if (lock) {
EyeJSONScalar* lockpid = static_cast<EyeJSONScalar*>(lock->GetMember("pid"));
EyeJSONScalar* lockid = static_cast<EyeJSONScalar*>(lock->GetMember("id"));
EyeJSONScalar* locktype = static_cast<EyeJSONScalar*>(lock->GetMember("type"));
EyeJSONScalar* locktime = static_cast<EyeJSONScalar*>(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<std::mutex> 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<std::mutex> 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<EyeJSONObject*>(lock.GetMember ((size_t)0));
EyeJSONScalar* lockid = static_cast<EyeJSONScalar*>(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<EyeJSONObject*>(lock.GetMember (i));
EyeJSONScalar* lockid = static_cast<EyeJSONScalar*>(elock->GetMember("id"));
EyeJSONScalar* locktype = static_cast<EyeJSONScalar*>(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<EyeJSONObject*>(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<EyeJSONScalar*>(lock.GetMember("id"));
EyeJSONScalar* locktype = static_cast<EyeJSONScalar*>(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<std::string,std::string>(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 <Accctrl.h>
#include <Aclapi.h>
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<EyeJSONObject*>(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<EyeJSONObject*>(lock->GetMember (j));
lockpid = static_cast<EyeJSONScalar*>(elock->GetMember("pid"));
lockid = static_cast<EyeJSONScalar*>(elock->GetMember("id"));
locktype = static_cast<EyeJSONScalar*>(elock->GetMember("type"));
locktime = static_cast<EyeJSONScalar*>(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