diff options
Diffstat (limited to 'ext/misc/appendvfs.c')
-rw-r--r-- | ext/misc/appendvfs.c | 243 |
1 files changed, 165 insertions, 78 deletions
diff --git a/ext/misc/appendvfs.c b/ext/misc/appendvfs.c index 14260efb5..6d3578800 100644 --- a/ext/misc/appendvfs.c +++ b/ext/misc/appendvfs.c @@ -14,24 +14,25 @@ ** 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 an offset to page 1. For -** best performance page 1 should be located at a disk page boundary, though -** that is not required. +** 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 is as a +** the file as an ordinary SQLite database, or it might treat it as a ** database appended onto some other file. Here are the rules: ** ** (1) When opening a new empty file, that file is treated as an ordinary ** database. ** -** (2) When opening a file that begins with the standard SQLite prefix -** string "SQLite format 3", that file is treated as an ordinary -** database. -** -** (3) When opening a file that ends with the appendvfs trailer string +** (2) When opening a file that ends with the appendvfs trailer string ** "Start-Of-SQLite3-NNNNNNNN" that file is treated as an appended -** database. +** database, even if rule 3 otherwise applies. +** +** (3) When opening a file that begins with the standard SQLite prefix +** string "SQLite format 3", that file is treated as an ordinary +** database, unless rule 2 applies. ** ** (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. @@ -39,13 +40,13 @@ ** (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 1GB. This VFS will refuse -** to read or write past the 1GB mark. This restriction might be lifted in -** future versions. For now, if you need a large database, then keep the -** database in a separate file. +** the file containing the database is limited to 1GB. (1000013824 bytes) +** This VFS will not read or write past the 1GB 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 not an appended database, then this shim is -** a pass-through into the default underlying VFS. +** 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 @@ -58,11 +59,12 @@ SQLITE_EXTENSION_INIT1 ** 123456789 123456789 12345 ** ** The NNNNNNNN represents a 64-bit big-endian unsigned integer which is -** the offset to page 1. +** 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_SIZE 25 +#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 @@ -71,6 +73,11 @@ SQLITE_EXTENSION_INIT1 #define APND_MAX_SIZE (65536*15259) /* +** Size of storage page upon which to align appendvfs portion. +*/ +#define APND_ROUNDUP_BITS 12 + +/* ** Forward declaration of objects used by this utility */ typedef struct sqlite3_vfs ApndVfs; @@ -82,11 +89,39 @@ typedef struct ApndFile ApndFile; #define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData)) #define ORIGFILE(p) ((sqlite3_file*)(((ApndFile*)(p))+1)) -/* An open file */ +/* Invariants for an open appendvfs file: + * Once an appendvfs file is opened, it will be in one of three states: + * State 0: Never written. Underlying file (if any) is unaltered. + * State 1: Append mark is persisted, content write is in progress. + * State 2: Append mark is persisted, content writes are complete. + * + * State 0 is persistent in the sense that nothing will have been done + * to the underlying file, including any attempt to convert it to an + * appendvfs file. + * + * State 1 is normally transitory. However, if a write operation ends + * abnormally (disk full, power loss, process kill, etc.), then State 1 + * may be persistent on disk with an incomplete content write-out. This + * is logically equivalent to an interrupted write to an ordinary file, + * where some unknown portion of to-be-written data is persisted while + * the remainder is not. Database integrity in such cases is maintained + * (or not) by the same measures available for ordinary file access. + * + * State 2 is persistent under normal circumstances (when there is no + * abnormal termination of a write operation such that data provided + * to the underlying VFS write method has not yet reached storage.) + * + * In order to maintain the state invariant, the append mark is written + * in advance of content writes where any part of such content would + * overwrite an existing (or yet to be written) append mark. + */ struct ApndFile { - sqlite3_file base; /* IO methods */ - sqlite3_int64 iPgOne; /* File offset to page 1 */ - sqlite3_int64 iMark; /* Start of the append-mark */ + /* IO methods of the underlying file */ + sqlite3_file base; + /* File offset to beginning of appended content (unchanging) */ + sqlite3_int64 iPgOne; + /* File offset of written append-mark, or -1 if unwritten */ + sqlite3_int64 iMark; }; /* @@ -178,8 +213,6 @@ static const sqlite3_io_methods apnd_io_methods = { apndUnfetch /* xUnfetch */ }; - - /* ** Close an apnd-file. */ @@ -203,16 +236,29 @@ static int apndRead( } /* -** Add the append-mark onto the end of the file. +** Add the append-mark onto what should become the end of the file. +* If and only if this succeeds, the ApndFile.iMark is updated. +* If it fails, there is little reason to proceed with content writes. +* Parameter imoNext is the appendvfs-relative offset of the new mark. */ -static int apndWriteMark(ApndFile *p, sqlite3_file *pFile){ - int i; +static int apndWriteMark( + ApndFile *p, + sqlite3_file *pFile, + sqlite_int64 imoNext +){ unsigned char a[APND_MARK_SIZE]; + int ibs = (APND_MARK_FOS_SZ - 1) * 8; + int i, rc; memcpy(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ); - for(i=0; i<8; i++){ - a[APND_MARK_PREFIX_SZ+i] = (p->iPgOne >> (56 - i*8)) & 0xff; + for(i=0; i<APND_MARK_FOS_SZ; ibs -= 8, i++){ + a[APND_MARK_PREFIX_SZ+i] = (p->iPgOne >> ibs) & 0xff; + } + imoNext += p->iPgOne; + if( SQLITE_OK==(rc = pFile->pMethods->xWrite + (pFile, a, APND_MARK_SIZE, imoNext)) ){ + p->iMark = imoNext; } - return pFile->pMethods->xWrite(pFile, a, APND_MARK_SIZE, p->iMark); + return rc; } /* @@ -224,23 +270,16 @@ static int apndWrite( int iAmt, sqlite_int64 iOfst ){ - int rc; ApndFile *p = (ApndFile *)pFile; + sqlite_int64 imoNext = iOfst + iAmt; + if( imoNext>=APND_MAX_SIZE ) return SQLITE_FULL; pFile = ORIGFILE(pFile); - if( iOfst+iAmt>=APND_MAX_SIZE ) return SQLITE_FULL; - rc = pFile->pMethods->xWrite(pFile, zBuf, iAmt, iOfst+p->iPgOne); - if( rc==SQLITE_OK && iOfst + iAmt + p->iPgOne > p->iMark ){ - sqlite3_int64 sz = 0; - rc = pFile->pMethods->xFileSize(pFile, &sz); - if( rc==SQLITE_OK ){ - p->iMark = sz - APND_MARK_SIZE; - if( iOfst + iAmt + p->iPgOne > p->iMark ){ - p->iMark = p->iPgOne + iOfst + iAmt; - rc = apndWriteMark(p, pFile); - } - } + if( p->iMark < 0 || imoNext + p->iPgOne > p->iMark ){ + int rc = apndWriteMark(p, pFile, imoNext); + if( SQLITE_OK!=rc ) + return rc; } - return rc; + return pFile->pMethods->xWrite(pFile, zBuf, iAmt, iOfst+p->iPgOne); } /* @@ -249,11 +288,13 @@ static int apndWrite( static int apndTruncate(sqlite3_file *pFile, sqlite_int64 size){ int rc; ApndFile *p = (ApndFile *)pFile; + sqlite_int64 iomNext = size+p->iPgOne; pFile = ORIGFILE(pFile); - rc = pFile->pMethods->xTruncate(pFile, size+p->iPgOne+APND_MARK_SIZE); + rc = apndWriteMark(p, pFile, iomNext); if( rc==SQLITE_OK ){ - p->iMark = p->iPgOne+size; - rc = apndWriteMark(p, pFile); + rc = pFile->pMethods->xTruncate(pFile, size+p->iPgOne+APND_MARK_SIZE); + if( rc==SQLITE_OK ) + p->iMark = iomNext; } return rc; } @@ -268,16 +309,12 @@ static int apndSync(sqlite3_file *pFile, int 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 *p = (ApndFile *)pFile; - int rc; - pFile = ORIGFILE(p); - rc = pFile->pMethods->xFileSize(pFile, pSize); - if( rc==SQLITE_OK && p->iPgOne ){ - *pSize -= p->iPgOne + APND_MARK_SIZE; - } - return rc; + ApndFile *paf = (ApndFile *)pFile; + *pSize = ( paf->iMark >= 0 )? (paf->iMark - paf->iPgOne) : 0; + return SQLITE_OK; } /* @@ -372,6 +409,8 @@ static int apndFetch( 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); } @@ -384,39 +423,68 @@ static int apndUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){ } /* -** Check to see if the file is an ordinary SQLite database file. -*/ -static int apndIsOrdinaryDatabaseFile(sqlite3_int64 sz, sqlite3_file *pFile){ - int rc; - char zHdr[16]; - static const char aSqliteHdr[] = "SQLite format 3"; - if( sz<512 ) return 0; - rc = pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), 0); - if( rc ) return 0; - return memcmp(zHdr, aSqliteHdr, sizeof(zHdr))==0; -} - -/* ** 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 append-mark, return -1; +** start of the appended database if the append-mark is present. +** If there is no valid append-mark, return -1; */ 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( sz<=APND_MARK_SIZE ) return -1; + 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))<<56; - for(i=1; i<8; i++){ - iMark += (sqlite3_int64)a[APND_MARK_PREFIX_SZ+i]<<(56-8*i); + 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; } 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 right end-marker, the expected odd size, and the + * SQLite DB type marker where the end-marker puts it, then it + * is an appendvfs database (to be treated as such.) + */ + 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; + return 1; +} + +/* Round-up used to get appendvfs portion to begin at a page boundary. */ +#define APND_START_ROUNDUP(fsz, nPageBits) \ + ((fsz) + ((1<<nPageBits)-1) & ~(sqlite3_int64)((1<<nPageBits)-1)) + /* ** Open an apnd file handle. */ @@ -433,6 +501,10 @@ static int apndOpen( int rc; sqlite3_int64 sz; pSubVfs = ORIGVFS(pVfs); + /* The appendvfs is not to be used for auxillary DB files. + * Attempting such will result in simply opening the named + * file however the underlying VFS does that. + */ if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){ return pSubVfs->xOpen(pSubVfs, zName, pFile, flags, pOutFlags); } @@ -451,27 +523,42 @@ static int apndOpen( memmove(pFile, pSubFile, pSubVfs->szOsFile); return SQLITE_OK; } - p->iMark = 0; + /* Record that append mark has not been written until seen otherwise. */ + p->iMark = -1; p->iPgOne = apndReadMark(sz, pFile); - if( p->iPgOne>0 ){ + if( p->iPgOne>=0 ){ + /* Append mark was found, infer its offset */ + p->iMark = sz - p->iPgOne - APND_MARK_SIZE; return SQLITE_OK; } if( (flags & SQLITE_OPEN_CREATE)==0 ){ pSubFile->pMethods->xClose(pSubFile); rc = SQLITE_CANTOPEN; } - p->iPgOne = (sz+0xfff) & ~(sqlite3_int64)0xfff; + /* 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, the p->iMark value indicates it is not yet written. + */ + p->iPgOne = APND_START_ROUNDUP(sz, APND_ROUNDUP_BITS); apnd_open_done: if( rc ) pFile->pMethods = 0; return rc; } /* -** All other VFS methods are pass-thrus. +** 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, |