/ Artifact Content
Login

Artifact b4b2ff870e31cdecaf5716c4373a7d9ffc1d3762:


/*
** 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.  Return non-zero if there is an error.
*/
static int ssdsimInit(void){
  int nPage;
  if( g.nPage ) return 0;
  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 1; }
  memset(g.apPage, 0, sizeof(g.apPage[0])*g.nPage);
  g.aDealloc = sqlite3_malloc( sizeof(g.aDealloc[0])*g.nPage );
  if( g.aDealloc==0 ){ ssdsimShutdown(); return 1; }
  g.nDealloc = 0;
  g.mxAlloc = 0;
  g.nHostWrite = 0;
  g.nNANDWrite = 0;
  return 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               */ 3,
  /* 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;
  char zTempname[100];

  if( ssdsimInit() ) return SQLITE_CANTOPEN;
  if( zName==0 ){
    sqlite3_uint64 r;
    sqlite3_randomness(sizeof(r), &r);
    sqlite3_snprintf(sizeof(zTempname), zTempname, "/tmp%llx", r);
    zName = zTempname;
  }
  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;

  if( ssdsimInit() ) return SQLITE_CANTOPEN;
  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;
  if( ssdsimInit() ) return SQLITE_CANTOPEN;
  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);
  }
}