]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Initial check-in of a test VFS designed to simulate a NAND-flash SSD for the
authordrh <drh@noemail.net>
Thu, 25 Oct 2012 01:50:59 +0000 (01:50 +0000)
committerdrh <drh@noemail.net>
Thu, 25 Oct 2012 01:50:59 +0000 (01:50 +0000)
purpose of measuring and subsequently minimizing write amplification caused
by SQLite.  The code in this check-in compiles but does not run.

FossilOrigin-Name: 9e6efcf05402cd0b259c9b2ae57e349800650bd7

manifest
manifest.uuid
src/shell.c
src/test_ssdsim.c [new file with mode: 0644]

index 4e7f38b9c76adb61c43c16c774711494ff3fd34a..4603362b799945775be1dc5c060abf0f470cf96e 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Make\ssure\ssubstructure\selements\shave\sproper\salignment\sin\sthe\sICU\stokenizers\nof\sFTS2\sand\sFTS3.
-D 2012-10-19T02:10:53.511
+C Initial\scheck-in\sof\sa\stest\sVFS\sdesigned\sto\ssimulate\sa\sNAND-flash\sSSD\sfor\sthe\npurpose\sof\smeasuring\sand\ssubsequently\sminimizing\swrite\samplification\scaused\nby\sSQLite.\s\sThe\scode\sin\sthis\scheck-in\scompiles\sbut\sdoes\snot\srun.
+D 2012-10-25T01:50:59.924
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 5f4f26109f9d80829122e0e09f9cda008fa065fb
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -175,7 +175,7 @@ F src/random.c cd4a67b3953b88019f8cd4ccd81394a8ddfaba50
 F src/resolve.c 7b986a715ac281643309c29257bb58cfae7aa810
 F src/rowset.c 64655f1a627c9c212d9ab497899e7424a34222e0
 F src/select.c 9b759521f805e25e97baa2dd64037157fe365817
-F src/shell.c 8ee5a3cb502e2d574f97b43972e6c1e275e7bec7
+F src/shell.c 623242218008315d402d3781c12c26800f4c5edd
 F src/sqlite.h.in c7be05ad191d2634292fcc77bdb2bcfa4526eb98
 F src/sqlite3.rc fea433eb0a59f4c9393c8e6d76a6e2596b1fe0c0
 F src/sqlite3ext.h 6904f4aadf976f95241311fbffb00823075d9477
@@ -221,6 +221,7 @@ F src/test_rtree.c aba603c949766c4193f1068b91c787f57274e0d9
 F src/test_schema.c 8c06ef9ddb240c7a0fcd31bc221a6a2aade58bf0
 F src/test_server.c 2f99eb2837dfa06a4aacf24af24c6affdf66a84f
 F src/test_spellfix.c 76dd8d3111d2f5354c374f71fa23b752bd0b029c
+F src/test_ssdsim.c 8588d7342a0f7860a4f82133739649f68061f19c
 F src/test_stat.c d1569c7a4839f13e80187e2c26b2ab4da2d03935
 F src/test_superlock.c 2b97936ca127d13962c3605dbc9a4ef269c424cd
 F src/test_syscall.c a992d8c80ea91fbf21fb2dd570db40e77dd7e6ae
@@ -1021,7 +1022,10 @@ F tool/vdbe-compress.tcl f12c884766bd14277f4fcedcae07078011717381
 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381
 F tool/win/sqlite.vsix 67d8a99aceb56384a81b3f30d6c71743146d2cc9
-P 0482c73bfdf80b0c0ce9abea706554d7ddf36f69
-R 359b18ae3c286a09e1c7579425de96fe
+P aaa2d9b0db74d8452d9294de17cff786ab4ec7c8
+R 96b1e07669c40725e91ba8ce05ad794a
+T *branch * ssdsim
+T *sym-ssdsim *
+T -sym-trunk *
 U drh
-Z 09f8af841cfaabcfb0c92e8b07cb012e
+Z b9dc476f0ad97be89f8c78207cb254e0
index 9bcbd35ae6f6cc61cdefc4b443c628e7351e411a..f8bf09ca638cf52a6cfa156ea92ac96f67905611 100644 (file)
@@ -1 +1 @@
-aaa2d9b0db74d8452d9294de17cff786ab4ec7c8
\ No newline at end of file
+9e6efcf05402cd0b259c9b2ae57e349800650bd7
\ No newline at end of file
index 7ad60c505bf15250dc8dd8658d9512cb1fd918ce..5e1b6ffb397634cb30939a4ef21927799588143a 100644 (file)
@@ -1016,6 +1016,10 @@ static char *save_err_msg(
   return zErrMsg;
 }
 
+#ifdef SQLITE_ENABLE_SSDSIM
+extern void ssdsim_report(FILE*, int);
+#endif
+
 /*
 ** Display memory stats.
 */
@@ -2100,6 +2104,9 @@ static int do_meta_command(char *zLine, struct callback_data *p){
   }else
 
   if( c=='q' && strncmp(azArg[0], "quit", n)==0 && nArg==1 ){
+#ifdef SQLITE_ENABLE_SSDSIM
+    ssdsim_report(p->out, 0);
+#endif
     rc = 2;
   }else
 
@@ -2953,6 +2960,11 @@ int main(int argc, char **argv){
     }else if( strcmp(z,"-multiplex")==0 ){
       extern int sqlite3_multiple_initialize(const char*,int);
       sqlite3_multiplex_initialize(0, 1);
+#endif
+#ifdef SQLITE_ENABLE_SSDSIM
+    }else if( strcmp(z, "-ssdsim")==0 ){
+      extern int ssdsim_register(const char*,const char*,int);
+      ssdsim_register(0, argv[++i], 1);
 #endif
     }else if( strcmp(z,"-vfs")==0 ){
       sqlite3_vfs *pVfs = sqlite3_vfs_find(argv[++i]);
@@ -3077,6 +3089,10 @@ int main(int argc, char **argv){
 #ifdef SQLITE_ENABLE_MULTIPLEX
     }else if( strcmp(z,"-multiplex")==0 ){
       i++;
+#endif
+#ifdef SQLITE_ENABLE_SSDSIM
+    }else if( strcmp(z,"-ssdsim")==0 ){
+      i++;
 #endif
     }else if( strcmp(z,"-help")==0 ){
       usage(1);
diff --git a/src/test_ssdsim.c b/src/test_ssdsim.c
new file mode 100644 (file)
index 0000000..a259ac2
--- /dev/null
@@ -0,0 +1,912 @@
+/*
+** 2012 October 23
+**
+** 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 code implements a VFS shim that attempts to simulate
+** a NAND-flash SSD in order to estimate the Write Amplification Factor
+** (WAF) for a typical SQLite workload.
+**
+** This simulator is single-threaded, for simplicity.
+**
+** USAGE:
+**
+** This source file exports a single symbol which is the name of a
+** function:
+**
+**   int ssdsim_register(
+**     const char *zBaseVfsName,     // Name of the underlying real VFS
+**     int makeDefault               // Make the new VFS the default
+**   );
+*/
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include "sqlite3.h"
+
+/* Forward declaration of objects */
+typedef struct ssdsim_state ssdsim_state;
+typedef struct ssdsim_inode ssdsim_inode;
+typedef struct ssdsim_file ssdsim_file;
+
+/*
+** Each file on disk
+*/
+struct ssdsim_inode {
+  ssdsim_inode *pNext;      /* Next inode in a list of them all */
+  char *zPath;              /* Full pathname of the file */
+  sqlite3_int64 len;        /* Size of the file in bytes */
+  int *aiPage;               /* Array of logical page numbers */
+  ssdsim_file *pFiles;      /* List of open file descriptors */
+  int inodeFlags;           /* SSDSIM_* flags */
+  int nShmRegion;           /* Number of allocated shared memory regions */
+  int szShmRegion;          /* Size of each shared-memory region */
+  char **apShm;             /* Shared memory regions */
+};
+
+#define SSDSIM_DELETEONCLOSE   0x0001
+
+/*
+** Each open file
+*/
+struct ssdsim_file {
+  sqlite3_file base;            /* Base class.  Must be first */
+  ssdsim_file *pNext;           /* Next opening of the same inode */
+  ssdsim_inode *pInode;         /* The file */
+  signed char eLock;            /* Lock state for this connection */
+  unsigned char shmOpen;        /* True if SHM is open */
+  unsigned short shmReadLock;   /* Shared locks held by the shared memory */
+  unsigned short shmWriteLock;  /* Exclusive locks held by the shared memory */
+  int openFlags;                /* xOpen() flags used to open this connection */
+};
+
+/*
+** Page status values
+*/
+#define SSDSIM_FREE      0
+#define SSDSIM_WRITTEN   1
+#define SSDSIM_OBSOLETE  2
+
+/*
+** Global state of the SSD simulator
+*/
+struct ssdsim_state {
+  int szPage;               /* Size of each page in bytes */
+  int szEBlock;             /* Size of an erase block in bytes */
+  sqlite3_int64 szDisk;     /* Total disk space in bytes */
+  int nPage;                /* Number of slots allocated in apPage[] */
+  int nEBlock;              /* Nubmer of erase blocks */
+  int nDealloc;             /* Number of reusable logical page numbers */
+  int mxAlloc;              /* Maximum allocated logical page number */
+  unsigned char **apPage;   /* Memory to hold physical pages */
+  int *aDealloc;            /* Array of reuseable logical page numbers */
+  int *pageMap;             /* Mapping from logical to physical pages */
+  unsigned char *eStat;     /* Status of each page */
+  unsigned int *nErase;     /* Number of erasures for each erase block */
+  ssdsim_inode *pInode;     /* List of all inodes */
+  int traceFlag;            /* True to trace operation */
+  int nHostWrite;           /* Number of pages written by the application */
+  int nNANDWrite;           /* Number of pages written to NAND-flash */
+  sqlite3_vfs *pBase;       /* True underlying VFS */
+};
+static ssdsim_state g;
+
+/*
+** Method declarations for ssdsim_file.
+*/
+static int ssdsimClose(sqlite3_file*);
+static int ssdsimRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+static int ssdsimWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64);
+static int ssdsimTruncate(sqlite3_file*, sqlite3_int64 size);
+static int ssdsimSync(sqlite3_file*, int flags);
+static int ssdsimFileSize(sqlite3_file*, sqlite3_int64 *pSize);
+static int ssdsimLock(sqlite3_file*, int);
+static int ssdsimUnlock(sqlite3_file*, int);
+static int ssdsimCheckReservedLock(sqlite3_file*, int *);
+static int ssdsimFileControl(sqlite3_file*, int op, void *pArg);
+static int ssdsimSectorSize(sqlite3_file*);
+static int ssdsimDeviceCharacteristics(sqlite3_file*);
+static int ssdsimShmLock(sqlite3_file*,int,int,int);
+static int ssdsimShmMap(sqlite3_file*,int,int,int, void volatile **);
+static void ssdsimShmBarrier(sqlite3_file*);
+static int ssdsimShmUnmap(sqlite3_file*,int);
+
+/*
+** Method declarations for ssdsim_vfs.
+*/
+static int ssdsimOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
+static int ssdsimDelete(sqlite3_vfs*, const char *zName, int syncDir);
+static int ssdsimAccess(sqlite3_vfs*, const char *zName, int flags, int *);
+static int ssdsimFullPathname(sqlite3_vfs*, const char *zName, int, char *);
+static void *ssdsimDlOpen(sqlite3_vfs*, const char *zFilename);
+static void ssdsimDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
+static void (*ssdsimDlSym(sqlite3_vfs*,void*, const char *zSymbol))(void);
+static void ssdsimDlClose(sqlite3_vfs*, void*);
+static int ssdsimRandomness(sqlite3_vfs*, int nByte, char *zOut);
+static int ssdsimSleep(sqlite3_vfs*, int microseconds);
+static int ssdsimCurrentTime(sqlite3_vfs*, double*);
+static int ssdsimGetLastError(sqlite3_vfs*, int, char*);
+static int ssdsimCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
+static int ssdsimSetSystemCall(sqlite3_vfs*,const char*, sqlite3_syscall_ptr);
+static sqlite3_syscall_ptr ssdsimGetSystemCall(sqlite3_vfs*, const char *);
+static const char *ssdsimNextSystemCall(sqlite3_vfs*, const char *zName);
+
+/*
+** Trace operation.
+*/
+static void ssdsimTrace(const char *zFormat, ...){
+  if( g.traceFlag ){
+    va_list ap;
+    char *zMsg;
+    va_start(ap, zFormat);
+    vprintf(zFormat, ap);
+    va_end(ap);
+  }
+}
+
+/*
+** Clear all memory associated with the ssd simulator
+*/
+static void ssdsimShutdown(void){
+  int i;
+  for(i=0; i<g.nPage; i++) sqlite3_free(g.apPage[i]);
+  sqlite3_free(g.apPage);
+  g.apPage = 0;
+  sqlite3_free(g.aDealloc);
+  g.aDealloc = 0;
+  g.nDealloc = 0;
+  g.mxAlloc = 0;
+  g.nPage = 0;
+}
+
+/*
+** Initialize the ssdsim system.
+*/
+static void ssdsimInit(void){
+  int nPage;
+  if( g.nPage ) return;
+  if( g.szPage==0 ) g.szPage = 4096;
+  if( g.szEBlock==0 ) g.szEBlock = 262144;
+  if( g.szDisk==0 ) g.szDisk = 67108864;
+  g.nPage = g.szDisk/g.szPage;
+  g.nEBlock = g.szDisk/g.szEBlock;
+  sqlite3_free(g.apPage);
+  g.apPage = sqlite3_malloc( sizeof(g.apPage[0])*g.nPage );
+  if( g.apPage==0 ){ ssdsimShutdown(); return; }
+  memset(g.apPage, 0, sizeof(g.apPage[0])*nPage);
+  g.aDealloc = sqlite3_malloc( sizeof(g.aDealloc[0])*g.nPage );
+  if( g.aDealloc==0 ){ ssdsimShutdown(); return; }
+  g.nDealloc = 0;
+  g.mxAlloc = 0;
+  g.nHostWrite = 0;
+  g.nNANDWrite = 0;
+}
+
+/*
+** Allocate a new, unused logical page number
+*/
+static int ssdsimCoreLpnAlloc(void){
+  if( g.nDealloc ){
+    return g.aDealloc[--g.nDealloc];
+  }else if( g.mxAlloc>=g.nPage ){
+    return -1;
+  }else{
+    return g.mxAlloc++;
+  }
+}
+
+/*
+** Indicate that the content of a logical page will never again be
+** read.
+*/
+static void ssdsimCoreTrim(int lpn){
+}
+
+/*
+** Deallocate a logical page number, indicating that it is no longer
+** in use.
+*/
+static int ssdsimCoreLpnDealloc(int lpn){
+  g.aDealloc[g.nDealloc++] = lpn;
+}
+
+/*
+** Translate a logical page number into a physical page number.
+*/
+static int ssdsimCoreLpnToPpn(int lpn, int writeFlag){
+  int ppn = lpn;
+  if( g.apPage[ppn]==0 ){
+    if( writeFlag ){
+      g.apPage[ppn] = sqlite3_malloc( g.szPage );
+    }
+    if( g.apPage[ppn]==0 ) ppn = -1;
+  }
+  return ppn;
+}
+
+/*
+** Indicate that a transaction boundary has occurred
+*/
+static int ssdsimCoreSync(void){
+}
+
+
+/*
+** Truncate an inode
+*/
+static void ssdsimTruncateInode(ssdsim_inode *pInode, sqlite3_int64 size){
+  if( pInode->len > size ){
+    int nOld = pInode->len/g.szPage;
+    int nNew = size/g.szPage;
+    int i;
+    for(i=nOld; i>nNew; i--){
+      ssdsimCoreLpnDealloc(pInode->aiPage[i]);
+    }
+    pInode->len = size;
+  }
+}
+
+/*
+** Delete an inode
+*/
+static void ssdsimDeleteInode(ssdsim_inode *pInode){
+  if( pInode->pFiles ){
+    pInode->inodeFlags |= SSDSIM_DELETEONCLOSE;
+    return;
+  }
+  ssdsimTruncateInode(pInode, 0);
+  sqlite3_free(pInode->apShm);
+  sqlite3_free(pInode->aiPage);
+  if( g.pInode==pInode ){
+    g.pInode = pInode->pNext;
+  }else{
+    ssdsim_inode *pX;
+    for(pX=g.pInode; pX && pX->pNext!=pInode; pX=pX->pNext){}
+    if( pX ) pX->pNext = pInode->pNext;
+  }
+  sqlite3_free(pInode);
+}
+
+
+/*
+** Close an ssdsim-file.
+*/
+static int ssdsimClose(sqlite3_file *pFile){
+  ssdsim_file *p = (ssdsim_file *)pFile;
+  int rc;
+  ssdsim_inode *pInode = p->pInode;
+  if( p==pInode->pFiles ){
+    pInode->pFiles = p->pNext;
+    if( (pInode->inodeFlags & SSDSIM_DELETEONCLOSE)!=0 ){
+      ssdsimDeleteInode(pInode);
+    }
+  }else{
+    ssdsim_file *pX;
+    for(pX = pInode->pFiles; pX && pX->pNext!=p; pX=pX->pNext){}
+    if( pX ) pX->pNext = p->pNext;
+  }
+  memset(p, 0, sizeof(*p));
+  return SQLITE_OK;
+}
+
+/*
+** Read data from an ssdsim-file.
+*/
+static int ssdsimRead(
+  sqlite3_file *pFile, 
+  void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  ssdsim_file *p = (ssdsim_file *)pFile;
+  ssdsim_inode *pInode = p->pInode;
+  int rc = SQLITE_OK;
+  int lpn, ppn, n;
+  unsigned char *pOut = (unsigned char*)zBuf;
+  unsigned char *pContent;
+  while( iAmt>0 ){
+    if( iAmt+iOfst>pInode->len ){
+      rc = SQLITE_IOERR_SHORT_READ;
+      iAmt = pInode->len - iOfst;
+      if( iAmt<=0 ) break;
+    }
+    lpn = pInode->aiPage[iOfst/g.szPage];
+    ppn = ssdsimCoreLpnToPpn(lpn, 0);
+    n = iAmt;
+    if( (iOfst+n-1)*g.szPage > lpn ){
+      n = (lpn+1)*g.szPage - iOfst;
+    }
+    if( ppn>=0 && ppn<g.nPage && (pContent = g.apPage[ppn])!=0 ){
+      memcpy(pOut, &pContent[iOfst%g.szPage], n);
+    }else{
+      memset(pOut, 0, n);
+    }
+    iOfst += n;
+    iAmt -= n;
+    pOut += n;
+  }
+  return rc;
+}
+
+/*
+** Write data to an ssdsim-file.
+*/
+static int ssdsimWrite(
+  sqlite3_file *pFile, 
+  const void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  ssdsim_file *p = (ssdsim_file *)pFile;
+  ssdsim_inode *pInode = p->pInode;
+  int rc = SQLITE_OK;
+  int pn, lpn, ppn;
+  sqlite3_int64 lenNew;
+  const unsigned char *pIn = (const unsigned char*)zBuf;
+  unsigned char *pDest;
+
+  lenNew = iOfst+iAmt;
+  if( lenNew <= pInode->len ){
+    lenNew = pInode->len;
+  }else{
+    int nOld, nNew;
+    int *aiPage;
+    nOld = pInode->len/g.szPage;
+    nNew = (iOfst+iAmt)/g.szPage;
+    if( nOld<nNew ){
+      aiPage = sqlite3_realloc(pInode->aiPage, nNew*sizeof(int));
+      if( aiPage==0 ) return SQLITE_NOMEM;
+      memset(aiPage+nOld, 0xff, sizeof(int)*(nNew - nOld));
+      pInode->aiPage = aiPage;
+    }
+  }
+  while( iAmt>0 ){
+    int n;
+    lpn = pInode->aiPage[iOfst/g.szPage];
+    if( lpn<0 ){
+      lpn = ssdsimCoreLpnAlloc();
+      if( lpn<0 ) return SQLITE_FULL;
+      pInode->aiPage[iOfst/g.szPage];
+    }
+    ppn = ssdsimCoreLpnToPpn(lpn, 1);
+    if( ppn<0 ) return SQLITE_NOMEM;
+    n = iAmt;
+    if( (iOfst+n-1)*g.szPage > lpn ){
+      n = (lpn+1)*g.szPage - iOfst;
+    }
+    pDest = g.apPage[ppn];
+    memcpy(pDest, pIn, n);
+    iOfst += n;
+    iAmt -= n;
+    pIn += n;
+  }
+  pInode->len = lenNew;
+  return rc;
+}
+
+/*
+** Truncate an ssdsim-file.
+*/
+static int ssdsimTruncate(sqlite3_file *pFile, sqlite_int64 size){
+  ssdsim_file *p = (ssdsim_file *)pFile;
+  ssdsim_inode *pInode = p->pInode;
+  ssdsimTruncateInode(pInode, size);
+  return SQLITE_OK;
+}
+
+/*
+** Sync an ssdsim-file.
+*/
+static int ssdsimSync(sqlite3_file *pFile, int flags){
+  ssdsim_file *p = (ssdsim_file *)pFile;
+  ssdsim_inode *pInode = p->pInode;
+  ssdsimCoreSync();
+  return SQLITE_OK;
+}
+
+/*
+** Return the current file-size of an ssdsim-file.
+*/
+static int ssdsimFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
+  ssdsim_file *p = (ssdsim_file *)pFile;
+  ssdsim_inode *pInode = p->pInode;
+  *pSize = pInode->len;
+  return SQLITE_OK;
+}
+
+/*
+** Return the name of a lock.
+*/
+static const char *lockName(int eLock){
+  const char *azLockNames[] = {
+     "NONE", "SHARED", "RESERVED", "PENDING", "EXCLUSIVE"
+  };
+  if( eLock<0 || eLock>=sizeof(azLockNames)/sizeof(azLockNames[0]) ){
+    return "???";
+  }else{
+    return azLockNames[eLock];
+  }
+}
+
+/*
+** Lock an ssdsim-file.
+*/
+static int ssdsimLock(sqlite3_file *pFile, int eLock){
+  ssdsim_file *p = (ssdsim_file *)pFile;
+  ssdsim_inode *pInode = p->pInode;
+  ssdsim_file *pF;
+  int rc = SQLITE_OK;
+  if( eLock==SQLITE_LOCK_SHARED ){
+    for(pF=pInode->pFiles; pF; pF=pF->pNext){
+      if( pF!=p && pF->eLock>=SQLITE_LOCK_PENDING ) return SQLITE_BUSY;
+    }
+  }else if( eLock>=SQLITE_LOCK_RESERVED ){
+    for(pF=pInode->pFiles; pF; pF=pF->pNext){
+      if( pF!=p && pF->eLock>=SQLITE_LOCK_RESERVED ) return SQLITE_BUSY;
+    }
+  }else if( eLock==SQLITE_LOCK_EXCLUSIVE ){
+    for(pF=pInode->pFiles; pF; pF=pF->pNext){
+      if( pF!=p && pF->eLock>=SQLITE_LOCK_SHARED ){
+        eLock = SQLITE_LOCK_PENDING;
+        rc = SQLITE_BUSY;
+      }
+    }
+  }
+  p->eLock = eLock;
+  return rc;
+}
+
+/*
+** Unlock an ssdsim-file.
+*/
+static int ssdsimUnlock(sqlite3_file *pFile, int eLock){
+  ssdsim_file *p = (ssdsim_file *)pFile;
+  if( p->eLock>eLock ) p->eLock = eLock;
+  return SQLITE_OK;
+}
+
+/*
+** Check if another file-handle holds a RESERVED lock on an ssdsim-file.
+*/
+static int ssdsimCheckReservedLock(sqlite3_file *pFile, int *pResOut){
+  ssdsim_file *p = (ssdsim_file *)pFile;
+  ssdsim_inode *pInode = p->pInode;
+  ssdsim_file *pF;
+  int rc = 0;
+  for(pF=pInode->pFiles; pF; pF=pF->pNext){
+    if( pF!=p && pF->eLock>=SQLITE_LOCK_RESERVED ){
+      rc = 1;
+      break;
+    }
+  }
+  *pResOut = rc;
+  return SQLITE_OK;
+}
+
+/*
+** File control method. For custom operations on an ssdsim-file.
+*/
+static int ssdsimFileControl(sqlite3_file *pFile, int op, void *pArg){
+  ssdsim_file *p = (ssdsim_file *)pFile;
+  ssdsim_inode *pInode = p->pInode;
+  switch( op ){
+    case SQLITE_FCNTL_LOCKSTATE: {
+      *(int*)pArg = p->eLock;
+      return SQLITE_OK;
+    }
+    case SQLITE_FCNTL_VFSNAME: {
+      *(char**)pArg = sqlite3_mprintf("ssdsim");
+      return SQLITE_OK;
+    }
+    case SQLITE_FCNTL_PRAGMA: {
+#if 0
+      const char *const* a = (const char*const*)pArg;
+      sqlite3_snprintf(sizeof(zBuf), zBuf, "PRAGMA,[%s,%s]",a[1],a[2]);
+      zOp = zBuf;
+#endif
+      break;
+    }
+    default: {
+      break;
+    }
+  }
+  return SQLITE_NOTFOUND;
+}
+
+/*
+** Return the sector-size in bytes for an ssdsim-file.
+*/
+static int ssdsimSectorSize(sqlite3_file *pFile){
+  return g.szPage;
+}
+
+/*
+** Return the device characteristic flags supported by an ssdsim-file.
+*/
+static int ssdsimDeviceCharacteristics(sqlite3_file *pFile){
+  return 
+     SQLITE_IOCAP_ATOMIC |
+     SQLITE_IOCAP_POWERSAFE_OVERWRITE |
+     SQLITE_IOCAP_SAFE_APPEND |
+     SQLITE_IOCAP_SEQUENTIAL |
+     SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN |
+     0;
+}
+
+/*
+** Shared-memory operations.
+*/
+static int ssdsimShmLock(sqlite3_file *pFile, int ofst, int n, int flags){
+  ssdsim_file *p = (ssdsim_file *)pFile;
+  ssdsim_inode *pInode = p->pInode;
+  ssdsim_file *pF;
+  unsigned int lockMask = 0;
+
+  /* Constraints on the SQLite core: */
+  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 );
+
+  /* Mask of bits involved in this lock */
+  lockMask = (1<<(ofst+n)) - (1<<ofst);
+
+  /* The unlock case */
+  if( flags & SQLITE_SHM_UNLOCK ){
+    p->shmWriteLock &= ~lockMask;
+    p->shmReadLock &= ~lockMask;
+    return SQLITE_OK;
+  }
+
+  /* The shared-lock case */
+  if( flags & SQLITE_SHM_SHARED ){
+    /* Disallow if any sibling (including ourself) holds an exclusive lock */
+    for(pF=pInode->pFiles; pF; pF=pF->pNext){
+      if( pF->shmWriteLock & lockMask ){
+        return SQLITE_BUSY;
+      }
+    }
+    p->shmReadLock |= lockMask;
+    return SQLITE_OK;
+  }
+
+  /* The rest of this procedure is the exclusive lock case */
+  assert( flags & SQLITE_SHM_EXCLUSIVE );
+
+  /* Disallow an exclusive if any kind of lock is held by any other */
+  for(pF=pInode->pFiles; pF; pF=pF->pNext){
+    if( pF==p ) continue;
+    if( (pF->shmWriteLock & lockMask)!=0 ){
+      return SQLITE_BUSY;
+    }
+    if( (pF->shmReadLock & lockMask)!=0 ){
+      return SQLITE_BUSY;
+    }
+  }
+  p->shmWriteLock |= lockMask;
+  return SQLITE_OK;
+}
+static int ssdsimShmMap(
+  sqlite3_file *pFile, 
+  int iRegion, 
+  int szRegion, 
+  int isWrite, 
+  void volatile **pp
+){
+  ssdsim_file *p = (ssdsim_file *)pFile;
+  ssdsim_inode *pInode = p->pInode;
+  char **apShm;
+  int i;
+  if( p->shmOpen==0 ){
+    p->shmOpen = 1;
+    p->shmReadLock = 0;
+    p->shmWriteLock = 0;
+  }
+  if( pInode->nShmRegion<=iRegion ){
+    if( isWrite==0 ){
+      *pp = 0;
+      return SQLITE_OK;
+    }
+    apShm = sqlite3_realloc(pInode->apShm, 
+                            (iRegion+1)*sizeof(pInode->apShm[0]));
+    if( apShm==0 ) return SQLITE_NOMEM;
+    pInode->apShm = apShm;
+    for(i=pInode->nShmRegion; i<=iRegion; i++){
+      apShm[i] = sqlite3_malloc(szRegion);
+      if( apShm[i]==0 ) return SQLITE_NOMEM;
+      memset(apShm[i], 0, szRegion);
+      pInode->nShmRegion = i+1;
+    }
+    pInode->szShmRegion = szRegion;
+  }
+  *pp = pInode->apShm[iRegion];
+  return SQLITE_OK;
+}
+static void ssdsimShmBarrier(sqlite3_file *pFile){
+  /* noop */
+}
+static int ssdsimShmUnmap(sqlite3_file *pFile, int delFlag){
+  ssdsim_file *p = (ssdsim_file *)pFile;
+  ssdsim_inode *pInode = p->pInode;
+  if( p->shmOpen ){
+    ssdsim_file *pF;
+    unsigned char shmOpen = 0;
+    p->shmOpen = 0;
+    for(pF=pInode->pFiles; pF; pF=pF->pNext) shmOpen |= pF->shmOpen;
+    if( !shmOpen ){
+      int i;
+      for(i=0; i<pInode->nShmRegion; i++) sqlite3_free(pInode->apShm[i]);
+      sqlite3_free(pInode->apShm);
+      pInode->apShm = 0;
+      pInode->nShmRegion = 0;
+    }
+  }
+  return SQLITE_OK;
+}
+
+static const sqlite3_io_methods ssdsim_io_methods = {
+  /* iVersion               */ 2,
+  /* xClose                 */ ssdsimClose,
+  /* xRead                  */ ssdsimRead,
+  /* xWrite                 */ ssdsimWrite,
+  /* xTruncate              */ ssdsimTruncate,
+  /* xSync                  */ ssdsimSync,
+  /* xFileSize              */ ssdsimFileSize,
+  /* xLock                  */ ssdsimLock,
+  /* xUnlock                */ ssdsimUnlock,
+  /* xCheckReservedLock     */ ssdsimCheckReservedLock,
+  /* xFileControl           */ ssdsimFileControl,
+  /* xSectorSize            */ ssdsimSectorSize,
+  /* xDeviceCharacteristics */ ssdsimDeviceCharacteristics,
+  /* xShmMap                */ ssdsimShmMap,
+  /* xShmLock               */ ssdsimShmLock,
+  /* xShmBarrier            */ ssdsimShmBarrier,
+  /* xShmUnmap              */ ssdsimShmUnmap
+};
+
+/*
+** Find an inode given its name.
+*/
+static ssdsim_inode *ssdsimFindInode(const char *zName){
+  ssdsim_inode *pInode;
+  for(pInode=g.pInode; pInode; pInode=pInode->pNext){
+    if( strcmp(pInode->zPath, zName)==0 ) break;
+  }
+  return pInode;
+}
+
+/*
+** Open an ssdsim file handle.
+*/
+static int ssdsimOpen(
+  sqlite3_vfs *pVfs,
+  const char *zName,
+  sqlite3_file *pFile,
+  int flags,
+  int *pOutFlags
+){
+  int rc;
+  ssdsim_file *p = (ssdsim_file *)pFile;
+  ssdsim_inode *pInode = ssdsimFindInode(zName);
+  if( pInode==0 ){
+    int n = (int)strlen(zName);
+    pInode = sqlite3_malloc( sizeof(*pInode) + n + 1 );
+    if( pInode==0 ) return SQLITE_NOMEM;
+    memset(pInode, 0, sizeof(*pInode));
+    pInode->pNext = g.pInode;
+    g.pInode = pInode;
+    pInode->zPath = (char*)&pInode[1];
+    strcpy(pInode->zPath, zName);
+    pInode->len = 0;
+    pInode->aiPage = 0;
+    pInode->pFiles = 0;
+    pInode->inodeFlags = 0;
+    pInode->nShmRegion = 0;
+    pInode->szShmRegion = 0;
+    pInode->apShm = 0;
+    if( flags & SQLITE_OPEN_DELETEONCLOSE ){
+      pInode->inodeFlags |= SSDSIM_DELETEONCLOSE;
+    }
+  }
+  p->pInode = pInode;
+  p->pNext = pInode->pFiles;
+  pInode->pFiles = p;
+  p->eLock = 0;
+  p->shmOpen = 0;
+  p->shmReadLock = 0;
+  p->shmWriteLock = 0;
+  p->openFlags = flags;
+  p->base.pMethods = &ssdsim_io_methods;
+  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 ssdsimDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
+  ssdsim_inode *pInode = ssdsimFindInode(zPath);
+  if( pInode==0 ) return SQLITE_NOTFOUND;
+  ssdsimDeleteInode(pInode);
+  return SQLITE_OK;
+}
+
+/*
+** Test for access permissions. Return true if the requested permission
+** is available, or false otherwise.
+*/
+static int ssdsimAccess(
+  sqlite3_vfs *pVfs, 
+  const char *zPath, 
+  int flags, 
+  int *pResOut
+){
+  ssdsim_inode *pInode = ssdsimFindInode(zPath);
+  *pResOut = pInode!=0;
+  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 (DEVSYM_MAX_PATHNAME+1) bytes.
+*/
+static int ssdsimFullPathname(
+  sqlite3_vfs *pVfs, 
+  const char *zPath, 
+  int nOut, 
+  char *zOut
+){
+  while( zPath[0]=='/' ) zPath++;
+  sqlite3_snprintf(nOut, zOut, "/%s", zPath);
+  return SQLITE_OK;
+}
+
+/*
+** Open the dynamic library located at zPath and return a handle.
+*/
+static void *ssdsimDlOpen(sqlite3_vfs *pVfs, const char *zPath){
+  return 0;
+}
+
+/*
+** Populate the buffer zErrMsg (size nByte bytes) with a human readable
+** utf-8 string describing the most recent error encountered associated 
+** with dynamic libraries.
+*/
+static void ssdsimDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
+  sqlite3_snprintf(nByte, zErrMsg, "not supported by this VFS");
+}
+
+/*
+** Return a pointer to the symbol zSymbol in the dynamic library pHandle.
+*/
+static void (*ssdsimDlSym(sqlite3_vfs *pVfs,void *p,const char *zSym))(void){
+  return 0;
+}
+
+/*
+** Close the dynamic library handle pHandle.
+*/
+static void ssdsimDlClose(sqlite3_vfs *pVfs, void *pHandle){
+}
+
+/*
+** Populate the buffer pointed to by zBufOut with nByte bytes of 
+** random data.
+*/
+static int ssdsimRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
+  return g.pBase->xRandomness(g.pBase, nByte, zBufOut);
+}
+
+/*
+** Sleep for nMicro microseconds. Return the number of microseconds 
+** actually slept.
+*/
+static int ssdsimSleep(sqlite3_vfs *pVfs, int nMicro){
+  return g.pBase->xSleep(g.pBase, nMicro);
+}
+
+/*
+** Return the current time as a Julian Day number in *pTimeOut.
+*/
+static int ssdsimCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
+  return g.pBase->xCurrentTime(g.pBase, pTimeOut);
+}
+static int ssdsimCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){
+  return g.pBase->xCurrentTimeInt64(g.pBase, pTimeOut);
+}
+
+/*
+** Return th3 emost recent error code and message
+*/
+static int ssdsimGetLastError(sqlite3_vfs *pVfs, int iErr, char *zErr){
+  return SQLITE_OK;
+}
+
+/*
+** Override system calls.
+*/
+static int ssdsimSetSystemCall(
+  sqlite3_vfs *pVfs,
+  const char *zName,
+  sqlite3_syscall_ptr pFunc
+){
+  return SQLITE_NOTFOUND;
+}
+static sqlite3_syscall_ptr ssdsimGetSystemCall(
+  sqlite3_vfs *pVfs,
+  const char *zName
+){
+  return 0;
+}
+static const char *ssdsimNextSystemCall(sqlite3_vfs *pVfs, const char *zName){
+  return 0;
+}
+
+static sqlite3_vfs ssdsim_vfs = {
+  /* iVersion          */ 3,
+  /* szOsFile          */ sizeof(ssdsim_file),
+  /* mxPathname        */ 1024,
+  /* pNext             */ 0,
+  /* zName             */ "ssdsim",
+  /* pAppData          */ 0,
+  /* xOpen             */ ssdsimOpen,
+  /* xDelete           */ ssdsimDelete,
+  /* xAccess           */ ssdsimAccess,
+  /* xFullPathname     */ ssdsimFullPathname,
+  /* xDlOpen           */ ssdsimDlOpen,
+  /* xDlError          */ ssdsimDlError,
+  /* xDlSym            */ ssdsimDlSym,
+  /* xDlClose          */ ssdsimDlClose,
+  /* xRandomness       */ ssdsimRandomness,
+  /* xSleep            */ ssdsimSleep,
+  /* xCurrentTime      */ ssdsimCurrentTime,
+  /* xGetLastError     */ ssdsimGetLastError,
+  /* xCurrentTimeInt64 */ ssdsimCurrentTimeInt64,
+  /* xSetSystemCall    */ ssdsimSetSystemCall,
+  /* xGetSystemCall    */ ssdsimGetSystemCall,
+  /* xNextSystemCall   */ ssdsimNextSystemCall
+};
+
+/*
+** Clients invoke this routine to register the SSD simulator
+*/
+int ssdsim_register(
+   const char *zBaseName,          /* Name of the underlying VFS */
+   const char *zParams,            /* Configuration parameter */
+   int makeDefault                 /* True to make the new VFS the default */
+){
+  sqlite3_vfs *pNew;
+  sqlite3_vfs *pRoot;
+
+  if( g.pBase ) return SQLITE_ERROR;
+  g.pBase = sqlite3_vfs_find(zBaseName);
+  if( g.pBase==0 ) return SQLITE_NOTFOUND;
+  return sqlite3_vfs_register(&ssdsim_vfs, makeDefault);
+}
+
+/*
+** Clients invoke this routine to get SSD simulator write-amplification
+** statistics.
+*/
+void ssdsim_report(FILE *pOut, int reportNum){
+  fprintf(pOut, "host page writes...... %9d\n", g.nHostWrite);
+  fprintf(pOut, "NAND page writes...... %9d\n", g.nNANDWrite);
+  if( g.nHostWrite>0 ){
+    fprintf(pOut, "write amplification... %11.2f\n",
+            (double)g.nNANDWrite/(double)g.nHostWrite);
+  }
+}