Attached is a pseudo-locking mechanism I wrote, intending to use it for
Windows CE. The design goal for the code was to allow the existing os_win.c
locking code to remain intact with zero replacement coding involved.
With that in mind, I basically implemented LockFile(), LockFileEx() and
UnlockFile() using a mutex and some shared global memory. It is similar in
nature to Nuno Lucas's "idea" behind locking -- however, its completely
re-realized in this code.
The code is designed around SQLite's behavioral use of those API functions
and for testing purposes, does not require Windows CE to run and test.
Please have a look at it and poke some holes in it.
Robert
#define _CRT_SECURE_NO_DEPRECATE
#include <windows.h>
// The following code is copied over from SQLITE so this app can test itself
#ifndef SQLITE_TEST
#define PENDING_BYTE 0x40000000 /* First byte past the 1GB boundary */
#else
extern unsigned int sqlite3_pending_byte;
#define PENDING_BYTE sqlite3_pending_byte
#endif
#define RESERVED_BYTE (PENDING_BYTE+1)
#define SHARED_FIRST (PENDING_BYTE+2)
#define SHARED_SIZE 510
typedef struct _LOCKDATA
{
long nReaders;
long nPending;
long nReserved;
long nExclusive;
} LOCKDATA;
typedef struct _LOCKSTRUCT
{
HANDLE hFile;
HANDLE hMutex;
HANDLE hShared;
LOCKDATA local;
LOCKDATA *shared;
} LOCKSTRUCT;
// In live mode, the "x" prefix will be removed for Windows CE, and these
macros will allow the main os_win.c code to implement locking
// without any modification
#define xLockFile(a, b, c, d, e) pseudoLockFile(&a, b, c, d, e)
#define xUnlockFile(a, b, c, d, e) pseudoUnlockFile(&a, b, c, d, e)
#define xLockFileEx(a, b, c, d, e, f) pseudoLockFileEx(&a, b, c, d, e, f)
#define HANDLE_TO_LOCKSTRUCT(a) (LOCKSTRUCT
*)&((LPBYTE)a)[-PtrToInt((&((LOCKSTRUCT *)0)->hFile))]
#define MUTEX_ACQUIRE(h) { DWORD dwErr; do { dwErr = WaitForSingleObject(h,
INFINITE); } while (dwErr != WAIT_OBJECT_0 && dwErr != WAIT_ABANDONED); }
#define MUTEX_RELEASE(h) ReleaseMutex(h)
BOOL CreateLockStruct(WCHAR *pszFilename, LOCKSTRUCT *pLocks)
{
WCHAR szName[MAX_PATH];
WCHAR *pszTok;
BOOL bInit = FALSE;
// Initialize the local lockdata
ZeroMemory(&pLocks->local, sizeof(LOCKDATA));
// Create a unique global name for the mutex and subsequently the shared
memory
wcscpy(szName, L"sqliteL");
wcscat(szName, pszFilename);
while (pszTok = wcschr(szName, '\\'))
{
*pszTok = '_';
}
// Create/open the named mutex
pLocks->hMutex = CreateMutexW(NULL, FALSE, szName);
if (!pLocks->hMutex) return FALSE;
// Acquire the mutex before continuing
MUTEX_ACQUIRE(pLocks->hMutex);
// Create/open the shared memory
wcscat(szName, L"MEM");
pLocks->hShared = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE, 0, sizeof(LOCKDATA), szName);
// Set a flag that indicates we're the first to create the memory so it must
be zero-initialized
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
bInit = TRUE;
}
// If we succeeded in making the shared memory handle, map it.
if (pLocks->hShared)
{
pLocks->shared = (LOCKDATA *)MapViewOfFile(pLocks->hShared,
FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, sizeof(LOCKDATA));
// If mapping failed, close the shared memory handle and erase it
if (!pLocks->shared)
{
CloseHandle(pLocks->hShared);
pLocks->hShared = NULL;
}
}
// If shared memory could not be created, then close the mutex and fail
if (pLocks->hShared == NULL)
{
MUTEX_RELEASE(pLocks->hMutex);
CloseHandle(pLocks->hMutex);
pLocks->hMutex = NULL;
return FALSE;
}
// Initialize the shared memory if we're supposed to
if (bInit)
{
ZeroMemory(pLocks->shared, sizeof(LOCKDATA));
}
MUTEX_RELEASE(pLocks->hMutex);
return TRUE;
}
void DestroyLockStruct(LOCKSTRUCT *pLocks)
{
if (pLocks->hMutex)
{
// Acquire the mutex
MUTEX_ACQUIRE(pLocks->hMutex);
// The following blocks should probably assert in debug mode, but they
// are to cleanup in case any locks remained open
if (pLocks->local.nReaders)
pLocks->shared->nReaders --;
if (pLocks->local.nReserved)
{
pLocks->shared->nReserved = 0;
}
if (pLocks->local.nPending)
{
pLocks->shared->nPending = 0;
}
if (pLocks->local.nExclusive)
{
pLocks->shared->nExclusive = 0;
}
// De-reference and close our copy of the shared memory handle
UnmapViewOfFile(pLocks->shared);
CloseHandle(pLocks->hShared);
// Done with the mutex
MUTEX_RELEASE(pLocks->hMutex);
CloseHandle(pLocks->hMutex);
pLocks->hMutex = NULL;
}
}
// Custom pseudo file locking support specifically for SQLite
BOOL pseudoLockFile(HANDLE *phFile, DWORD dwFileOffsetLow, DWORD
dwFileOffsetHigh, DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh)
{
LOCKSTRUCT *pls = HANDLE_TO_LOCKSTRUCT(phFile);
BOOL bReturn = FALSE;
MUTEX_ACQUIRE(pls->hMutex);
// Wanting an exclusive lock?
if (dwFileOffsetLow == SHARED_FIRST && nNumberOfBytesToLockLow == SHARED_SIZE)
{
if (pls->shared->nReaders == 0 && pls->shared->nExclusive == 0)
{
pls->shared->nExclusive = 1;
pls->local.nExclusive = 1;
bReturn = TRUE;
}
else if (pls->local.nExclusive) // We already had an exclusive lock
{
pls->local.nExclusive ++;
bReturn = TRUE;
}
}
// Want a read-only lock?
else if ((dwFileOffsetLow >= SHARED_FIRST && dwFileOffsetLow < SHARED_FIRST +
SHARED_SIZE) && nNumberOfBytesToLockLow == 1)
{
if (pls->shared->nExclusive == 0)
{
pls->local.nReaders ++;
if (pls->local.nReaders == 1)
{
pls->shared->nReaders ++;
}
bReturn = TRUE;
}
}
// Want a pending lock?
else if (dwFileOffsetLow == PENDING_BYTE && nNumberOfBytesToLockLow == 1)
{
// If no pending lock has been acquired, then acquire it
if (pls->shared->nPending == 0)
{
pls->shared->nPending = 1;
pls->local.nPending = 1;
bReturn = TRUE;
}
else if (pls->local.nPending)
{
pls->local.nPending ++;
bReturn = TRUE; // Already had a pending lock
}
}
// Want a reserved lock?
else if (dwFileOffsetLow == RESERVED_BYTE && nNumberOfBytesToLockLow == 1)
{
if (pls->shared->nReserved == 0)
{
pls->shared->nReserved = 1;
pls->local.nReserved = 1;
bReturn = TRUE;
}
else if (pls->local.nReserved)
{
pls->local.nReserved ++;
bReturn = TRUE; // Already had a reserved lock
}
}
MUTEX_RELEASE(pls->hMutex);
return bReturn;
}
BOOL pseudoUnlockFile(HANDLE *phFile, DWORD dwFileOffsetLow, DWORD
dwFileOffsetHigh, DWORD nNumberOfBytesToUnlockLow, DWORD
nNumberOfBytesToUnlockHigh)
{
LOCKSTRUCT *pls = HANDLE_TO_LOCKSTRUCT(phFile);
BOOL bReturn = FALSE;
MUTEX_ACQUIRE(pls->hMutex);
// Releasing a reader lock or an exclusive lock
if (dwFileOffsetLow >= SHARED_FIRST && dwFileOffsetLow < SHARED_FIRST +
SHARED_SIZE)
{
// Did we have an exclusive lock?
if (pls->local.nExclusive)
{
pls->local.nExclusive --;
if (pls->local.nExclusive == 0)
{
pls->shared->nExclusive = 0;
}
bReturn = TRUE;
}
// Did we just have a reader lock?
else if (pls->local.nReaders)
{
pls->local.nReaders --;
if (pls->local.nReaders == 0)
{
pls->shared->nReaders --;
}
bReturn = TRUE;
}
}
// Releasing a pending lock
else if (dwFileOffsetLow == PENDING_BYTE && nNumberOfBytesToUnlockLow == 1)
{
if (pls->local.nPending)
{
pls->local.nPending --;
if (pls->local.nPending == 0)
{
pls->shared->nPending = 0;
}
bReturn = TRUE;
}
}
// Releasing a reserved lock
else if (dwFileOffsetLow == RESERVED_BYTE && nNumberOfBytesToUnlockLow == 1)
{
if (pls->local.nReserved)
{
pls->local.nReserved --;
if (pls->local.nReserved == 0)
{
pls->shared->nReserved = 0;
}
bReturn = TRUE;
}
}
MUTEX_RELEASE(pls->hMutex);
return bReturn;
}
BOOL pseudoLockFileEx(HANDLE *phFile, DWORD dwFlags, DWORD dwReserved, DWORD
nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh, LPOVERLAPPED
lpOverlapped)
{
// If the caller wants a shared read lock, foward this call to pseudoLockFile
if (lpOverlapped->Offset == SHARED_FIRST && dwFlags == 1 &&
nNumberOfBytesToLockLow == SHARED_SIZE)
return pseudoLockFile(phFile, SHARED_FIRST, 0, 1, 0);
return FALSE;
}
int main()
{
LOCKSTRUCT ls;
BOOL res;
CreateLockStruct(L"foo", &ls);
res = xLockFile(ls.hFile, SHARED_FIRST, 0, SHARED_SIZE, 0);
res = xUnlockFile(ls.hFile, SHARED_FIRST, 0, SHARED_SIZE, 0);
DestroyLockStruct(&ls);
return 0;
}