** 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.
** (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
** 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
*/
#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
*/
#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;
};
/*
apndUnfetch /* xUnfetch */
};
-
-
/*
** Close an apnd-file.
*/
}
/*
-** 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;
}
/*
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);
}
/*
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;
}
/*
** 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;
}
/*
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);
}
return pFile->pMethods->xUnfetch(pFile, iOfst+p->iPgOne, 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.
*/
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);
}
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,