mirror of
https://github.com/tursodatabase/libsql.git
synced 2024-11-27 06:58:59 +00:00
673 lines
22 KiB
C
673 lines
22 KiB
C
/*
|
|
** 2017-10-20
|
|
**
|
|
** 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 implements a VFS shim that allows an SQLite database to be
|
|
** appended onto the end of some other file, such as an executable.
|
|
**
|
|
** A special record must appear at the end of the file that identifies the
|
|
** file as an appended database and provides the offset to the first page
|
|
** of the exposed content. (Or, it is the length of the content prefix.)
|
|
** For best performance page 1 should be located at a disk page boundary,
|
|
** though that is not required.
|
|
**
|
|
** When opening a database using this VFS, the connection might treat
|
|
** the file as an ordinary SQLite database, or it might treat it as a
|
|
** database appended onto some other file. The decision is made by
|
|
** applying the following rules in order:
|
|
**
|
|
** (1) An empty file is an ordinary database.
|
|
**
|
|
** (2) If the file ends with the appendvfs trailer string
|
|
** "Start-Of-SQLite3-NNNNNNNN" that file is an appended database.
|
|
**
|
|
** (3) If the file begins with the standard SQLite prefix string
|
|
** "SQLite format 3", that file is an ordinary database.
|
|
**
|
|
** (4) If none of the above apply and the SQLITE_OPEN_CREATE flag is
|
|
** set, then a new database is appended to the already existing file.
|
|
**
|
|
** (5) Otherwise, SQLITE_CANTOPEN is returned.
|
|
**
|
|
** To avoid unnecessary complications with the PENDING_BYTE, the size of
|
|
** the file containing the database is limited to 1GiB. (1073741824 bytes)
|
|
** This VFS will not read or write past the 1GiB mark. This restriction
|
|
** might be lifted in future versions. For now, if you need a larger
|
|
** database, then keep it in a separate file.
|
|
**
|
|
** If the file being opened is a plain database (not an appended one), then
|
|
** this shim is a pass-through into the default underlying VFS. (rule 3)
|
|
**/
|
|
#include "sqlite3ext.h"
|
|
SQLITE_EXTENSION_INIT1
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
/* The append mark at the end of the database is:
|
|
**
|
|
** Start-Of-SQLite3-NNNNNNNN
|
|
** 123456789 123456789 12345
|
|
**
|
|
** The NNNNNNNN represents a 64-bit big-endian unsigned integer which is
|
|
** the offset to page 1, and also the length of the prefix content.
|
|
*/
|
|
#define APND_MARK_PREFIX "Start-Of-SQLite3-"
|
|
#define APND_MARK_PREFIX_SZ 17
|
|
#define APND_MARK_FOS_SZ 8
|
|
#define APND_MARK_SIZE (APND_MARK_PREFIX_SZ+APND_MARK_FOS_SZ)
|
|
|
|
/*
|
|
** Maximum size of the combined prefix + database + append-mark. This
|
|
** must be less than 0x40000000 to avoid locking issues on Windows.
|
|
*/
|
|
#define APND_MAX_SIZE (0x40000000)
|
|
|
|
/*
|
|
** Try to align the database to an even multiple of APND_ROUNDUP bytes.
|
|
*/
|
|
#ifndef APND_ROUNDUP
|
|
#define APND_ROUNDUP 4096
|
|
#endif
|
|
#define APND_ALIGN_MASK ((sqlite3_int64)(APND_ROUNDUP-1))
|
|
#define APND_START_ROUNDUP(fsz) (((fsz)+APND_ALIGN_MASK) & ~APND_ALIGN_MASK)
|
|
|
|
/*
|
|
** Forward declaration of objects used by this utility
|
|
*/
|
|
typedef struct sqlite3_vfs ApndVfs;
|
|
typedef struct ApndFile ApndFile;
|
|
|
|
/* Access to a lower-level VFS that (might) implement dynamic loading,
|
|
** access to randomness, etc.
|
|
*/
|
|
#define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData))
|
|
#define ORIGFILE(p) ((sqlite3_file*)(((ApndFile*)(p))+1))
|
|
|
|
/* An open appendvfs file
|
|
**
|
|
** An instance of this structure describes the appended database file.
|
|
** A separate sqlite3_file object is always appended. The appended
|
|
** sqlite3_file object (which can be accessed using ORIGFILE()) describes
|
|
** the entire file, including the prefix, the database, and the
|
|
** append-mark.
|
|
**
|
|
** The structure of an AppendVFS database is like this:
|
|
**
|
|
** +-------------+---------+----------+-------------+
|
|
** | prefix-file | padding | database | append-mark |
|
|
** +-------------+---------+----------+-------------+
|
|
** ^ ^
|
|
** | |
|
|
** iPgOne iMark
|
|
**
|
|
**
|
|
** "prefix file" - file onto which the database has been appended.
|
|
** "padding" - zero or more bytes inserted so that "database"
|
|
** starts on an APND_ROUNDUP boundary
|
|
** "database" - The SQLite database file
|
|
** "append-mark" - The 25-byte "Start-Of-SQLite3-NNNNNNNN" that indicates
|
|
** the offset from the start of prefix-file to the start
|
|
** of "database".
|
|
**
|
|
** The size of the database is iMark - iPgOne.
|
|
**
|
|
** The NNNNNNNN in the "Start-Of-SQLite3-NNNNNNNN" suffix is the value
|
|
** of iPgOne stored as a big-ending 64-bit integer.
|
|
**
|
|
** iMark will be the size of the underlying file minus 25 (APND_MARKSIZE).
|
|
** Or, iMark is -1 to indicate that it has not yet been written.
|
|
*/
|
|
struct ApndFile {
|
|
sqlite3_file base; /* Subclass. MUST BE FIRST! */
|
|
sqlite3_int64 iPgOne; /* Offset to the start of the database */
|
|
sqlite3_int64 iMark; /* Offset of the append mark. -1 if unwritten */
|
|
/* Always followed by another sqlite3_file that describes the whole file */
|
|
};
|
|
|
|
/*
|
|
** Methods for ApndFile
|
|
*/
|
|
static int apndClose(sqlite3_file*);
|
|
static int apndRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
|
|
static int apndWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst);
|
|
static int apndTruncate(sqlite3_file*, sqlite3_int64 size);
|
|
static int apndSync(sqlite3_file*, int flags);
|
|
static int apndFileSize(sqlite3_file*, sqlite3_int64 *pSize);
|
|
static int apndLock(sqlite3_file*, int);
|
|
static int apndUnlock(sqlite3_file*, int);
|
|
static int apndCheckReservedLock(sqlite3_file*, int *pResOut);
|
|
static int apndFileControl(sqlite3_file*, int op, void *pArg);
|
|
static int apndSectorSize(sqlite3_file*);
|
|
static int apndDeviceCharacteristics(sqlite3_file*);
|
|
static int apndShmMap(sqlite3_file*, int iPg, int pgsz, int, void volatile**);
|
|
static int apndShmLock(sqlite3_file*, int offset, int n, int flags);
|
|
static void apndShmBarrier(sqlite3_file*);
|
|
static int apndShmUnmap(sqlite3_file*, int deleteFlag);
|
|
static int apndFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp);
|
|
static int apndUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p);
|
|
|
|
/*
|
|
** Methods for ApndVfs
|
|
*/
|
|
static int apndOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
|
|
static int apndDelete(sqlite3_vfs*, const char *zName, int syncDir);
|
|
static int apndAccess(sqlite3_vfs*, const char *zName, int flags, int *);
|
|
static int apndFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
|
|
static void *apndDlOpen(sqlite3_vfs*, const char *zFilename);
|
|
static void apndDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
|
|
static void (*apndDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void);
|
|
static void apndDlClose(sqlite3_vfs*, void*);
|
|
static int apndRandomness(sqlite3_vfs*, int nByte, char *zOut);
|
|
static int apndSleep(sqlite3_vfs*, int microseconds);
|
|
static int apndCurrentTime(sqlite3_vfs*, double*);
|
|
static int apndGetLastError(sqlite3_vfs*, int, char *);
|
|
static int apndCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
|
|
static int apndSetSystemCall(sqlite3_vfs*, const char*,sqlite3_syscall_ptr);
|
|
static sqlite3_syscall_ptr apndGetSystemCall(sqlite3_vfs*, const char *z);
|
|
static const char *apndNextSystemCall(sqlite3_vfs*, const char *zName);
|
|
|
|
static sqlite3_vfs apnd_vfs = {
|
|
3, /* iVersion (set when registered) */
|
|
0, /* szOsFile (set when registered) */
|
|
1024, /* mxPathname */
|
|
0, /* pNext */
|
|
"apndvfs", /* zName */
|
|
0, /* pAppData (set when registered) */
|
|
apndOpen, /* xOpen */
|
|
apndDelete, /* xDelete */
|
|
apndAccess, /* xAccess */
|
|
apndFullPathname, /* xFullPathname */
|
|
apndDlOpen, /* xDlOpen */
|
|
apndDlError, /* xDlError */
|
|
apndDlSym, /* xDlSym */
|
|
apndDlClose, /* xDlClose */
|
|
apndRandomness, /* xRandomness */
|
|
apndSleep, /* xSleep */
|
|
apndCurrentTime, /* xCurrentTime */
|
|
apndGetLastError, /* xGetLastError */
|
|
apndCurrentTimeInt64, /* xCurrentTimeInt64 */
|
|
apndSetSystemCall, /* xSetSystemCall */
|
|
apndGetSystemCall, /* xGetSystemCall */
|
|
apndNextSystemCall /* xNextSystemCall */
|
|
};
|
|
|
|
static const sqlite3_io_methods apnd_io_methods = {
|
|
3, /* iVersion */
|
|
apndClose, /* xClose */
|
|
apndRead, /* xRead */
|
|
apndWrite, /* xWrite */
|
|
apndTruncate, /* xTruncate */
|
|
apndSync, /* xSync */
|
|
apndFileSize, /* xFileSize */
|
|
apndLock, /* xLock */
|
|
apndUnlock, /* xUnlock */
|
|
apndCheckReservedLock, /* xCheckReservedLock */
|
|
apndFileControl, /* xFileControl */
|
|
apndSectorSize, /* xSectorSize */
|
|
apndDeviceCharacteristics, /* xDeviceCharacteristics */
|
|
apndShmMap, /* xShmMap */
|
|
apndShmLock, /* xShmLock */
|
|
apndShmBarrier, /* xShmBarrier */
|
|
apndShmUnmap, /* xShmUnmap */
|
|
apndFetch, /* xFetch */
|
|
apndUnfetch /* xUnfetch */
|
|
};
|
|
|
|
/*
|
|
** Close an apnd-file.
|
|
*/
|
|
static int apndClose(sqlite3_file *pFile){
|
|
pFile = ORIGFILE(pFile);
|
|
return pFile->pMethods->xClose(pFile);
|
|
}
|
|
|
|
/*
|
|
** Read data from an apnd-file.
|
|
*/
|
|
static int apndRead(
|
|
sqlite3_file *pFile,
|
|
void *zBuf,
|
|
int iAmt,
|
|
sqlite_int64 iOfst
|
|
){
|
|
ApndFile *paf = (ApndFile *)pFile;
|
|
pFile = ORIGFILE(pFile);
|
|
return pFile->pMethods->xRead(pFile, zBuf, iAmt, paf->iPgOne+iOfst);
|
|
}
|
|
|
|
/*
|
|
** Add the append-mark onto what should become the end of the file.
|
|
* If and only if this succeeds, internal ApndFile.iMark is updated.
|
|
* Parameter iWriteEnd is the appendvfs-relative offset of the new mark.
|
|
*/
|
|
static int apndWriteMark(
|
|
ApndFile *paf,
|
|
sqlite3_file *pFile,
|
|
sqlite_int64 iWriteEnd
|
|
){
|
|
sqlite_int64 iPgOne = paf->iPgOne;
|
|
unsigned char a[APND_MARK_SIZE];
|
|
int i = APND_MARK_FOS_SZ;
|
|
int rc;
|
|
assert(pFile == ORIGFILE(paf));
|
|
memcpy(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ);
|
|
while( --i >= 0 ){
|
|
a[APND_MARK_PREFIX_SZ+i] = (unsigned char)(iPgOne & 0xff);
|
|
iPgOne >>= 8;
|
|
}
|
|
iWriteEnd += paf->iPgOne;
|
|
if( SQLITE_OK==(rc = pFile->pMethods->xWrite
|
|
(pFile, a, APND_MARK_SIZE, iWriteEnd)) ){
|
|
paf->iMark = iWriteEnd;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
** Write data to an apnd-file.
|
|
*/
|
|
static int apndWrite(
|
|
sqlite3_file *pFile,
|
|
const void *zBuf,
|
|
int iAmt,
|
|
sqlite_int64 iOfst
|
|
){
|
|
ApndFile *paf = (ApndFile *)pFile;
|
|
sqlite_int64 iWriteEnd = iOfst + iAmt;
|
|
if( iWriteEnd>=APND_MAX_SIZE ) return SQLITE_FULL;
|
|
pFile = ORIGFILE(pFile);
|
|
/* If append-mark is absent or will be overwritten, write it. */
|
|
if( paf->iMark < 0 || paf->iPgOne + iWriteEnd > paf->iMark ){
|
|
int rc = apndWriteMark(paf, pFile, iWriteEnd);
|
|
if( SQLITE_OK!=rc ) return rc;
|
|
}
|
|
return pFile->pMethods->xWrite(pFile, zBuf, iAmt, paf->iPgOne+iOfst);
|
|
}
|
|
|
|
/*
|
|
** Truncate an apnd-file.
|
|
*/
|
|
static int apndTruncate(sqlite3_file *pFile, sqlite_int64 size){
|
|
ApndFile *paf = (ApndFile *)pFile;
|
|
pFile = ORIGFILE(pFile);
|
|
/* The append mark goes out first so truncate failure does not lose it. */
|
|
if( SQLITE_OK!=apndWriteMark(paf, pFile, size) ) return SQLITE_IOERR;
|
|
/* Truncate underlying file just past append mark */
|
|
return pFile->pMethods->xTruncate(pFile, paf->iMark+APND_MARK_SIZE);
|
|
}
|
|
|
|
/*
|
|
** Sync an apnd-file.
|
|
*/
|
|
static int apndSync(sqlite3_file *pFile, int flags){
|
|
pFile = ORIGFILE(pFile);
|
|
return pFile->pMethods->xSync(pFile, flags);
|
|
}
|
|
|
|
/*
|
|
** Return the current file-size of an apnd-file.
|
|
** If the append mark is not yet there, the file-size is 0.
|
|
*/
|
|
static int apndFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
|
|
ApndFile *paf = (ApndFile *)pFile;
|
|
*pSize = ( paf->iMark >= 0 )? (paf->iMark - paf->iPgOne) : 0;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Lock an apnd-file.
|
|
*/
|
|
static int apndLock(sqlite3_file *pFile, int eLock){
|
|
pFile = ORIGFILE(pFile);
|
|
return pFile->pMethods->xLock(pFile, eLock);
|
|
}
|
|
|
|
/*
|
|
** Unlock an apnd-file.
|
|
*/
|
|
static int apndUnlock(sqlite3_file *pFile, int eLock){
|
|
pFile = ORIGFILE(pFile);
|
|
return pFile->pMethods->xUnlock(pFile, eLock);
|
|
}
|
|
|
|
/*
|
|
** Check if another file-handle holds a RESERVED lock on an apnd-file.
|
|
*/
|
|
static int apndCheckReservedLock(sqlite3_file *pFile, int *pResOut){
|
|
pFile = ORIGFILE(pFile);
|
|
return pFile->pMethods->xCheckReservedLock(pFile, pResOut);
|
|
}
|
|
|
|
/*
|
|
** File control method. For custom operations on an apnd-file.
|
|
*/
|
|
static int apndFileControl(sqlite3_file *pFile, int op, void *pArg){
|
|
ApndFile *paf = (ApndFile *)pFile;
|
|
int rc;
|
|
pFile = ORIGFILE(pFile);
|
|
if( op==SQLITE_FCNTL_SIZE_HINT ) *(sqlite3_int64*)pArg += paf->iPgOne;
|
|
rc = pFile->pMethods->xFileControl(pFile, op, pArg);
|
|
if( rc==SQLITE_OK && op==SQLITE_FCNTL_VFSNAME ){
|
|
*(char**)pArg = sqlite3_mprintf("apnd(%lld)/%z", paf->iPgOne,*(char**)pArg);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
** Return the sector-size in bytes for an apnd-file.
|
|
*/
|
|
static int apndSectorSize(sqlite3_file *pFile){
|
|
pFile = ORIGFILE(pFile);
|
|
return pFile->pMethods->xSectorSize(pFile);
|
|
}
|
|
|
|
/*
|
|
** Return the device characteristic flags supported by an apnd-file.
|
|
*/
|
|
static int apndDeviceCharacteristics(sqlite3_file *pFile){
|
|
pFile = ORIGFILE(pFile);
|
|
return pFile->pMethods->xDeviceCharacteristics(pFile);
|
|
}
|
|
|
|
/* Create a shared memory file mapping */
|
|
static int apndShmMap(
|
|
sqlite3_file *pFile,
|
|
int iPg,
|
|
int pgsz,
|
|
int bExtend,
|
|
void volatile **pp
|
|
){
|
|
pFile = ORIGFILE(pFile);
|
|
return pFile->pMethods->xShmMap(pFile,iPg,pgsz,bExtend,pp);
|
|
}
|
|
|
|
/* Perform locking on a shared-memory segment */
|
|
static int apndShmLock(sqlite3_file *pFile, int offset, int n, int flags){
|
|
pFile = ORIGFILE(pFile);
|
|
return pFile->pMethods->xShmLock(pFile,offset,n,flags);
|
|
}
|
|
|
|
/* Memory barrier operation on shared memory */
|
|
static void apndShmBarrier(sqlite3_file *pFile){
|
|
pFile = ORIGFILE(pFile);
|
|
pFile->pMethods->xShmBarrier(pFile);
|
|
}
|
|
|
|
/* Unmap a shared memory segment */
|
|
static int apndShmUnmap(sqlite3_file *pFile, int deleteFlag){
|
|
pFile = ORIGFILE(pFile);
|
|
return pFile->pMethods->xShmUnmap(pFile,deleteFlag);
|
|
}
|
|
|
|
/* Fetch a page of a memory-mapped file */
|
|
static int apndFetch(
|
|
sqlite3_file *pFile,
|
|
sqlite3_int64 iOfst,
|
|
int iAmt,
|
|
void **pp
|
|
){
|
|
ApndFile *p = (ApndFile *)pFile;
|
|
if( p->iMark < 0 || iOfst+iAmt > p->iMark ){
|
|
return SQLITE_IOERR; /* Cannot read what is not yet there. */
|
|
}
|
|
pFile = ORIGFILE(pFile);
|
|
return pFile->pMethods->xFetch(pFile, iOfst+p->iPgOne, iAmt, pp);
|
|
}
|
|
|
|
/* Release a memory-mapped page */
|
|
static int apndUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){
|
|
ApndFile *p = (ApndFile *)pFile;
|
|
pFile = ORIGFILE(pFile);
|
|
return pFile->pMethods->xUnfetch(pFile, iOfst+p->iPgOne, pPage);
|
|
}
|
|
|
|
/*
|
|
** Try to read the append-mark off the end of a file. Return the
|
|
** start of the appended database if the append-mark is present.
|
|
** If there is no valid append-mark, return -1;
|
|
**
|
|
** An append-mark is only valid if the NNNNNNNN start-of-database offset
|
|
** indicates that the appended database contains at least one page. The
|
|
** start-of-database value must be a multiple of 512.
|
|
*/
|
|
static sqlite3_int64 apndReadMark(sqlite3_int64 sz, sqlite3_file *pFile){
|
|
int rc, i;
|
|
sqlite3_int64 iMark;
|
|
int msbs = 8 * (APND_MARK_FOS_SZ-1);
|
|
unsigned char a[APND_MARK_SIZE];
|
|
|
|
if( APND_MARK_SIZE!=(sz & 0x1ff) ) return -1;
|
|
rc = pFile->pMethods->xRead(pFile, a, APND_MARK_SIZE, sz-APND_MARK_SIZE);
|
|
if( rc ) return -1;
|
|
if( memcmp(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ)!=0 ) return -1;
|
|
iMark = ((sqlite3_int64)(a[APND_MARK_PREFIX_SZ] & 0x7f)) << msbs;
|
|
for(i=1; i<8; i++){
|
|
msbs -= 8;
|
|
iMark |= (sqlite3_int64)a[APND_MARK_PREFIX_SZ+i]<<msbs;
|
|
}
|
|
if( iMark > (sz - APND_MARK_SIZE - 512) ) return -1;
|
|
if( iMark & 0x1ff ) return -1;
|
|
return iMark;
|
|
}
|
|
|
|
static const char apvfsSqliteHdr[] = "SQLite format 3";
|
|
/*
|
|
** Check to see if the file is an appendvfs SQLite database file.
|
|
** Return true iff it is such. Parameter sz is the file's size.
|
|
*/
|
|
static int apndIsAppendvfsDatabase(sqlite3_int64 sz, sqlite3_file *pFile){
|
|
int rc;
|
|
char zHdr[16];
|
|
sqlite3_int64 iMark = apndReadMark(sz, pFile);
|
|
if( iMark>=0 ){
|
|
/* If file has the correct end-marker, the expected odd size, and the
|
|
** SQLite DB type marker where the end-marker puts it, then it
|
|
** is an appendvfs database.
|
|
*/
|
|
rc = pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), iMark);
|
|
if( SQLITE_OK==rc
|
|
&& memcmp(zHdr, apvfsSqliteHdr, sizeof(zHdr))==0
|
|
&& (sz & 0x1ff) == APND_MARK_SIZE
|
|
&& sz>=512+APND_MARK_SIZE
|
|
){
|
|
return 1; /* It's an appendvfs database */
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
** Check to see if the file is an ordinary SQLite database file.
|
|
** Return true iff so. Parameter sz is the file's size.
|
|
*/
|
|
static int apndIsOrdinaryDatabaseFile(sqlite3_int64 sz, sqlite3_file *pFile){
|
|
char zHdr[16];
|
|
if( apndIsAppendvfsDatabase(sz, pFile) /* rule 2 */
|
|
|| (sz & 0x1ff) != 0
|
|
|| SQLITE_OK!=pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), 0)
|
|
|| memcmp(zHdr, apvfsSqliteHdr, sizeof(zHdr))!=0
|
|
){
|
|
return 0;
|
|
}else{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Open an apnd file handle.
|
|
*/
|
|
static int apndOpen(
|
|
sqlite3_vfs *pApndVfs,
|
|
const char *zName,
|
|
sqlite3_file *pFile,
|
|
int flags,
|
|
int *pOutFlags
|
|
){
|
|
ApndFile *pApndFile = (ApndFile*)pFile;
|
|
sqlite3_file *pBaseFile = ORIGFILE(pFile);
|
|
sqlite3_vfs *pBaseVfs = ORIGVFS(pApndVfs);
|
|
int rc;
|
|
sqlite3_int64 sz = 0;
|
|
if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){
|
|
/* The appendvfs is not to be used for transient or temporary databases.
|
|
** Just use the base VFS open to initialize the given file object and
|
|
** open the underlying file. (Appendvfs is then unused for this file.)
|
|
*/
|
|
return pBaseVfs->xOpen(pBaseVfs, zName, pFile, flags, pOutFlags);
|
|
}
|
|
memset(pApndFile, 0, sizeof(ApndFile));
|
|
pFile->pMethods = &apnd_io_methods;
|
|
pApndFile->iMark = -1; /* Append mark not yet written */
|
|
|
|
rc = pBaseVfs->xOpen(pBaseVfs, zName, pBaseFile, flags, pOutFlags);
|
|
if( rc==SQLITE_OK ){
|
|
rc = pBaseFile->pMethods->xFileSize(pBaseFile, &sz);
|
|
if( rc ){
|
|
pBaseFile->pMethods->xClose(pBaseFile);
|
|
}
|
|
}
|
|
if( rc ){
|
|
pFile->pMethods = 0;
|
|
return rc;
|
|
}
|
|
if( apndIsOrdinaryDatabaseFile(sz, pBaseFile) ){
|
|
/* The file being opened appears to be just an ordinary DB. Copy
|
|
** the base dispatch-table so this instance mimics the base VFS.
|
|
*/
|
|
memmove(pApndFile, pBaseFile, pBaseVfs->szOsFile);
|
|
return SQLITE_OK;
|
|
}
|
|
pApndFile->iPgOne = apndReadMark(sz, pFile);
|
|
if( pApndFile->iPgOne>=0 ){
|
|
pApndFile->iMark = sz - APND_MARK_SIZE; /* Append mark found */
|
|
return SQLITE_OK;
|
|
}
|
|
if( (flags & SQLITE_OPEN_CREATE)==0 ){
|
|
pBaseFile->pMethods->xClose(pBaseFile);
|
|
rc = SQLITE_CANTOPEN;
|
|
pFile->pMethods = 0;
|
|
}else{
|
|
/* Round newly added appendvfs location to #define'd page boundary.
|
|
** Note that nothing has yet been written to the underlying file.
|
|
** The append mark will be written along with first content write.
|
|
** Until then, paf->iMark value indicates it is not yet written.
|
|
*/
|
|
pApndFile->iPgOne = APND_START_ROUNDUP(sz);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
** Delete an apnd file.
|
|
** For an appendvfs, this could mean delete the appendvfs portion,
|
|
** leaving the appendee as it was before it gained an appendvfs.
|
|
** For now, this code deletes the underlying file too.
|
|
*/
|
|
static int apndDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
|
|
return ORIGVFS(pVfs)->xDelete(ORIGVFS(pVfs), zPath, dirSync);
|
|
}
|
|
|
|
/*
|
|
** All other VFS methods are pass-thrus.
|
|
*/
|
|
static int apndAccess(
|
|
sqlite3_vfs *pVfs,
|
|
const char *zPath,
|
|
int flags,
|
|
int *pResOut
|
|
){
|
|
return ORIGVFS(pVfs)->xAccess(ORIGVFS(pVfs), zPath, flags, pResOut);
|
|
}
|
|
static int apndFullPathname(
|
|
sqlite3_vfs *pVfs,
|
|
const char *zPath,
|
|
int nOut,
|
|
char *zOut
|
|
){
|
|
return ORIGVFS(pVfs)->xFullPathname(ORIGVFS(pVfs),zPath,nOut,zOut);
|
|
}
|
|
static void *apndDlOpen(sqlite3_vfs *pVfs, const char *zPath){
|
|
return ORIGVFS(pVfs)->xDlOpen(ORIGVFS(pVfs), zPath);
|
|
}
|
|
static void apndDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
|
|
ORIGVFS(pVfs)->xDlError(ORIGVFS(pVfs), nByte, zErrMsg);
|
|
}
|
|
static void (*apndDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){
|
|
return ORIGVFS(pVfs)->xDlSym(ORIGVFS(pVfs), p, zSym);
|
|
}
|
|
static void apndDlClose(sqlite3_vfs *pVfs, void *pHandle){
|
|
ORIGVFS(pVfs)->xDlClose(ORIGVFS(pVfs), pHandle);
|
|
}
|
|
static int apndRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
|
|
return ORIGVFS(pVfs)->xRandomness(ORIGVFS(pVfs), nByte, zBufOut);
|
|
}
|
|
static int apndSleep(sqlite3_vfs *pVfs, int nMicro){
|
|
return ORIGVFS(pVfs)->xSleep(ORIGVFS(pVfs), nMicro);
|
|
}
|
|
static int apndCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
|
|
return ORIGVFS(pVfs)->xCurrentTime(ORIGVFS(pVfs), pTimeOut);
|
|
}
|
|
static int apndGetLastError(sqlite3_vfs *pVfs, int a, char *b){
|
|
return ORIGVFS(pVfs)->xGetLastError(ORIGVFS(pVfs), a, b);
|
|
}
|
|
static int apndCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){
|
|
return ORIGVFS(pVfs)->xCurrentTimeInt64(ORIGVFS(pVfs), p);
|
|
}
|
|
static int apndSetSystemCall(
|
|
sqlite3_vfs *pVfs,
|
|
const char *zName,
|
|
sqlite3_syscall_ptr pCall
|
|
){
|
|
return ORIGVFS(pVfs)->xSetSystemCall(ORIGVFS(pVfs),zName,pCall);
|
|
}
|
|
static sqlite3_syscall_ptr apndGetSystemCall(
|
|
sqlite3_vfs *pVfs,
|
|
const char *zName
|
|
){
|
|
return ORIGVFS(pVfs)->xGetSystemCall(ORIGVFS(pVfs),zName);
|
|
}
|
|
static const char *apndNextSystemCall(sqlite3_vfs *pVfs, const char *zName){
|
|
return ORIGVFS(pVfs)->xNextSystemCall(ORIGVFS(pVfs), zName);
|
|
}
|
|
|
|
|
|
#ifdef _WIN32
|
|
__declspec(dllexport)
|
|
#endif
|
|
/*
|
|
** This routine is called when the extension is loaded.
|
|
** Register the new VFS.
|
|
*/
|
|
int sqlite3_appendvfs_init(
|
|
sqlite3 *db,
|
|
char **pzErrMsg,
|
|
const sqlite3_api_routines *pApi
|
|
){
|
|
int rc = SQLITE_OK;
|
|
sqlite3_vfs *pOrig;
|
|
SQLITE_EXTENSION_INIT2(pApi);
|
|
(void)pzErrMsg;
|
|
(void)db;
|
|
pOrig = sqlite3_vfs_find(0);
|
|
if( pOrig==0 ) return SQLITE_ERROR;
|
|
apnd_vfs.iVersion = pOrig->iVersion;
|
|
apnd_vfs.pAppData = pOrig;
|
|
apnd_vfs.szOsFile = pOrig->szOsFile + sizeof(ApndFile);
|
|
rc = sqlite3_vfs_register(&apnd_vfs, 0);
|
|
#ifdef APPENDVFS_TEST
|
|
if( rc==SQLITE_OK ){
|
|
rc = sqlite3_auto_extension((void(*)(void))apndvfsRegister);
|
|
}
|
|
#endif
|
|
if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY;
|
|
return rc;
|
|
}
|