/* ** 2022-09-06 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** ** ** This file contains an experimental VFS layer that operates on a ** Key/Value storage engine where both keys and values must be pure ** text. */ #include #if SQLITE_OS_KV || (SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL)) /***************************************************************************** ** Debugging logic */ /* SQLITE_KV_TRACE() is used for tracing calls to kvstorage routines. */ #if 0 #define SQLITE_KV_TRACE(X) printf X #else #define SQLITE_KV_TRACE(X) #endif /* SQLITE_KV_LOG() is used for tracing calls to the VFS interface */ #if 0 #define SQLITE_KV_LOG(X) printf X #else #define SQLITE_KV_LOG(X) #endif /* ** Forward declaration of objects used by this VFS implementation */ typedef struct KVVfsFile KVVfsFile; /* A single open file. There are only two files represented by this ** VFS - the database and the rollback journal. */ struct KVVfsFile { sqlite3_file base; /* IO methods */ const char *zClass; /* Storage class */ int isJournal; /* True if this is a journal file */ unsigned int nJrnl; /* Space allocated for aJrnl[] */ char *aJrnl; /* Journal content */ int szPage; /* Last known page size */ sqlite3_int64 szDb; /* Database file size. -1 means unknown */ }; /* ** Methods for KVVfsFile */ static int kvvfsClose(sqlite3_file*); static int kvvfsReadDb(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); static int kvvfsReadJrnl(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); static int kvvfsWriteDb(sqlite3_file*,const void*,int iAmt, sqlite3_int64); static int kvvfsWriteJrnl(sqlite3_file*,const void*,int iAmt, sqlite3_int64); static int kvvfsTruncateDb(sqlite3_file*, sqlite3_int64 size); static int kvvfsTruncateJrnl(sqlite3_file*, sqlite3_int64 size); static int kvvfsSyncDb(sqlite3_file*, int flags); static int kvvfsSyncJrnl(sqlite3_file*, int flags); static int kvvfsFileSizeDb(sqlite3_file*, sqlite3_int64 *pSize); static int kvvfsFileSizeJrnl(sqlite3_file*, sqlite3_int64 *pSize); static int kvvfsLock(sqlite3_file*, int); static int kvvfsUnlock(sqlite3_file*, int); static int kvvfsCheckReservedLock(sqlite3_file*, int *pResOut); static int kvvfsFileControlDb(sqlite3_file*, int op, void *pArg); static int kvvfsFileControlJrnl(sqlite3_file*, int op, void *pArg); static int kvvfsSectorSize(sqlite3_file*); static int kvvfsDeviceCharacteristics(sqlite3_file*); /* ** Methods for sqlite3_vfs */ static int kvvfsOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *); static int kvvfsDelete(sqlite3_vfs*, const char *zName, int syncDir); static int kvvfsAccess(sqlite3_vfs*, const char *zName, int flags, int *); static int kvvfsFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut); static void *kvvfsDlOpen(sqlite3_vfs*, const char *zFilename); static int kvvfsRandomness(sqlite3_vfs*, int nByte, char *zOut); static int kvvfsSleep(sqlite3_vfs*, int microseconds); static int kvvfsCurrentTime(sqlite3_vfs*, double*); static int kvvfsCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); static sqlite3_vfs sqlite3OsKvvfsObject = { 1, /* iVersion */ sizeof(KVVfsFile), /* szOsFile */ 1024, /* mxPathname */ 0, /* pNext */ "kvvfs", /* zName */ 0, /* pAppData */ kvvfsOpen, /* xOpen */ kvvfsDelete, /* xDelete */ kvvfsAccess, /* xAccess */ kvvfsFullPathname, /* xFullPathname */ kvvfsDlOpen, /* xDlOpen */ 0, /* xDlError */ 0, /* xDlSym */ 0, /* xDlClose */ kvvfsRandomness, /* xRandomness */ kvvfsSleep, /* xSleep */ kvvfsCurrentTime, /* xCurrentTime */ 0, /* xGetLastError */ kvvfsCurrentTimeInt64 /* xCurrentTimeInt64 */ }; /* Methods for sqlite3_file objects referencing a database file */ static sqlite3_io_methods kvvfs_db_io_methods = { 1, /* iVersion */ kvvfsClose, /* xClose */ kvvfsReadDb, /* xRead */ kvvfsWriteDb, /* xWrite */ kvvfsTruncateDb, /* xTruncate */ kvvfsSyncDb, /* xSync */ kvvfsFileSizeDb, /* xFileSize */ kvvfsLock, /* xLock */ kvvfsUnlock, /* xUnlock */ kvvfsCheckReservedLock, /* xCheckReservedLock */ kvvfsFileControlDb, /* xFileControl */ kvvfsSectorSize, /* xSectorSize */ kvvfsDeviceCharacteristics, /* xDeviceCharacteristics */ 0, /* xShmMap */ 0, /* xShmLock */ 0, /* xShmBarrier */ 0, /* xShmUnmap */ 0, /* xFetch */ 0 /* xUnfetch */ }; /* Methods for sqlite3_file objects referencing a rollback journal */ static sqlite3_io_methods kvvfs_jrnl_io_methods = { 1, /* iVersion */ kvvfsClose, /* xClose */ kvvfsReadJrnl, /* xRead */ kvvfsWriteJrnl, /* xWrite */ kvvfsTruncateJrnl, /* xTruncate */ kvvfsSyncJrnl, /* xSync */ kvvfsFileSizeJrnl, /* xFileSize */ kvvfsLock, /* xLock */ kvvfsUnlock, /* xUnlock */ kvvfsCheckReservedLock, /* xCheckReservedLock */ kvvfsFileControlJrnl, /* xFileControl */ kvvfsSectorSize, /* xSectorSize */ kvvfsDeviceCharacteristics, /* xDeviceCharacteristics */ 0, /* xShmMap */ 0, /* xShmLock */ 0, /* xShmBarrier */ 0, /* xShmUnmap */ 0, /* xFetch */ 0 /* xUnfetch */ }; /****** Storage subsystem **************************************************/ #include #include #include /* Forward declarations for the low-level storage engine */ static int kvstorageWrite(const char*, const char *zKey, const char *zData); static int kvstorageDelete(const char*, const char *zKey); static int kvstorageRead(const char*, const char *zKey, char *zBuf, int nBuf); #define KVSTORAGE_KEY_SZ 32 /* Expand the key name with an appropriate prefix and put the result ** zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least ** KVSTORAGE_KEY_SZ bytes. */ static void kvstorageMakeKey( const char *zClass, const char *zKeyIn, char *zKeyOut ){ sqlite3_snprintf(KVSTORAGE_KEY_SZ, zKeyOut, "kvvfs-%s-%s", zClass, zKeyIn); } /* Write content into a key. zClass is the particular namespace of the ** underlying key/value store to use - either "local" or "session". ** ** Both zKey and zData are zero-terminated pure text strings. ** ** Return the number of errors. */ static int kvstorageWrite( const char *zClass, const char *zKey, const char *zData ){ FILE *fd; char zXKey[KVSTORAGE_KEY_SZ]; kvstorageMakeKey(zClass, zKey, zXKey); fd = fopen(zXKey, "wb"); if( fd ){ SQLITE_KV_TRACE(("KVVFS-WRITE %-15s (%d) %.50s%s\n", zXKey, (int)strlen(zData), zData, strlen(zData)>50 ? "..." : "")); fputs(zData, fd); fclose(fd); return 0; }else{ return 1; } } /* Delete a key (with its corresponding data) from the key/value ** namespace given by zClass. If the key does not previously exist, ** this routine is a no-op. */ static int kvstorageDelete(const char *zClass, const char *zKey){ char zXKey[KVSTORAGE_KEY_SZ]; kvstorageMakeKey(zClass, zKey, zXKey); unlink(zXKey); SQLITE_KV_TRACE(("KVVFS-DELETE %-15s\n", zXKey)); return 0; } /* Read the value associated with a zKey from the key/value namespace given ** by zClass and put the text data associated with that key in the first ** nBuf bytes of zBuf[]. The value might be truncated if zBuf is not large ** enough to hold it all. The value put into zBuf must always be zero ** terminated, even if it gets truncated because nBuf is not large enough. ** ** Return the total number of bytes in the data, without truncation, and ** not counting the final zero terminator. Return -1 if the key does ** not exist. ** ** If nBuf<=0 then this routine simply returns the size of the data without ** actually reading it. */ static int kvstorageRead( const char *zClass, const char *zKey, char *zBuf, int nBuf ){ FILE *fd; struct stat buf; char zXKey[KVSTORAGE_KEY_SZ]; kvstorageMakeKey(zClass, zKey, zXKey); if( access(zXKey, R_OK)!=0 || stat(zXKey, &buf)!=0 || !S_ISREG(buf.st_mode) ){ SQLITE_KV_TRACE(("KVVFS-READ %-15s (-1)\n", zXKey)); return -1; } if( nBuf<=0 ){ return (int)buf.st_size; }else if( nBuf==1 ){ zBuf[0] = 0; SQLITE_KV_TRACE(("KVVFS-READ %-15s (%d)\n", zXKey, (int)buf.st_size)); return (int)buf.st_size; } if( nBuf > buf.st_size + 1 ){ nBuf = buf.st_size + 1; } fd = fopen(zXKey, "rb"); if( fd==0 ){ SQLITE_KV_TRACE(("KVVFS-READ %-15s (-1)\n", zXKey)); return -1; }else{ sqlite3_int64 n = fread(zBuf, 1, nBuf-1, fd); fclose(fd); zBuf[n] = 0; SQLITE_KV_TRACE(("KVVFS-READ %-15s (%lld) %.50s%s\n", zXKey, n, zBuf, n>50 ? "..." : "")); return (int)n; } } /* ** An internal level of indirection which enables us to replace the ** kvvfs i/o methods with JavaScript implementations in WASM builds. ** Maintenance reminder: if this struct changes in any way, the JSON ** rendering of its structure must be updated in ** sqlite3_wasm_enum_json(). There are no binary compatibility ** concerns, so it does not need an iVersion member. This file is ** necessarily always compiled together with sqlite3_wasm_enum_json(), ** and JS code dynamically creates the mapping of members based on ** that JSON description. */ typedef struct sqlite3_kvvfs_methods sqlite3_kvvfs_methods; struct sqlite3_kvvfs_methods { int (*xRead)(const char *zClass, const char *zKey, char *zBuf, int nBuf); int (*xWrite)(const char *zClass, const char *zKey, const char *zData); int (*xDelete)(const char *zClass, const char *zKey); const int nKeySize; }; /* ** This object holds the kvvfs I/O methods which may be swapped out ** for JavaScript-side implementations in WASM builds. In such builds ** it cannot be const, but in native builds it should be so that ** the compiler can hopefully optimize this level of indirection out. ** That said, kvvfs is intended primarily for use in WASM builds. ** ** Note that this is not explicitly flagged as static because the ** amalgamation build will tag it with SQLITE_PRIVATE. */ #ifndef SQLITE_WASM const #endif sqlite3_kvvfs_methods sqlite3KvvfsMethods = { kvstorageRead, kvstorageWrite, kvstorageDelete, KVSTORAGE_KEY_SZ }; /****** Utility subroutines ************************************************/ /* ** Encode binary into the text encoded used to persist on disk. ** The output text is stored in aOut[], which must be at least ** nData+1 bytes in length. ** ** Return the actual length of the encoded text, not counting the ** zero terminator at the end. ** ** Encoding format ** --------------- ** ** * Non-zero bytes are encoded as upper-case hexadecimal ** ** * A sequence of one or more zero-bytes that are not at the ** beginning of the buffer are encoded as a little-endian ** base-26 number using a..z. "a" means 0. "b" means 1, ** "z" means 25. "ab" means 26. "ac" means 52. And so forth. ** ** * Because there is no overlap between the encoding characters ** of hexadecimal and base-26 numbers, it is always clear where ** one stops and the next begins. */ static int kvvfsEncode(const char *aData, int nData, char *aOut){ int i, j; const unsigned char *a = (const unsigned char*)aData; for(i=j=0; i>4]; aOut[j++] = "0123456789ABCDEF"[c&0xf]; }else{ /* A sequence of 1 or more zeros is stored as a little-endian ** base-26 number using a..z as the digits. So one zero is "b". ** Two zeros is "c". 25 zeros is "z", 26 zeros is "ab", 27 is "bb", ** and so forth. */ int k; for(k=1; i+k0 ){ aOut[j++] = 'a'+(k%26); k /= 26; } } } aOut[j] = 0; return j; } static const signed char kvvfsHexValue[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; /* ** Decode the text encoding back to binary. The binary content is ** written into pOut, which must be at least nOut bytes in length. ** ** The return value is the number of bytes actually written into aOut[]. */ static int kvvfsDecode(const char *a, char *aOut, int nOut){ int i, j; int c; const unsigned char *aIn = (const unsigned char*)a; i = 0; j = 0; while( 1 ){ c = kvvfsHexValue[aIn[i]]; if( c<0 ){ int n = 0; int mult = 1; c = aIn[i]; if( c==0 ) break; while( c>='a' && c<='z' ){ n += (c - 'a')*mult; mult *= 26; c = aIn[++i]; } if( j+n>nOut ) return -1; memset(&aOut[j], 0, n); j += n; c = aIn[i]; if( c==0 ) break; }else{ aOut[j] = c<<4; c = kvvfsHexValue[aIn[++i]]; if( c<0 ) break; aOut[j++] += c; i++; } } return j; } /* ** Decode a complete journal file. Allocate space in pFile->aJrnl ** and store the decoding there. Or leave pFile->aJrnl set to NULL ** if an error is encountered. ** ** The first few characters of the text encoding will be a little-endian ** base-26 number (digits a..z) that is the total number of bytes ** in the decoded journal file image. This base-26 number is followed ** by a single space, then the encoding of the journal. The space ** separator is required to act as a terminator for the base-26 number. */ static void kvvfsDecodeJournal( KVVfsFile *pFile, /* Store decoding in pFile->aJrnl */ const char *zTxt, /* Text encoding. Zero-terminated */ int nTxt /* Bytes in zTxt, excluding zero terminator */ ){ unsigned int n = 0; int c, i, mult; i = 0; mult = 1; while( (c = zTxt[i++])>='a' && c<='z' ){ n += (zTxt[i] - 'a')*mult; mult *= 26; } sqlite3_free(pFile->aJrnl); pFile->aJrnl = sqlite3_malloc64( n ); if( pFile->aJrnl==0 ){ pFile->nJrnl = 0; return; } pFile->nJrnl = n; n = kvvfsDecode(zTxt+i, pFile->aJrnl, pFile->nJrnl); if( nnJrnl ){ sqlite3_free(pFile->aJrnl); pFile->aJrnl = 0; pFile->nJrnl = 0; } } /* ** Read or write the "sz" element, containing the database file size. */ static sqlite3_int64 kvvfsReadFileSize(KVVfsFile *pFile){ char zData[50]; zData[0] = 0; sqlite3KvvfsMethods.xRead(pFile->zClass, "sz", zData, sizeof(zData)-1); return strtoll(zData, 0, 0); } static int kvvfsWriteFileSize(KVVfsFile *pFile, sqlite3_int64 sz){ char zData[50]; sqlite3_snprintf(sizeof(zData), zData, "%lld", sz); return sqlite3KvvfsMethods.xWrite(pFile->zClass, "sz", zData); } /****** sqlite3_io_methods methods ******************************************/ /* ** Close an kvvfs-file. */ static int kvvfsClose(sqlite3_file *pProtoFile){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; SQLITE_KV_LOG(("xClose %s %s\n", pFile->zClass, pFile->isJournal ? "journal" : "db")); sqlite3_free(pFile->aJrnl); return SQLITE_OK; } /* ** Read from the -journal file. */ static int kvvfsReadJrnl( sqlite3_file *pProtoFile, void *zBuf, int iAmt, sqlite_int64 iOfst ){ KVVfsFile *pFile = (KVVfsFile*)pProtoFile; assert( pFile->isJournal ); SQLITE_KV_LOG(("xRead('%s-journal',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); if( pFile->aJrnl==0 ){ int szTxt = kvstorageRead(pFile->zClass, "jrnl", 0, 0); char *aTxt; if( szTxt<=4 ){ return SQLITE_IOERR; } aTxt = sqlite3_malloc64( szTxt+1 ); if( aTxt==0 ) return SQLITE_NOMEM; kvstorageRead(pFile->zClass, "jrnl", aTxt, szTxt+1); kvvfsDecodeJournal(pFile, aTxt, szTxt); sqlite3_free(aTxt); if( pFile->aJrnl==0 ) return SQLITE_IOERR; } if( iOfst+iAmt>pFile->nJrnl ){ return SQLITE_IOERR_SHORT_READ; } memcpy(zBuf, pFile->aJrnl+iOfst, iAmt); return SQLITE_OK; } /* ** Read from the database file. */ static int kvvfsReadDb( sqlite3_file *pProtoFile, void *zBuf, int iAmt, sqlite_int64 iOfst ){ KVVfsFile *pFile = (KVVfsFile*)pProtoFile; unsigned int pgno; int got, n; char zKey[30]; char aData[133073]; assert( iOfst>=0 ); assert( iAmt>=0 ); SQLITE_KV_LOG(("xRead('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); if( iOfst+iAmt>=512 ){ if( (iOfst % iAmt)!=0 ){ return SQLITE_IOERR_READ; } if( (iAmt & (iAmt-1))!=0 || iAmt<512 || iAmt>65536 ){ return SQLITE_IOERR_READ; } pFile->szPage = iAmt; pgno = 1 + iOfst/iAmt; }else{ pgno = 1; } sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); got = sqlite3KvvfsMethods.xRead(pFile->zClass, zKey, aData, sizeof(aData)-1); if( got<0 ){ n = 0; }else{ aData[got] = 0; if( iOfst+iAmt<512 ){ int k = iOfst+iAmt; aData[k*2] = 0; n = kvvfsDecode(aData, &aData[2000], sizeof(aData)-2000); if( n>=iOfst+iAmt ){ memcpy(zBuf, &aData[2000+iOfst], iAmt); n = iAmt; }else{ n = 0; } }else{ n = kvvfsDecode(aData, zBuf, iAmt); } } if( nzClass, iAmt, iOfst)); if( iEnd>=0x10000000 ) return SQLITE_FULL; if( pFile->aJrnl==0 || pFile->nJrnlaJrnl, iEnd); if( aNew==0 ){ return SQLITE_IOERR_NOMEM; } pFile->aJrnl = aNew; if( pFile->nJrnlaJrnl+pFile->nJrnl, 0, iOfst-pFile->nJrnl); } pFile->nJrnl = iEnd; } memcpy(pFile->aJrnl+iOfst, zBuf, iAmt); return SQLITE_OK; } /* ** Write into the database file. */ static int kvvfsWriteDb( sqlite3_file *pProtoFile, const void *zBuf, int iAmt, sqlite_int64 iOfst ){ KVVfsFile *pFile = (KVVfsFile*)pProtoFile; unsigned int pgno; char zKey[30]; char aData[131073]; SQLITE_KV_LOG(("xWrite('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); assert( iAmt>=512 && iAmt<=65536 ); assert( (iAmt & (iAmt-1))==0 ); assert( pFile->szPage<0 || pFile->szPage==iAmt ); pFile->szPage = iAmt; pgno = 1 + iOfst/iAmt; sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); kvvfsEncode(zBuf, iAmt, aData); if( sqlite3KvvfsMethods.xWrite(pFile->zClass, zKey, aData) ){ return SQLITE_IOERR; } if( iOfst+iAmt > pFile->szDb ){ pFile->szDb = iOfst + iAmt; } return SQLITE_OK; } /* ** Truncate an kvvfs-file. */ static int kvvfsTruncateJrnl(sqlite3_file *pProtoFile, sqlite_int64 size){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; SQLITE_KV_LOG(("xTruncate('%s-journal',%lld)\n", pFile->zClass, size)); assert( size==0 ); sqlite3KvvfsMethods.xDelete(pFile->zClass, "jrnl"); sqlite3_free(pFile->aJrnl); pFile->aJrnl = 0; pFile->nJrnl = 0; return SQLITE_OK; } static int kvvfsTruncateDb(sqlite3_file *pProtoFile, sqlite_int64 size){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; if( pFile->szDb>size && pFile->szPage>0 && (size % pFile->szPage)==0 ){ char zKey[50]; unsigned int pgno, pgnoMax; SQLITE_KV_LOG(("xTruncate('%s-db',%lld)\n", pFile->zClass, size)); pgno = 1 + size/pFile->szPage; pgnoMax = 2 + pFile->szDb/pFile->szPage; while( pgno<=pgnoMax ){ sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); sqlite3KvvfsMethods.xDelete(pFile->zClass, zKey); pgno++; } pFile->szDb = size; return kvvfsWriteFileSize(pFile, size) ? SQLITE_IOERR : SQLITE_OK; } return SQLITE_IOERR; } /* ** Sync an kvvfs-file. */ static int kvvfsSyncJrnl(sqlite3_file *pProtoFile, int flags){ int i, n; KVVfsFile *pFile = (KVVfsFile *)pProtoFile; char *zOut; SQLITE_KV_LOG(("xSync('%s-journal')\n", pFile->zClass)); if( pFile->nJrnl<=0 ){ return kvvfsTruncateJrnl(pProtoFile, 0); } zOut = sqlite3_malloc64( pFile->nJrnl*2 + 50 ); if( zOut==0 ){ return SQLITE_IOERR_NOMEM; } n = pFile->nJrnl; i = 0; do{ zOut[i++] = 'a' + (n%26); n /= 26; }while( n>0 ); zOut[i++] = ' '; kvvfsEncode(pFile->aJrnl, pFile->nJrnl, &zOut[i]); i = sqlite3KvvfsMethods.xWrite(pFile->zClass, "jrnl", zOut); sqlite3_free(zOut); return i ? SQLITE_IOERR : SQLITE_OK; } static int kvvfsSyncDb(sqlite3_file *pProtoFile, int flags){ return SQLITE_OK; } /* ** Return the current file-size of an kvvfs-file. */ static int kvvfsFileSizeJrnl(sqlite3_file *pProtoFile, sqlite_int64 *pSize){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; SQLITE_KV_LOG(("xFileSize('%s-journal')\n", pFile->zClass)); *pSize = pFile->nJrnl; return SQLITE_OK; } static int kvvfsFileSizeDb(sqlite3_file *pProtoFile, sqlite_int64 *pSize){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; SQLITE_KV_LOG(("xFileSize('%s-db')\n", pFile->zClass)); if( pFile->szDb>=0 ){ *pSize = pFile->szDb; }else{ *pSize = kvvfsReadFileSize(pFile); } return SQLITE_OK; } /* ** Lock an kvvfs-file. */ static int kvvfsLock(sqlite3_file *pProtoFile, int eLock){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; assert( !pFile->isJournal ); SQLITE_KV_LOG(("xLock(%s,%d)\n", pFile->zClass, eLock)); if( eLock!=SQLITE_LOCK_NONE ){ pFile->szDb = kvvfsReadFileSize(pFile); } return SQLITE_OK; } /* ** Unlock an kvvfs-file. */ static int kvvfsUnlock(sqlite3_file *pProtoFile, int eLock){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; assert( !pFile->isJournal ); SQLITE_KV_LOG(("xUnlock(%s,%d)\n", pFile->zClass, eLock)); if( eLock==SQLITE_LOCK_NONE ){ pFile->szDb = -1; } return SQLITE_OK; } /* ** Check if another file-handle holds a RESERVED lock on an kvvfs-file. */ static int kvvfsCheckReservedLock(sqlite3_file *pProtoFile, int *pResOut){ SQLITE_KV_LOG(("xCheckReservedLock\n")); *pResOut = 0; return SQLITE_OK; } /* ** File control method. For custom operations on an kvvfs-file. */ static int kvvfsFileControlJrnl(sqlite3_file *pProtoFile, int op, void *pArg){ SQLITE_KV_LOG(("xFileControl(%d) on journal\n", op)); return SQLITE_NOTFOUND; } static int kvvfsFileControlDb(sqlite3_file *pProtoFile, int op, void *pArg){ SQLITE_KV_LOG(("xFileControl(%d) on database\n", op)); if( op==SQLITE_FCNTL_SYNC ){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; int rc = SQLITE_OK; SQLITE_KV_LOG(("xSync('%s-db')\n", pFile->zClass)); if( pFile->szDb>0 && 0!=kvvfsWriteFileSize(pFile, pFile->szDb) ){ rc = SQLITE_IOERR; } return rc; } return SQLITE_NOTFOUND; } /* ** Return the sector-size in bytes for an kvvfs-file. */ static int kvvfsSectorSize(sqlite3_file *pFile){ return 512; } /* ** Return the device characteristic flags supported by an kvvfs-file. */ static int kvvfsDeviceCharacteristics(sqlite3_file *pProtoFile){ return 0; } /****** sqlite3_vfs methods *************************************************/ /* ** Open an kvvfs file handle. */ static int kvvfsOpen( sqlite3_vfs *pProtoVfs, const char *zName, sqlite3_file *pProtoFile, int flags, int *pOutFlags ){ KVVfsFile *pFile = (KVVfsFile*)pProtoFile; if( zName==0 ) zName = ""; SQLITE_KV_LOG(("xOpen(\"%s\")\n", zName)); if( strcmp(zName, "local")==0 || strcmp(zName, "session")==0 ){ pFile->isJournal = 0; pFile->base.pMethods = &kvvfs_db_io_methods; }else if( strcmp(zName, "local-journal")==0 || strcmp(zName, "session-journal")==0 ){ pFile->isJournal = 1; pFile->base.pMethods = &kvvfs_jrnl_io_methods; }else{ return SQLITE_CANTOPEN; } if( zName[0]=='s' ){ pFile->zClass = "session"; }else{ pFile->zClass = "local"; } pFile->aJrnl = 0; pFile->nJrnl = 0; pFile->szPage = -1; pFile->szDb = -1; return SQLITE_OK; } /* ** Delete the file located at zPath. If the dirSync argument is true, ** ensure the file-system modifications are synced to disk before ** returning. */ static int kvvfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ if( strcmp(zPath, "local-journal")==0 ){ sqlite3KvvfsMethods.xDelete("local", "jrnl"); }else if( strcmp(zPath, "session-journal")==0 ){ sqlite3KvvfsMethods.xDelete("session", "jrnl"); } return SQLITE_OK; } /* ** Test for access permissions. Return true if the requested permission ** is available, or false otherwise. */ static int kvvfsAccess( sqlite3_vfs *pProtoVfs, const char *zPath, int flags, int *pResOut ){ SQLITE_KV_LOG(("xAccess(\"%s\")\n", zPath)); if( strcmp(zPath, "local-journal")==0 ){ *pResOut = sqlite3KvvfsMethods.xRead("local", "jrnl", 0, 0)>0; }else if( strcmp(zPath, "session-journal")==0 ){ *pResOut = sqlite3KvvfsMethods.xRead("session", "jrnl", 0, 0)>0; }else if( strcmp(zPath, "local")==0 ){ *pResOut = sqlite3KvvfsMethods.xRead("local", "sz", 0, 0)>0; }else if( strcmp(zPath, "session")==0 ){ *pResOut = sqlite3KvvfsMethods.xRead("session", "sz", 0, 0)>0; }else { *pResOut = 0; } SQLITE_KV_LOG(("xAccess returns %d\n",*pResOut)); return SQLITE_OK; } /* ** Populate buffer zOut with the full canonical pathname corresponding ** to the pathname in zPath. zOut is guaranteed to point to a buffer ** of at least (INST_MAX_PATHNAME+1) bytes. */ static int kvvfsFullPathname( sqlite3_vfs *pVfs, const char *zPath, int nOut, char *zOut ){ size_t nPath; #ifdef SQLITE_OS_KV_ALWAYS_LOCAL zPath = "local"; #endif nPath = strlen(zPath); SQLITE_KV_LOG(("xFullPathname(\"%s\")\n", zPath)); if( nOut static int kvvfsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){ static const sqlite3_int64 unixEpoch = 24405875*(sqlite3_int64)8640000; struct timeval sNow; (void)gettimeofday(&sNow, 0); /* Cannot fail given valid arguments */ *pTimeOut = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000; return SQLITE_OK; } #endif /* SQLITE_OS_KV || SQLITE_OS_UNIX */ #if SQLITE_OS_KV /* ** This routine is called initialize the KV-vfs as the default VFS. */ int sqlite3_os_init(void){ return sqlite3_vfs_register(&sqlite3OsKvvfsObject, 1); } int sqlite3_os_end(void){ return SQLITE_OK; } #endif /* SQLITE_OS_KV */ #if SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL) int sqlite3KvvfsInit(void){ return sqlite3_vfs_register(&sqlite3OsKvvfsObject, 0); } #endif