foundationdb/fdbserver/VFSAsync.cpp

800 lines
27 KiB
C++

/*
* VFSAsync.cpp
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "sqlite/sqlite3.h"
#include <stdio.h>
#include <string>
#include <vector>
#include "fdbrpc/fdbrpc.h"
#include "fdbrpc/IAsyncFile.h"
#include "fdbserver/CoroFlow.h"
#include "fdbrpc/simulator.h"
#include "fdbrpc/AsyncFileReadAhead.actor.h"
#include <assert.h>
#include <string.h>
#ifdef WIN32
#include <Windows.h>
#endif
#ifdef __unixish__
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#endif
/*
** The maximum pathname length supported by this VFS.
*/
#define MAXPATHNAME 512
#define NO_LOCK 0
#define SHARED_LOCK 1
#define RESERVED_LOCK 2
#define PENDING_LOCK 3
#define EXCLUSIVE_LOCK 4
const uint32_t RESERVED_COUNT = 1U<<29;
/*
** When using this VFS, the sqlite3_file* handles that SQLite uses are
** actually pointers to instances of type VFSAsyncFile.
*/
typedef struct VFSAsyncFile VFSAsyncFile;
struct VFSAsyncFile {
sqlite3_file base; /* Base class. Must be first. */
int flags;
std::string filename;
Reference<IAsyncFile> file;
uint32_t * const pLockCount; // +1 for each SHARED_LOCK, or 1+X_COUNT for lock level X
int lockLevel; // NO_LOCK, SHARED_LOCK, RESERVED_LOCK, PENDING_LOCK, or EXCLUSIVE_LOCK
struct SharedMemoryInfo *sharedMemory;
int sharedMemorySharedLocks;
int sharedMemoryExclusiveLocks;
int debug_zcrefs, debug_zcreads, debug_reads;
int chunkSize;
VFSAsyncFile(std::string const& filename, int flags) : filename(filename), flags(flags), pLockCount(&filename_lockCount_openCount[filename].first), debug_zcrefs(0), debug_zcreads(0), debug_reads(0), chunkSize(0) {
filename_lockCount_openCount[filename].second++;
}
~VFSAsyncFile();
static std::map<std::string, std::pair<uint32_t,int>> filename_lockCount_openCount;
};
std::map<std::string, std::pair<uint32_t,int>> VFSAsyncFile::filename_lockCount_openCount;
static int asyncClose(sqlite3_file *pFile){
VFSAsyncFile *p = (VFSAsyncFile*)pFile;
/*TraceEvent("VFSAsyncClose").detail("Fd", p->file->debugFD())
.detail("Filename", p->filename).detail("ZCRefs", p->debug_zcrefs)
.detail("ZCReads", p->debug_zcreads).detail("NormalReads", p->debug_reads).backtrace();*/
//printf("Closing %s: %d zcrefs, %d/%d reads zc\n", filename.c_str(), debug_zcrefs, debug_zcreads, debug_zcreads+debug_reads);
ASSERT( !p->debug_zcrefs );
p->~VFSAsyncFile();
return SQLITE_OK;
}
static int asyncRead(sqlite3_file *pFile, void *zBuf, int iAmt, sqlite_int64 iOfst) {
VFSAsyncFile *p = (VFSAsyncFile*)pFile;
try {
++p->debug_reads;
int readBytes = waitForAndGet( p->file->read( zBuf, iAmt, iOfst ) );
if (readBytes < iAmt) {
memset((uint8_t*)zBuf + readBytes, 0, iAmt-readBytes); // When reading past the EOF, sqlite expects the extra portion of the buffer to be zeroed
return SQLITE_IOERR_SHORT_READ;
}
return SQLITE_OK;
} catch (Error& ) {
return SQLITE_IOERR_READ;
}
}
#if 1
static int asyncReleaseZeroCopy(sqlite3_file* pFile, void* data, int iAmt, sqlite_int64 iOfst) {
VFSAsyncFile *p = (VFSAsyncFile*)pFile;
try{
--p->debug_zcrefs;
p->file->releaseZeroCopy( data, iAmt, iOfst );
} catch (Error& ) {
return SQLITE_IOERR;
}
return SQLITE_OK;
}
static int asyncReadZeroCopy(sqlite3_file *pFile, void **data, int iAmt, sqlite_int64 iOfst, int *pDataWasCached) {
VFSAsyncFile *p = (VFSAsyncFile*)pFile;
try {
int readBytes = iAmt;
Future<Void> readFuture = p->file->readZeroCopy( data, &readBytes, iOfst );
if(pDataWasCached)
*pDataWasCached = readFuture.isReady() ? 1 : 0;
waitFor(readFuture);
++p->debug_zcrefs;
if (readBytes < iAmt) {
// When reading past the EOF, sqlite expects the extra portion of the buffer to be zeroed. We can't do that, so return and sqlite will use the slow path.
asyncReleaseZeroCopy(pFile, *data, readBytes, iOfst);
return SQLITE_IOERR_SHORT_READ;
}
++p->debug_zcreads;
return SQLITE_OK;
} catch (Error& ) {
return SQLITE_IOERR_READ;
}
}
#else
static int asyncReadZeroCopy(sqlite3_file *pFile, void **data, int iAmt, sqlite_int64 iOfst) {
VFSAsyncFile *p = (VFSAsyncFile*)pFile;
try {
*data = new char[iAmt];
int readBytes = waitForAndGet( p->file->read( *data, iAmt, iOfst ) );
//printf("+asyncReadRef %p +%lld %d/%d = %p\n", pFile, iOfst, readBytes, iAmt, *data);
if (readBytes < iAmt) {
memset((uint8_t*)*data + readBytes, 0, iAmt-readBytes); // When reading past the EOF, sqlite expects the extra portion of the buffer to be zeroed
return SQLITE_IOERR_SHORT_READ;
}
return SQLITE_OK;
} catch (Error& ) {
return SQLITE_IOERR_READ;
}
}
static int asyncReleaseZeroCopy(sqlite3_file* pFile, void* data, int iAmt, sqlite_int64 iOfst) {
//printf("-asyncReleaseRef %p +%lld %d <= %p\n", pFile, iOfst, iAmt, data);
delete[] (char*)data;
return SQLITE_OK;
}
#endif
static int asyncWrite(sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst) {
VFSAsyncFile *p = (VFSAsyncFile*)pFile;
try {
waitFor( p->file->write( zBuf, iAmt, iOfst ) );
return SQLITE_OK;
} catch(Error& ) {
return SQLITE_IOERR_WRITE;
}
}
static int asyncTruncate(sqlite3_file *pFile, sqlite_int64 size){
VFSAsyncFile *p = (VFSAsyncFile*)pFile;
// Adjust size to a multiple of chunkSize if set
if(p->chunkSize != 0) {
size = ((size + p->chunkSize - 1) / p->chunkSize) * p->chunkSize;
}
try {
waitFor( p->file->truncate( size ) );
return SQLITE_OK;
} catch(Error& ) {
return SQLITE_IOERR_TRUNCATE;
}
}
static int asyncSync(sqlite3_file *pFile, int flags){
VFSAsyncFile *p = (VFSAsyncFile*)pFile;
try {
waitFor( p->file->sync() );
return SQLITE_OK;
} catch (Error& e) {
TraceEvent("VFSSyncError")
.error(e)
.detail("Filename", p->filename)
.detail("Sqlite3File", (int64_t)pFile)
.detail("IAsyncFile", (int64_t)p->file.getPtr());
return SQLITE_IOERR_FSYNC;
}
}
/*
** Write the size of the file in bytes to *pSize.
*/
static int VFSAsyncFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
VFSAsyncFile *p = (VFSAsyncFile*)pFile;
try {
*pSize = waitForAndGet( p->file->size() );
return SQLITE_OK;
} catch (Error& ) {
return SQLITE_IOERR_FSTAT;
}
}
static int asyncLock(sqlite3_file *pFile, int eLock){
//VFSAsyncFile *p = (VFSAsyncFile*)pFile;
//TraceEvent("FileLock").detail("File", p->filename).detail("Fd", p->file->debugFD()).detail("PrevLockLevel", p->lockLevel).detail("Op", eLock).detail("LockCount", *p->pLockCount);
return eLock == EXCLUSIVE_LOCK ? SQLITE_BUSY : SQLITE_OK;
}
static int asyncUnlock(sqlite3_file *pFile, int eLock) {
assert( eLock <= SHARED_LOCK );
return SQLITE_OK;
}
static int asyncCheckReservedLock(sqlite3_file *pFile, int *pResOut){
VFSAsyncFile *p = (VFSAsyncFile*)pFile;
*pResOut = *p->pLockCount >= RESERVED_COUNT;
return SQLITE_OK;
}
/*
** No xFileControl() verbs are implemented by this VFS.
*/
static int VFSAsyncFileControl(sqlite3_file *pFile, int op, void *pArg){
VFSAsyncFile *p = (VFSAsyncFile*)pFile;
switch(op) {
case SQLITE_FCNTL_CHUNK_SIZE:
p->chunkSize = *(int *)pArg;
return SQLITE_OK;
case SQLITE_FCNTL_SIZE_HINT:
return asyncTruncate(pFile, *(int64_t *)pArg);
default:
return SQLITE_NOTFOUND;
};
}
static int asyncSectorSize(sqlite3_file *pFile){ return 512; } // SOMEDAY: Would 4K be better?
static int asyncDeviceCharacteristics(sqlite3_file *pFile){ return 0; }
#if 1
struct SharedMemoryInfo { // for a file
std::string filename;
std::vector<void*> regions;
int regionSize;
int refcount; // Number of connections with this open
int sharedLocks[SQLITE_SHM_NLOCK];
int exclusiveLocks[SQLITE_SHM_NLOCK];
SharedMemoryInfo() : regionSize(0), refcount(0) {
memset(sharedLocks, 0, sizeof(sharedLocks));
memset(exclusiveLocks, 0, sizeof(exclusiveLocks));
}
void cleanup(){
for(int i=0; i<regions.size(); i++)
delete[] (uint8_t*)regions[i];
table.erase(filename);
}
static Mutex mutex;
static std::map< std::string, SharedMemoryInfo > table;
};
Mutex SharedMemoryInfo::mutex;
std::map< std::string, SharedMemoryInfo > SharedMemoryInfo::table;
/*
** This function is called to obtain a pointer to region iRegion of the
** shared-memory associated with the database file fd. Shared-memory regions
** are numbered starting from zero. Each shared-memory region is szRegion
** bytes in size.
**
** If an error occurs, an error code is returned and *pp is set to NULL.
**
** Otherwise, if the bExtend parameter is 0 and the requested shared-memory
** region has not been allocated (by any client, including one running in a
** separate process), then *pp is set to NULL and SQLITE_OK returned. If
** bExtend is non-zero and the requested shared-memory region has not yet
** been allocated, it is allocated by this function.
**
** If the shared-memory region has already been allocated or is allocated by
** this call as described above, then it is mapped into this processes
** address space (if it is not already), *pp is set to point to the mapped
** memory and SQLITE_OK returned.
*/
static int asyncShmMap(
sqlite3_file *fd, /* Handle open on database file */
int iRegion, /* Region to retrieve */
int szRegion, /* Size of regions */
int bExtend, /* True to extend file if necessary */
void volatile **pp /* OUT: Mapped memory */
)
{
MutexHolder hold( SharedMemoryInfo::mutex );
VFSAsyncFile *pDbFd = (VFSAsyncFile*)fd;
SharedMemoryInfo* memInfo = pDbFd->sharedMemory;
if (!memInfo) {
std::string filename = pDbFd->filename;
memInfo = pDbFd->sharedMemory = &SharedMemoryInfo::table[ filename ];
memInfo->filename = filename;
memInfo->regionSize = szRegion;
++memInfo->refcount;
//printf("Shared memory for: '%s' (%d refs)\n", filename.c_str(), memInfo->refcount);
} else {
assert( memInfo->regionSize == szRegion );
}
if (iRegion >= memInfo->regions.size()) {
if (!bExtend) { *pp = NULL; return SQLITE_OK; }
while (memInfo->regions.size() <= iRegion) {
void *mem = new uint8_t[ szRegion ];
memset( mem, 0, szRegion );
memInfo->regions.push_back( mem );
}
}
*pp = memInfo->regions[ iRegion ];
return SQLITE_OK;
}
/*
** Change the lock state for a shared-memory segment.
**
** Note that the relationship between SHAREd and EXCLUSIVE locks is a little
** different here than in posix. In xShmLock(), one can go from unlocked
** to shared and back or from unlocked to exclusive and back. But one may
** not go from shared to exclusive or from exclusive to shared.
*/
// sqlite doesn't seem to match these up correctly - it happily calls unlock on locks it doesn't hold.
// So we have to keep track of which locks are held by a given sqlite3_file
static int asyncShmLock(
sqlite3_file *fd, /* Database file holding the shared memory */
int ofst, /* First lock to acquire or release */
int n, /* Number of locks to acquire or release */
int flags /* What to do with the lock */
){
assert( ofst>=0 && ofst+n<=SQLITE_SHM_NLOCK );
assert( n>=1 );
assert( flags==(SQLITE_SHM_LOCK | SQLITE_SHM_SHARED)
|| flags==(SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE)
|| flags==(SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED)
|| flags==(SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE) );
assert( n==1 || (flags & SQLITE_SHM_EXCLUSIVE)!=0 );
MutexHolder hold( SharedMemoryInfo::mutex );
VFSAsyncFile *pDbFd = (VFSAsyncFile*)fd;
SharedMemoryInfo* memInfo = pDbFd->sharedMemory;
if (flags & SQLITE_SHM_UNLOCK) {
for(int i=ofst; i<ofst+n; i++) {
if ( pDbFd->sharedMemorySharedLocks & (1<<i) ) {
pDbFd->sharedMemorySharedLocks &= ~(1<<i);
--memInfo->sharedLocks[i];
}
if ( pDbFd->sharedMemoryExclusiveLocks & (1<<i) ) {
pDbFd->sharedMemoryExclusiveLocks &= ~(1<<i);
--memInfo->exclusiveLocks[i];
}
}
} else if (flags & SQLITE_SHM_SHARED) {
for(int i=ofst; i<ofst+n; i++)
if ( memInfo->exclusiveLocks[i] != ((pDbFd->sharedMemoryExclusiveLocks>>i)&1) ) {
//TraceEvent("ShmLocked").detail("File", DEBUG_DETERMINISM ? 0 : (int64_t)pDbFd).detail("Acquiring", "Shared").detail("I", i).detail("Exclusive", memInfo->exclusiveLocks[i]).detail("MyExclusive", pDbFd->sharedMemoryExclusiveLocks);
return SQLITE_BUSY;
}
for(int i=ofst; i<ofst+n; i++)
if ( !(pDbFd->sharedMemorySharedLocks & (1<<i)) ) {
pDbFd->sharedMemorySharedLocks |= 1<<i;
memInfo->sharedLocks[i]++;
}
} else {
for(int i=ofst; i<ofst+n; i++)
if ( memInfo->exclusiveLocks[i] != ((pDbFd->sharedMemoryExclusiveLocks>>i)&1) ||
memInfo->sharedLocks[i] != ((pDbFd->sharedMemorySharedLocks>>i)&1) )
{
//TraceEvent("ShmLocked").detail("File", DEBUG_DETERMINISM ? 0 : (int64_t)pDbFd).detail("Acquiring", "Exclusive").detail("I", i).detail("Exclusive", memInfo->exclusiveLocks[i]).detail("MyExclusive", pDbFd->sharedMemoryExclusiveLocks).detail("Shared", memInfo->sharedLocks[i]).detail("MyShared", pDbFd->sharedMemorySharedLocks);
return SQLITE_BUSY;
}
for(int i=ofst; i<ofst+n; i++)
if (!( pDbFd->sharedMemoryExclusiveLocks & (1<<i) )) {
pDbFd->sharedMemoryExclusiveLocks |= 1<<i;
memInfo->exclusiveLocks[i]++;
}
}
return SQLITE_OK;
}
/*
** Implement a memory barrier or memory fence on shared memory.
**
** All loads and stores begun before the barrier must complete before
** any load or store begun after the barrier.
*/
static void asyncShmBarrier(sqlite3_file*){
#if WIN32
_ReadWriteBarrier();
#else
__sync_synchronize();
#endif
}
/*
** Close a connection to shared-memory. Delete the underlying
** storage if deleteFlag is true.
**
** If there is no shared memory associated with the connection then this
** routine is a harmless no-op.
*/
static int asyncShmUnmap(
sqlite3_file *fd, /* The underlying database file */
int deleteFlag /* Delete shared-memory if true */
){
MutexHolder hold( SharedMemoryInfo::mutex );
VFSAsyncFile *pDbFd = (VFSAsyncFile*)fd;
SharedMemoryInfo* memInfo = pDbFd->sharedMemory;
if (!memInfo) return SQLITE_OK;
pDbFd->sharedMemory = 0;
//printf("Connection %p closed shared memory\n", fd);
if (!--memInfo->refcount) {
//printf("Cleanup shared memory for: '%s' (%d refs; deleteFlag=%d)\n", memInfo->filename.c_str(), memInfo->refcount, deleteFlag);
//printf(" Shared locks: "); for(int i=0; i<8; i++) printf("%d ", memInfo->sharedLocks[i]); printf("\n");
//printf(" Exclusive locks: "); for(int i=0; i<8; i++) printf("%d ", memInfo->exclusiveLocks[i]); printf("\n");
//TraceEvent("CleanupSharedMemory").detail("Filename", memInfo->filename.c_str()).detail("RefCount", memInfo->refcount).detail("DeleteFlag", deleteFlag);
//for(int i = 0; i < 8; i++)
//TraceEvent("CleanupSharedMemory_Locks").detail("Filename", memInfo->filename.c_str()).detail("Num", i).detail("Shared", memInfo->sharedLocks[i]).detail("Exclusive", memInfo->exclusiveLocks[i]);
//We don't think deleteFlag will ever be set
ASSERT(!deleteFlag);
}
return SQLITE_OK;
}
VFSAsyncFile::~VFSAsyncFile() {
//TraceEvent("VFSAsyncFileDel").detail("Filename", filename);
if (!--filename_lockCount_openCount[filename].second) {
filename_lockCount_openCount.erase(filename);
//Always delete the shared memory when the last copy of the file is deleted. In simulation, this is helpful because "killing" a file without properly closing
//it can result in a shared memory state that causes corruption when reopening the killed file. The only expected penalty from doing this
//is a potentially slower open operation on a database, but that should happen infrequently.
//
//We can't do this in ShmUnmap when refcount is 0 because it seems that SQLite sometimes subsequently tries to reopen the WAL from multiple locations simultaneously,
//resulting in a locking error
auto itr = SharedMemoryInfo::table.find(filename);
if(itr != SharedMemoryInfo::table.end()) {
ASSERT_ABORT(itr->second.refcount == 0);
itr->second.cleanup();
}
}
}
#endif
/*
** Open a file handle.
*/
static int asyncOpen(
sqlite3_vfs *pVfs, /* VFS */
const char *zName, /* File to open, or 0 for a temp file */
sqlite3_file *pFile, /* Pointer to VFSAsyncFile struct to populate */
int flags, /* Input SQLITE_OPEN_XXX flags */
int *pOutFlags /* Output SQLITE_OPEN_XXX flags (or NULL) */
){
static const sqlite3_io_methods asyncio = {
3, /* iVersion */
asyncClose, /* xClose */
asyncRead, /* xRead */
asyncWrite, /* xWrite */
asyncTruncate, /* xTruncate */
asyncSync, /* xSync */
VFSAsyncFileSize, /* xFileSize */
asyncLock, /* xLock */
asyncUnlock, /* xUnlock */
asyncCheckReservedLock, /* xCheckReservedLock */
VFSAsyncFileControl, /* xFileControl */
asyncSectorSize, /* xSectorSize */
asyncDeviceCharacteristics, /* xDeviceCharacteristics */
asyncShmMap,
asyncShmLock,
asyncShmBarrier,
asyncShmUnmap,
asyncReadZeroCopy,
asyncReleaseZeroCopy
};
VFSAsyncFile *p = (VFSAsyncFile*)pFile; /* Populate this structure */
if( zName==0 )
return SQLITE_IOERR;
static_assert( SQLITE_OPEN_EXCLUSIVE == IAsyncFile::OPEN_EXCLUSIVE &&
SQLITE_OPEN_CREATE == IAsyncFile::OPEN_CREATE &&
SQLITE_OPEN_READONLY == IAsyncFile::OPEN_READONLY &&
SQLITE_OPEN_READWRITE == IAsyncFile::OPEN_READWRITE, "SQLite flag values don't match IAsyncFile flag values" );
// File creation here is disabled because we always create the files first in KeyValueStoreSQLite, using atomic creation
int oflags = flags & (/*SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_CREATE |*/ SQLITE_OPEN_READONLY | SQLITE_OPEN_READWRITE);
if (flags & SQLITE_OPEN_WAL) oflags |= IAsyncFile::OPEN_LARGE_PAGES;
oflags |= IAsyncFile::OPEN_LOCK;
memset(p, 0, sizeof(VFSAsyncFile));
new (p) VFSAsyncFile(zName, flags);
try {
// Note that SQLiteDB::open also opens the db file, so its flags and modes are important, too
p->file = waitForAndGet( IAsyncFileSystem::filesystem()->open( p->filename, oflags, 0600 ) );
/*TraceEvent("VFSOpened")
.detail("Filename", p->filename)
.detail("Fd", DEBUG_DETERMINISM ? 0 : p->file->debugFD())
.detail("Flags", flags)
.detail("Sqlite3File", DEBUG_DETERMINISM ? 0 : (int64_t)pFile)
.detail("IAsyncFile", DEBUG_DETERMINISM ? 0 : (int64_t)p->file.getPtr());*/
} catch (Error& e) {
TraceEvent("SQLiteOpenFail").error(e).detail("Filename", p->filename);
p->~VFSAsyncFile();
return SQLITE_CANTOPEN;
}
if( pOutFlags ){
*pOutFlags = flags;
}
p->base.pMethods = &asyncio;
return SQLITE_OK;
}
// The next few functions, which perform filesystem operations by path rather than by file, have
// OS-specific implementations.
/*
** Delete the file identified by argument zPath. If the dirSync parameter
** is non-zero, then ensure the file-system modification to delete the
** file has been synced to disk before returning.
*/
static int asyncDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
ASSERT( false ); // At the moment this isn't used; hence isn't under test. Could easily use IAsyncFileSystem::filesystem()->deleteFile().
return SQLITE_IOERR_DELETE;
}
/*
** Query the file-system to see if the named file exists, is readable or
** is both readable and writable. For an exists query, treat a zero-length file
** as if it does not exist.
*/
static int asyncAccess(
sqlite3_vfs *pVfs,
const char *zPath,
int flags,
int *pResOut
){
#ifdef __unixish__
#ifndef F_OK
# define F_OK 0
#endif
#ifndef R_OK
# define R_OK 4
#endif
#ifndef W_OK
# define W_OK 2
#endif
int rc; /* access() return code */
int eAccess = F_OK; /* Second argument to access() */
assert(flags==SQLITE_ACCESS_EXISTS /* access(zPath, F_OK) */
|| flags==SQLITE_ACCESS_READ /* access(zPath, R_OK) */
|| flags==SQLITE_ACCESS_READWRITE /* access(zPath, R_OK|W_OK) */
);
if( flags==SQLITE_ACCESS_READWRITE ) eAccess = R_OK|W_OK;
if( flags==SQLITE_ACCESS_READ ) eAccess = R_OK;
rc = access(zPath, eAccess);
*pResOut = (rc==0);
if( flags==SQLITE_ACCESS_EXISTS && *pResOut ){
struct stat buf;
if( 0==stat(zPath, &buf) && buf.st_size==0 ){
*pResOut = 0;
}
}
return SQLITE_OK;
#else
WIN32_FILE_ATTRIBUTE_DATA data;
DWORD attr = INVALID_FILE_ATTRIBUTES;
memset(&data, 0, sizeof(data));
if (GetFileAttributesEx(zPath, GetFileExInfoStandard, &data)) {
if (!(flags == SQLITE_ACCESS_EXISTS && data.nFileSizeHigh==0 && data.nFileSizeLow==0))
attr = data.dwFileAttributes;
} else if (GetLastError()!=ERROR_FILE_NOT_FOUND)
return SQLITE_IOERR_ACCESS;
if (flags == SQLITE_ACCESS_READWRITE)
*pResOut = (attr & FILE_ATTRIBUTE_READONLY)==0;
else
*pResOut = attr != INVALID_FILE_ATTRIBUTES;
return SQLITE_OK;
#endif
}
/*
** Argument zPath points to a nul-terminated string containing a file path.
** If zPath is an absolute path, then it is copied as is into the output
** buffer. Otherwise, if it is a relative path, then the equivalent full
** path is written to the output buffer.
*/
static int asyncFullPathname(
sqlite3_vfs *pVfs, /* VFS */
const char *zPath, /* Input path (possibly a relative path) */
int nPathOut, /* Size of output buffer in bytes */
char *zPathOut /* Pointer to output buffer */
){
try {
auto s = abspath( zPath );
if (s.size() >= nPathOut)
return SQLITE_IOERR;
memcpy(zPathOut, s.c_str(), s.size()+1);
return SQLITE_OK;
} catch (Error& e) {
TraceEvent(SevError,"VFSAsyncFullPathnameError").error(e).detail("PathIn", (std::string)zPath);
return SQLITE_IOERR;
} catch(...) {
TraceEvent(SevError,"VFSAsyncFullPathnameError").error(unknown_error()).detail("PathIn", (std::string)zPath);
return SQLITE_IOERR;
}
}
/*
** Returns true if there is a shared memory entry for the specified filename,
** and false otherwise.
*/
bool vfsAsyncIsOpen( std::string filename ) {
return SharedMemoryInfo::table.count( abspath(filename) ) > 0;
}
/*
** The following four VFS methods:
**
** xDlOpen
** xDlError
** xDlSym
** xDlClose
**
** are supposed to implement the functionality needed by SQLite to load
** extensions compiled as shared objects. This simple VFS does not support
** this functionality, so the following functions are no-ops.
*/
static void *asyncDlOpen(sqlite3_vfs *pVfs, const char *zPath){
return 0;
}
static void asyncDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
sqlite3_snprintf(nByte, zErrMsg, "Loadable extensions are not supported");
zErrMsg[nByte-1] = '\0';
}
static void (*asyncDlSym(sqlite3_vfs *pVfs, void *pH, const char *z))(void){
return 0;
}
static void asyncDlClose(sqlite3_vfs *pVfs, void *pHandle){
return;
}
/*
** Parameter zByte points to a buffer nByte bytes in size. Populate this
** buffer with pseudo-random data.
*/
static int asyncRandomness(sqlite3_vfs *pVfs, int nByte, char *zByte){
for(int i=0; i<nByte; i++)
zByte[i] = deterministicRandom()->randomInt(0,256);
return SQLITE_OK;
}
/*
** Sleep for at least nMicro microseconds. Return the (approximate) number
** of microseconds slept for.
*/
static int asyncSleep(sqlite3_vfs *pVfs, int microseconds){
try {
Future<Void> simCancel = Never();
if( g_network->isSimulated() )
simCancel = success( g_simulator.getCurrentProcess()->shutdownSignal.getFuture() );
if( simCancel.isReady() ) {
waitFor( delay(FLOW_KNOBS->MAX_BUGGIFIED_DELAY) );
return 0;
}
waitFor( g_network->delay( microseconds*1e-6, TaskPriority::DefaultDelay ) || simCancel );
return microseconds;
} catch( Error &e ) {
TraceEvent(SevError, "AsyncSleepError").error(e,true);
return 0;
}
}
/*
** Find the current time (in Universal Coordinated Time). Write into *piNow
** the current time and date as a Julian Day number times 86_400_000. In
** other words, write into *piNow the number of milliseconds since the Julian
** epoch of noon in Greenwich on November 24, 4714 B.C according to the
** proleptic Gregorian calendar.
**
** On success, return 0. Return 1 if the time and date cannot be found.
*/
static int asyncCurrentTimeInt64(sqlite3_vfs *NotUsed, sqlite3_int64 *piNow){
#if __unixish__
static const sqlite3_int64 unixEpoch = 24405875*(sqlite3_int64)8640000;
struct timeval sNow;
gettimeofday(&sNow, NULL);
*piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000;
#elif defined(_WIN32)
static const sqlite3_int64 winFiletimeEpoch = 23058135*(sqlite3_int64)8640000;
int64_t ft = 0;
GetSystemTimeAsFileTime( (FILETIME*)&ft );
*piNow = winFiletimeEpoch + ft / 10000;
#else
#error Port me!
#endif
return 0;
}
/*
** Set *pTime to the current UTC time expressed as a Julian day. Return
** SQLITE_OK if successful, or an error code otherwise.
**
** http://en.wikipedia.org/wiki/Julian_day
*/
static int asyncCurrentTime(sqlite3_vfs *pVfs, double *pTime){
sqlite3_int64 t = 0;
int rc = asyncCurrentTimeInt64(pVfs, &t);
if (rc) return rc;
*pTime = t / 86400000.0;
return SQLITE_OK;
}
static int asyncGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ return 0; }
/*
** This function returns a pointer to the VFS implemented in this file.
** To make the VFS available to SQLite:
**
** sqlite3_vfs_register(sqlite3_asyncvfs(), 0);
*/
sqlite3_vfs *vfsAsync(){
static sqlite3_vfs asyncvfs = {
3, /* iVersion */
sizeof(VFSAsyncFile), /* szOsFile */
MAXPATHNAME, /* mxPathname */
0, /* pNext */
"fdb_async", /* zName */
0, /* pAppData */
asyncOpen, /* xOpen */
asyncDelete, /* xDelete */
asyncAccess, /* xAccess */
asyncFullPathname, /* xFullPathname */
asyncDlOpen, /* xDlOpen */
asyncDlError, /* xDlError */
asyncDlSym, /* xDlSym */
asyncDlClose, /* xDlClose */
asyncRandomness, /* xRandomness */
asyncSleep, /* xSleep */
asyncCurrentTime, /* xCurrentTime */
asyncGetLastError, /* xGetLastError */
asyncCurrentTimeInt64, /* xCurrentTimeInt64 */
0, /* xSetSystemCall */
0, /* xGetSystemCall */
0, /* xNextSystemCall */
};
return &asyncvfs;
}