xref: /sqlite-3.40.0/src/test_vfs.c (revision 3d403c71)
1 /*
2 ** 2010 May 05
3 **
4 ** The author disclaims copyright to this source code.  In place of
5 ** a legal notice, here is a blessing:
6 **
7 **    May you do good and not evil.
8 **    May you find forgiveness for yourself and forgive others.
9 **    May you share freely, never taking more than you give.
10 **
11 ******************************************************************************
12 **
13 ** This file contains the implementation of the Tcl [testvfs] command,
14 ** used to create SQLite VFS implementations with various properties and
15 ** instrumentation to support testing SQLite.
16 **
17 **   testvfs VFSNAME ?OPTIONS?
18 **
19 ** Available options are:
20 **
21 **   -noshm      BOOLEAN        (True to omit shm methods. Default false)
22 **   -default    BOOLEAN        (True to make the vfs default. Default false)
23 **   -szosfile   INTEGER        (Value for sqlite3_vfs.szOsFile)
24 **   -mxpathname INTEGER        (Value for sqlite3_vfs.mxPathname)
25 **   -iversion   INTEGER        (Value for sqlite3_vfs.iVersion)
26 */
27 #if SQLITE_TEST          /* This file is used for testing only */
28 
29 #include "sqlite3.h"
30 #include "sqliteInt.h"
31 
32 typedef struct Testvfs Testvfs;
33 typedef struct TestvfsShm TestvfsShm;
34 typedef struct TestvfsBuffer TestvfsBuffer;
35 typedef struct TestvfsFile TestvfsFile;
36 typedef struct TestvfsFd TestvfsFd;
37 
38 /*
39 ** An open file handle.
40 */
41 struct TestvfsFile {
42   sqlite3_file base;              /* Base class.  Must be first */
43   TestvfsFd *pFd;                 /* File data */
44 };
45 #define tvfsGetFd(pFile) (((TestvfsFile *)pFile)->pFd)
46 
47 struct TestvfsFd {
48   sqlite3_vfs *pVfs;              /* The VFS */
49   const char *zFilename;          /* Filename as passed to xOpen() */
50   sqlite3_file *pReal;            /* The real, underlying file descriptor */
51   Tcl_Obj *pShmId;                /* Shared memory id for Tcl callbacks */
52 
53   TestvfsBuffer *pShm;            /* Shared memory buffer */
54   u32 excllock;                   /* Mask of exclusive locks */
55   u32 sharedlock;                 /* Mask of shared locks */
56   TestvfsFd *pNext;               /* Next handle opened on the same file */
57 };
58 
59 
60 #define FAULT_INJECT_NONE       0
61 #define FAULT_INJECT_TRANSIENT  1
62 #define FAULT_INJECT_PERSISTENT 2
63 
64 typedef struct TestFaultInject TestFaultInject;
65 struct TestFaultInject {
66   int iCnt;                       /* Remaining calls before fault injection */
67   int eFault;                     /* A FAULT_INJECT_* value */
68   int nFail;                      /* Number of faults injected */
69 };
70 
71 /*
72 ** An instance of this structure is allocated for each VFS created. The
73 ** sqlite3_vfs.pAppData field of the VFS structure registered with SQLite
74 ** is set to point to it.
75 */
76 struct Testvfs {
77   char *zName;                    /* Name of this VFS */
78   sqlite3_vfs *pParent;           /* The VFS to use for file IO */
79   sqlite3_vfs *pVfs;              /* The testvfs registered with SQLite */
80   Tcl_Interp *interp;             /* Interpreter to run script in */
81   Tcl_Obj *pScript;               /* Script to execute */
82   TestvfsBuffer *pBuffer;         /* List of shared buffers */
83   int isNoshm;
84 
85   int mask;                       /* Mask controlling [script] and [ioerr] */
86 
87   TestFaultInject ioerr_err;
88   TestFaultInject full_err;
89   TestFaultInject cantopen_err;
90 
91 #if 0
92   int iIoerrCnt;
93   int ioerr;
94   int nIoerrFail;
95   int iFullCnt;
96   int fullerr;
97   int nFullFail;
98 #endif
99 
100   int iDevchar;
101   int iSectorsize;
102 };
103 
104 /*
105 ** The Testvfs.mask variable is set to a combination of the following.
106 ** If a bit is clear in Testvfs.mask, then calls made by SQLite to the
107 ** corresponding VFS method is ignored for purposes of:
108 **
109 **   + Simulating IO errors, and
110 **   + Invoking the Tcl callback script.
111 */
112 #define TESTVFS_SHMOPEN_MASK      0x00000001
113 #define TESTVFS_SHMLOCK_MASK      0x00000010
114 #define TESTVFS_SHMMAP_MASK       0x00000020
115 #define TESTVFS_SHMBARRIER_MASK   0x00000040
116 #define TESTVFS_SHMCLOSE_MASK     0x00000080
117 
118 #define TESTVFS_OPEN_MASK         0x00000100
119 #define TESTVFS_SYNC_MASK         0x00000200
120 #define TESTVFS_DELETE_MASK       0x00000400
121 #define TESTVFS_CLOSE_MASK        0x00000800
122 #define TESTVFS_WRITE_MASK        0x00001000
123 #define TESTVFS_TRUNCATE_MASK     0x00002000
124 #define TESTVFS_ACCESS_MASK       0x00004000
125 #define TESTVFS_FULLPATHNAME_MASK 0x00008000
126 #define TESTVFS_READ_MASK         0x00010000
127 
128 #define TESTVFS_ALL_MASK          0x0001FFFF
129 
130 
131 #define TESTVFS_MAX_PAGES 1024
132 
133 /*
134 ** A shared-memory buffer. There is one of these objects for each shared
135 ** memory region opened by clients. If two clients open the same file,
136 ** there are two TestvfsFile structures but only one TestvfsBuffer structure.
137 */
138 struct TestvfsBuffer {
139   char *zFile;                    /* Associated file name */
140   int pgsz;                       /* Page size */
141   u8 *aPage[TESTVFS_MAX_PAGES];   /* Array of ckalloc'd pages */
142   TestvfsFd *pFile;               /* List of open handles */
143   TestvfsBuffer *pNext;           /* Next in linked list of all buffers */
144 };
145 
146 
147 #define PARENTVFS(x) (((Testvfs *)((x)->pAppData))->pParent)
148 
149 #define TESTVFS_MAX_ARGS 12
150 
151 
152 /*
153 ** Method declarations for TestvfsFile.
154 */
155 static int tvfsClose(sqlite3_file*);
156 static int tvfsRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
157 static int tvfsWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst);
158 static int tvfsTruncate(sqlite3_file*, sqlite3_int64 size);
159 static int tvfsSync(sqlite3_file*, int flags);
160 static int tvfsFileSize(sqlite3_file*, sqlite3_int64 *pSize);
161 static int tvfsLock(sqlite3_file*, int);
162 static int tvfsUnlock(sqlite3_file*, int);
163 static int tvfsCheckReservedLock(sqlite3_file*, int *);
164 static int tvfsFileControl(sqlite3_file*, int op, void *pArg);
165 static int tvfsSectorSize(sqlite3_file*);
166 static int tvfsDeviceCharacteristics(sqlite3_file*);
167 
168 /*
169 ** Method declarations for tvfs_vfs.
170 */
171 static int tvfsOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
172 static int tvfsDelete(sqlite3_vfs*, const char *zName, int syncDir);
173 static int tvfsAccess(sqlite3_vfs*, const char *zName, int flags, int *);
174 static int tvfsFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
175 #ifndef SQLITE_OMIT_LOAD_EXTENSION
176 static void *tvfsDlOpen(sqlite3_vfs*, const char *zFilename);
177 static void tvfsDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
178 static void (*tvfsDlSym(sqlite3_vfs*,void*, const char *zSymbol))(void);
179 static void tvfsDlClose(sqlite3_vfs*, void*);
180 #endif /* SQLITE_OMIT_LOAD_EXTENSION */
181 static int tvfsRandomness(sqlite3_vfs*, int nByte, char *zOut);
182 static int tvfsSleep(sqlite3_vfs*, int microseconds);
183 static int tvfsCurrentTime(sqlite3_vfs*, double*);
184 
185 static int tvfsShmOpen(sqlite3_file*);
186 static int tvfsShmLock(sqlite3_file*, int , int, int);
187 static int tvfsShmMap(sqlite3_file*,int,int,int, void volatile **);
188 static void tvfsShmBarrier(sqlite3_file*);
189 static int tvfsShmUnmap(sqlite3_file*, int);
190 
191 static sqlite3_io_methods tvfs_io_methods = {
192   2,                              /* iVersion */
193   tvfsClose,                      /* xClose */
194   tvfsRead,                       /* xRead */
195   tvfsWrite,                      /* xWrite */
196   tvfsTruncate,                   /* xTruncate */
197   tvfsSync,                       /* xSync */
198   tvfsFileSize,                   /* xFileSize */
199   tvfsLock,                       /* xLock */
200   tvfsUnlock,                     /* xUnlock */
201   tvfsCheckReservedLock,          /* xCheckReservedLock */
202   tvfsFileControl,                /* xFileControl */
203   tvfsSectorSize,                 /* xSectorSize */
204   tvfsDeviceCharacteristics,      /* xDeviceCharacteristics */
205   tvfsShmMap,                     /* xShmMap */
206   tvfsShmLock,                    /* xShmLock */
207   tvfsShmBarrier,                 /* xShmBarrier */
208   tvfsShmUnmap                    /* xShmUnmap */
209 };
210 
211 static int tvfsResultCode(Testvfs *p, int *pRc){
212   struct errcode {
213     int eCode;
214     const char *zCode;
215   } aCode[] = {
216     { SQLITE_OK,     "SQLITE_OK"     },
217     { SQLITE_ERROR,  "SQLITE_ERROR"  },
218     { SQLITE_IOERR,  "SQLITE_IOERR"  },
219     { SQLITE_LOCKED, "SQLITE_LOCKED" },
220     { SQLITE_BUSY,   "SQLITE_BUSY"   },
221   };
222 
223   const char *z;
224   int i;
225 
226   z = Tcl_GetStringResult(p->interp);
227   for(i=0; i<ArraySize(aCode); i++){
228     if( 0==strcmp(z, aCode[i].zCode) ){
229       *pRc = aCode[i].eCode;
230       return 1;
231     }
232   }
233 
234   return 0;
235 }
236 
237 static int tvfsInjectFault(TestFaultInject *p){
238   int ret = 0;
239   if( p->eFault ){
240     p->iCnt--;
241     if( p->iCnt==0 || (p->iCnt<0 && p->eFault==FAULT_INJECT_PERSISTENT ) ){
242       ret = 1;
243       p->nFail++;
244     }
245   }
246   return ret;
247 }
248 
249 
250 static int tvfsInjectIoerr(Testvfs *p){
251   return tvfsInjectFault(&p->ioerr_err);
252 }
253 
254 static int tvfsInjectFullerr(Testvfs *p){
255   return tvfsInjectFault(&p->full_err);
256 }
257 static int tvfsInjectCantopenerr(Testvfs *p){
258   return tvfsInjectFault(&p->cantopen_err);
259 }
260 
261 
262 static void tvfsExecTcl(
263   Testvfs *p,
264   const char *zMethod,
265   Tcl_Obj *arg1,
266   Tcl_Obj *arg2,
267   Tcl_Obj *arg3
268 ){
269   int rc;                         /* Return code from Tcl_EvalObj() */
270   Tcl_Obj *pEval;
271   assert( p->pScript );
272 
273   assert( zMethod );
274   assert( p );
275   assert( arg2==0 || arg1!=0 );
276   assert( arg3==0 || arg2!=0 );
277 
278   pEval = Tcl_DuplicateObj(p->pScript);
279   Tcl_IncrRefCount(p->pScript);
280   Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zMethod, -1));
281   if( arg1 ) Tcl_ListObjAppendElement(p->interp, pEval, arg1);
282   if( arg2 ) Tcl_ListObjAppendElement(p->interp, pEval, arg2);
283   if( arg3 ) Tcl_ListObjAppendElement(p->interp, pEval, arg3);
284 
285   rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
286   if( rc!=TCL_OK ){
287     Tcl_BackgroundError(p->interp);
288     Tcl_ResetResult(p->interp);
289   }
290 }
291 
292 
293 /*
294 ** Close an tvfs-file.
295 */
296 static int tvfsClose(sqlite3_file *pFile){
297   int rc;
298   TestvfsFile *pTestfile = (TestvfsFile *)pFile;
299   TestvfsFd *pFd = pTestfile->pFd;
300   Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
301 
302   if( p->pScript && p->mask&TESTVFS_CLOSE_MASK ){
303     tvfsExecTcl(p, "xClose",
304         Tcl_NewStringObj(pFd->zFilename, -1), pFd->pShmId, 0
305     );
306   }
307 
308   if( pFd->pShmId ){
309     Tcl_DecrRefCount(pFd->pShmId);
310     pFd->pShmId = 0;
311   }
312   if( pFile->pMethods ){
313     ckfree((char *)pFile->pMethods);
314   }
315   rc = sqlite3OsClose(pFd->pReal);
316   ckfree((char *)pFd);
317   pTestfile->pFd = 0;
318   return rc;
319 }
320 
321 /*
322 ** Read data from an tvfs-file.
323 */
324 static int tvfsRead(
325   sqlite3_file *pFile,
326   void *zBuf,
327   int iAmt,
328   sqlite_int64 iOfst
329 ){
330   int rc = SQLITE_OK;
331   TestvfsFd *pFd = tvfsGetFd(pFile);
332   Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
333   if( p->pScript && p->mask&TESTVFS_READ_MASK ){
334     tvfsExecTcl(p, "xRead",
335         Tcl_NewStringObj(pFd->zFilename, -1), pFd->pShmId, 0
336     );
337     tvfsResultCode(p, &rc);
338   }
339   if( rc==SQLITE_OK && p->mask&TESTVFS_READ_MASK && tvfsInjectIoerr(p) ){
340     rc = SQLITE_IOERR;
341   }
342   if( rc==SQLITE_OK ){
343     rc = sqlite3OsRead(pFd->pReal, zBuf, iAmt, iOfst);
344   }
345   return rc;
346 }
347 
348 /*
349 ** Write data to an tvfs-file.
350 */
351 static int tvfsWrite(
352   sqlite3_file *pFile,
353   const void *zBuf,
354   int iAmt,
355   sqlite_int64 iOfst
356 ){
357   int rc = SQLITE_OK;
358   TestvfsFd *pFd = tvfsGetFd(pFile);
359   Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
360 
361   if( p->pScript && p->mask&TESTVFS_WRITE_MASK ){
362     tvfsExecTcl(p, "xWrite",
363         Tcl_NewStringObj(pFd->zFilename, -1), pFd->pShmId, 0
364     );
365     tvfsResultCode(p, &rc);
366   }
367 
368   if( rc==SQLITE_OK && tvfsInjectFullerr(p) ){
369     rc = SQLITE_FULL;
370   }
371   if( rc==SQLITE_OK && p->mask&TESTVFS_WRITE_MASK && tvfsInjectIoerr(p) ){
372     rc = SQLITE_IOERR;
373   }
374 
375   if( rc==SQLITE_OK ){
376     rc = sqlite3OsWrite(pFd->pReal, zBuf, iAmt, iOfst);
377   }
378   return rc;
379 }
380 
381 /*
382 ** Truncate an tvfs-file.
383 */
384 static int tvfsTruncate(sqlite3_file *pFile, sqlite_int64 size){
385   int rc = SQLITE_OK;
386   TestvfsFd *pFd = tvfsGetFd(pFile);
387   Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
388 
389   if( p->pScript && p->mask&TESTVFS_TRUNCATE_MASK ){
390     tvfsExecTcl(p, "xTruncate",
391         Tcl_NewStringObj(pFd->zFilename, -1), pFd->pShmId, 0
392     );
393     tvfsResultCode(p, &rc);
394   }
395 
396   if( rc==SQLITE_OK ){
397     rc = sqlite3OsTruncate(pFd->pReal, size);
398   }
399   return rc;
400 }
401 
402 /*
403 ** Sync an tvfs-file.
404 */
405 static int tvfsSync(sqlite3_file *pFile, int flags){
406   int rc = SQLITE_OK;
407   TestvfsFd *pFd = tvfsGetFd(pFile);
408   Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
409 
410   if( p->pScript && p->mask&TESTVFS_SYNC_MASK ){
411     char *zFlags;
412 
413     switch( flags ){
414       case SQLITE_SYNC_NORMAL:
415         zFlags = "normal";
416         break;
417       case SQLITE_SYNC_FULL:
418         zFlags = "full";
419         break;
420       case SQLITE_SYNC_NORMAL|SQLITE_SYNC_DATAONLY:
421         zFlags = "normal|dataonly";
422         break;
423       case SQLITE_SYNC_FULL|SQLITE_SYNC_DATAONLY:
424         zFlags = "full|dataonly";
425         break;
426       default:
427         assert(0);
428     }
429 
430     tvfsExecTcl(p, "xSync",
431         Tcl_NewStringObj(pFd->zFilename, -1), pFd->pShmId,
432         Tcl_NewStringObj(zFlags, -1)
433     );
434     tvfsResultCode(p, &rc);
435   }
436 
437   if( rc==SQLITE_OK && tvfsInjectFullerr(p) ) rc = SQLITE_FULL;
438 
439   if( rc==SQLITE_OK ){
440     rc = sqlite3OsSync(pFd->pReal, flags);
441   }
442 
443   return rc;
444 }
445 
446 /*
447 ** Return the current file-size of an tvfs-file.
448 */
449 static int tvfsFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
450   TestvfsFd *p = tvfsGetFd(pFile);
451   return sqlite3OsFileSize(p->pReal, pSize);
452 }
453 
454 /*
455 ** Lock an tvfs-file.
456 */
457 static int tvfsLock(sqlite3_file *pFile, int eLock){
458   TestvfsFd *p = tvfsGetFd(pFile);
459   return sqlite3OsLock(p->pReal, eLock);
460 }
461 
462 /*
463 ** Unlock an tvfs-file.
464 */
465 static int tvfsUnlock(sqlite3_file *pFile, int eLock){
466   TestvfsFd *p = tvfsGetFd(pFile);
467   return sqlite3OsUnlock(p->pReal, eLock);
468 }
469 
470 /*
471 ** Check if another file-handle holds a RESERVED lock on an tvfs-file.
472 */
473 static int tvfsCheckReservedLock(sqlite3_file *pFile, int *pResOut){
474   TestvfsFd *p = tvfsGetFd(pFile);
475   return sqlite3OsCheckReservedLock(p->pReal, pResOut);
476 }
477 
478 /*
479 ** File control method. For custom operations on an tvfs-file.
480 */
481 static int tvfsFileControl(sqlite3_file *pFile, int op, void *pArg){
482   TestvfsFd *p = tvfsGetFd(pFile);
483   if( op==SQLITE_FCNTL_PRAGMA ){
484     char **argv = (char**)pArg;
485     if( sqlite3_stricmp(argv[1],"error")==0 ){
486       int rc = SQLITE_ERROR;
487       if( argv[2] ){
488         const char *z = argv[2];
489         int x = atoi(z);
490         if( x ){
491           rc = x;
492           while( sqlite3Isdigit(z[0]) ){ z++; }
493           while( sqlite3Isspace(z[0]) ){ z++; }
494         }
495         if( z[0] ) argv[0] = sqlite3_mprintf("%s", z);
496       }
497       return rc;
498     }
499     if( sqlite3_stricmp(argv[1], "filename")==0 ){
500       argv[0] = sqlite3_mprintf("%s", p->zFilename);
501       return SQLITE_OK;
502     }
503   }
504   return sqlite3OsFileControl(p->pReal, op, pArg);
505 }
506 
507 /*
508 ** Return the sector-size in bytes for an tvfs-file.
509 */
510 static int tvfsSectorSize(sqlite3_file *pFile){
511   TestvfsFd *pFd = tvfsGetFd(pFile);
512   Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
513   if( p->iSectorsize>=0 ){
514     return p->iSectorsize;
515   }
516   return sqlite3OsSectorSize(pFd->pReal);
517 }
518 
519 /*
520 ** Return the device characteristic flags supported by an tvfs-file.
521 */
522 static int tvfsDeviceCharacteristics(sqlite3_file *pFile){
523   TestvfsFd *pFd = tvfsGetFd(pFile);
524   Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
525   if( p->iDevchar>=0 ){
526     return p->iDevchar;
527   }
528   return sqlite3OsDeviceCharacteristics(pFd->pReal);
529 }
530 
531 /*
532 ** Open an tvfs file handle.
533 */
534 static int tvfsOpen(
535   sqlite3_vfs *pVfs,
536   const char *zName,
537   sqlite3_file *pFile,
538   int flags,
539   int *pOutFlags
540 ){
541   int rc;
542   TestvfsFile *pTestfile = (TestvfsFile *)pFile;
543   TestvfsFd *pFd;
544   Tcl_Obj *pId = 0;
545   Testvfs *p = (Testvfs *)pVfs->pAppData;
546 
547   pFd = (TestvfsFd *)ckalloc(sizeof(TestvfsFd) + PARENTVFS(pVfs)->szOsFile);
548   memset(pFd, 0, sizeof(TestvfsFd) + PARENTVFS(pVfs)->szOsFile);
549   pFd->pShm = 0;
550   pFd->pShmId = 0;
551   pFd->zFilename = zName;
552   pFd->pVfs = pVfs;
553   pFd->pReal = (sqlite3_file *)&pFd[1];
554   memset(pTestfile, 0, sizeof(TestvfsFile));
555   pTestfile->pFd = pFd;
556 
557   /* Evaluate the Tcl script:
558   **
559   **   SCRIPT xOpen FILENAME KEY-VALUE-ARGS
560   **
561   ** If the script returns an SQLite error code other than SQLITE_OK, an
562   ** error is returned to the caller. If it returns SQLITE_OK, the new
563   ** connection is named "anon". Otherwise, the value returned by the
564   ** script is used as the connection name.
565   */
566   Tcl_ResetResult(p->interp);
567   if( p->pScript && p->mask&TESTVFS_OPEN_MASK ){
568     Tcl_Obj *pArg = Tcl_NewObj();
569     Tcl_IncrRefCount(pArg);
570     if( flags&SQLITE_OPEN_MAIN_DB ){
571       const char *z = &zName[strlen(zName)+1];
572       while( *z ){
573         Tcl_ListObjAppendElement(0, pArg, Tcl_NewStringObj(z, -1));
574         z += strlen(z) + 1;
575         Tcl_ListObjAppendElement(0, pArg, Tcl_NewStringObj(z, -1));
576         z += strlen(z) + 1;
577       }
578     }
579     tvfsExecTcl(p, "xOpen", Tcl_NewStringObj(pFd->zFilename, -1), pArg, 0);
580     Tcl_DecrRefCount(pArg);
581     if( tvfsResultCode(p, &rc) ){
582       if( rc!=SQLITE_OK ) return rc;
583     }else{
584       pId = Tcl_GetObjResult(p->interp);
585     }
586   }
587 
588   if( (p->mask&TESTVFS_OPEN_MASK) &&  tvfsInjectIoerr(p) ) return SQLITE_IOERR;
589   if( tvfsInjectCantopenerr(p) ) return SQLITE_CANTOPEN;
590   if( tvfsInjectFullerr(p) ) return SQLITE_FULL;
591 
592   if( !pId ){
593     pId = Tcl_NewStringObj("anon", -1);
594   }
595   Tcl_IncrRefCount(pId);
596   pFd->pShmId = pId;
597   Tcl_ResetResult(p->interp);
598 
599   rc = sqlite3OsOpen(PARENTVFS(pVfs), zName, pFd->pReal, flags, pOutFlags);
600   if( pFd->pReal->pMethods ){
601     sqlite3_io_methods *pMethods;
602     int nByte;
603 
604     if( pVfs->iVersion>1 ){
605       nByte = sizeof(sqlite3_io_methods);
606     }else{
607       nByte = offsetof(sqlite3_io_methods, xShmMap);
608     }
609 
610     pMethods = (sqlite3_io_methods *)ckalloc(nByte);
611     memcpy(pMethods, &tvfs_io_methods, nByte);
612     pMethods->iVersion = pVfs->iVersion;
613     if( pVfs->iVersion>1 && ((Testvfs *)pVfs->pAppData)->isNoshm ){
614       pMethods->xShmUnmap = 0;
615       pMethods->xShmLock = 0;
616       pMethods->xShmBarrier = 0;
617       pMethods->xShmMap = 0;
618     }
619     pFile->pMethods = pMethods;
620   }
621 
622   return rc;
623 }
624 
625 /*
626 ** Delete the file located at zPath. If the dirSync argument is true,
627 ** ensure the file-system modifications are synced to disk before
628 ** returning.
629 */
630 static int tvfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
631   int rc = SQLITE_OK;
632   Testvfs *p = (Testvfs *)pVfs->pAppData;
633 
634   if( p->pScript && p->mask&TESTVFS_DELETE_MASK ){
635     tvfsExecTcl(p, "xDelete",
636         Tcl_NewStringObj(zPath, -1), Tcl_NewIntObj(dirSync), 0
637     );
638     tvfsResultCode(p, &rc);
639   }
640   if( rc==SQLITE_OK ){
641     rc = sqlite3OsDelete(PARENTVFS(pVfs), zPath, dirSync);
642   }
643   return rc;
644 }
645 
646 /*
647 ** Test for access permissions. Return true if the requested permission
648 ** is available, or false otherwise.
649 */
650 static int tvfsAccess(
651   sqlite3_vfs *pVfs,
652   const char *zPath,
653   int flags,
654   int *pResOut
655 ){
656   Testvfs *p = (Testvfs *)pVfs->pAppData;
657   if( p->pScript && p->mask&TESTVFS_ACCESS_MASK ){
658     int rc;
659     char *zArg = 0;
660     if( flags==SQLITE_ACCESS_EXISTS ) zArg = "SQLITE_ACCESS_EXISTS";
661     if( flags==SQLITE_ACCESS_READWRITE ) zArg = "SQLITE_ACCESS_READWRITE";
662     if( flags==SQLITE_ACCESS_READ ) zArg = "SQLITE_ACCESS_READ";
663     tvfsExecTcl(p, "xAccess",
664         Tcl_NewStringObj(zPath, -1), Tcl_NewStringObj(zArg, -1), 0
665     );
666     if( tvfsResultCode(p, &rc) ){
667       if( rc!=SQLITE_OK ) return rc;
668     }else{
669       Tcl_Interp *interp = p->interp;
670       if( TCL_OK==Tcl_GetBooleanFromObj(0, Tcl_GetObjResult(interp), pResOut) ){
671         return SQLITE_OK;
672       }
673     }
674   }
675   return sqlite3OsAccess(PARENTVFS(pVfs), zPath, flags, pResOut);
676 }
677 
678 /*
679 ** Populate buffer zOut with the full canonical pathname corresponding
680 ** to the pathname in zPath. zOut is guaranteed to point to a buffer
681 ** of at least (DEVSYM_MAX_PATHNAME+1) bytes.
682 */
683 static int tvfsFullPathname(
684   sqlite3_vfs *pVfs,
685   const char *zPath,
686   int nOut,
687   char *zOut
688 ){
689   Testvfs *p = (Testvfs *)pVfs->pAppData;
690   if( p->pScript && p->mask&TESTVFS_FULLPATHNAME_MASK ){
691     int rc;
692     tvfsExecTcl(p, "xFullPathname", Tcl_NewStringObj(zPath, -1), 0, 0);
693     if( tvfsResultCode(p, &rc) ){
694       if( rc!=SQLITE_OK ) return rc;
695     }
696   }
697   return sqlite3OsFullPathname(PARENTVFS(pVfs), zPath, nOut, zOut);
698 }
699 
700 #ifndef SQLITE_OMIT_LOAD_EXTENSION
701 /*
702 ** Open the dynamic library located at zPath and return a handle.
703 */
704 static void *tvfsDlOpen(sqlite3_vfs *pVfs, const char *zPath){
705   return sqlite3OsDlOpen(PARENTVFS(pVfs), zPath);
706 }
707 
708 /*
709 ** Populate the buffer zErrMsg (size nByte bytes) with a human readable
710 ** utf-8 string describing the most recent error encountered associated
711 ** with dynamic libraries.
712 */
713 static void tvfsDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
714   sqlite3OsDlError(PARENTVFS(pVfs), nByte, zErrMsg);
715 }
716 
717 /*
718 ** Return a pointer to the symbol zSymbol in the dynamic library pHandle.
719 */
720 static void (*tvfsDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){
721   return sqlite3OsDlSym(PARENTVFS(pVfs), p, zSym);
722 }
723 
724 /*
725 ** Close the dynamic library handle pHandle.
726 */
727 static void tvfsDlClose(sqlite3_vfs *pVfs, void *pHandle){
728   sqlite3OsDlClose(PARENTVFS(pVfs), pHandle);
729 }
730 #endif /* SQLITE_OMIT_LOAD_EXTENSION */
731 
732 /*
733 ** Populate the buffer pointed to by zBufOut with nByte bytes of
734 ** random data.
735 */
736 static int tvfsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
737   return sqlite3OsRandomness(PARENTVFS(pVfs), nByte, zBufOut);
738 }
739 
740 /*
741 ** Sleep for nMicro microseconds. Return the number of microseconds
742 ** actually slept.
743 */
744 static int tvfsSleep(sqlite3_vfs *pVfs, int nMicro){
745   return sqlite3OsSleep(PARENTVFS(pVfs), nMicro);
746 }
747 
748 /*
749 ** Return the current time as a Julian Day number in *pTimeOut.
750 */
751 static int tvfsCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
752   return PARENTVFS(pVfs)->xCurrentTime(PARENTVFS(pVfs), pTimeOut);
753 }
754 
755 static int tvfsShmOpen(sqlite3_file *pFile){
756   Testvfs *p;
757   int rc = SQLITE_OK;             /* Return code */
758   TestvfsBuffer *pBuffer;         /* Buffer to open connection to */
759   TestvfsFd *pFd;                 /* The testvfs file structure */
760 
761   pFd = tvfsGetFd(pFile);
762   p = (Testvfs *)pFd->pVfs->pAppData;
763   assert( pFd->pShmId && pFd->pShm==0 && pFd->pNext==0 );
764 
765   /* Evaluate the Tcl script:
766   **
767   **   SCRIPT xShmOpen FILENAME
768   */
769   Tcl_ResetResult(p->interp);
770   if( p->pScript && p->mask&TESTVFS_SHMOPEN_MASK ){
771     tvfsExecTcl(p, "xShmOpen", Tcl_NewStringObj(pFd->zFilename, -1), 0, 0);
772     if( tvfsResultCode(p, &rc) ){
773       if( rc!=SQLITE_OK ) return rc;
774     }
775   }
776 
777   assert( rc==SQLITE_OK );
778   if( p->mask&TESTVFS_SHMOPEN_MASK && tvfsInjectIoerr(p) ){
779     return SQLITE_IOERR;
780   }
781 
782   /* Search for a TestvfsBuffer. Create a new one if required. */
783   for(pBuffer=p->pBuffer; pBuffer; pBuffer=pBuffer->pNext){
784     if( 0==strcmp(pFd->zFilename, pBuffer->zFile) ) break;
785   }
786   if( !pBuffer ){
787     int nByte = sizeof(TestvfsBuffer) + (int)strlen(pFd->zFilename) + 1;
788     pBuffer = (TestvfsBuffer *)ckalloc(nByte);
789     memset(pBuffer, 0, nByte);
790     pBuffer->zFile = (char *)&pBuffer[1];
791     strcpy(pBuffer->zFile, pFd->zFilename);
792     pBuffer->pNext = p->pBuffer;
793     p->pBuffer = pBuffer;
794   }
795 
796   /* Connect the TestvfsBuffer to the new TestvfsShm handle and return. */
797   pFd->pNext = pBuffer->pFile;
798   pBuffer->pFile = pFd;
799   pFd->pShm = pBuffer;
800   return SQLITE_OK;
801 }
802 
803 static void tvfsAllocPage(TestvfsBuffer *p, int iPage, int pgsz){
804   assert( iPage<TESTVFS_MAX_PAGES );
805   if( p->aPage[iPage]==0 ){
806     p->aPage[iPage] = (u8 *)ckalloc(pgsz);
807     memset(p->aPage[iPage], 0, pgsz);
808     p->pgsz = pgsz;
809   }
810 }
811 
812 static int tvfsShmMap(
813   sqlite3_file *pFile,            /* Handle open on database file */
814   int iPage,                      /* Page to retrieve */
815   int pgsz,                       /* Size of pages */
816   int isWrite,                    /* True to extend file if necessary */
817   void volatile **pp              /* OUT: Mapped memory */
818 ){
819   int rc = SQLITE_OK;
820   TestvfsFd *pFd = tvfsGetFd(pFile);
821   Testvfs *p = (Testvfs *)(pFd->pVfs->pAppData);
822 
823   if( 0==pFd->pShm ){
824     rc = tvfsShmOpen(pFile);
825     if( rc!=SQLITE_OK ){
826       return rc;
827     }
828   }
829 
830   if( p->pScript && p->mask&TESTVFS_SHMMAP_MASK ){
831     Tcl_Obj *pArg = Tcl_NewObj();
832     Tcl_IncrRefCount(pArg);
833     Tcl_ListObjAppendElement(p->interp, pArg, Tcl_NewIntObj(iPage));
834     Tcl_ListObjAppendElement(p->interp, pArg, Tcl_NewIntObj(pgsz));
835     Tcl_ListObjAppendElement(p->interp, pArg, Tcl_NewIntObj(isWrite));
836     tvfsExecTcl(p, "xShmMap",
837         Tcl_NewStringObj(pFd->pShm->zFile, -1), pFd->pShmId, pArg
838     );
839     tvfsResultCode(p, &rc);
840     Tcl_DecrRefCount(pArg);
841   }
842   if( rc==SQLITE_OK && p->mask&TESTVFS_SHMMAP_MASK && tvfsInjectIoerr(p) ){
843     rc = SQLITE_IOERR;
844   }
845 
846   if( rc==SQLITE_OK && isWrite && !pFd->pShm->aPage[iPage] ){
847     tvfsAllocPage(pFd->pShm, iPage, pgsz);
848   }
849   *pp = (void volatile *)pFd->pShm->aPage[iPage];
850 
851   return rc;
852 }
853 
854 
855 static int tvfsShmLock(
856   sqlite3_file *pFile,
857   int ofst,
858   int n,
859   int flags
860 ){
861   int rc = SQLITE_OK;
862   TestvfsFd *pFd = tvfsGetFd(pFile);
863   Testvfs *p = (Testvfs *)(pFd->pVfs->pAppData);
864   int nLock;
865   char zLock[80];
866 
867   if( p->pScript && p->mask&TESTVFS_SHMLOCK_MASK ){
868     sqlite3_snprintf(sizeof(zLock), zLock, "%d %d", ofst, n);
869     nLock = (int)strlen(zLock);
870     if( flags & SQLITE_SHM_LOCK ){
871       strcpy(&zLock[nLock], " lock");
872     }else{
873       strcpy(&zLock[nLock], " unlock");
874     }
875     nLock += (int)strlen(&zLock[nLock]);
876     if( flags & SQLITE_SHM_SHARED ){
877       strcpy(&zLock[nLock], " shared");
878     }else{
879       strcpy(&zLock[nLock], " exclusive");
880     }
881     tvfsExecTcl(p, "xShmLock",
882         Tcl_NewStringObj(pFd->pShm->zFile, -1), pFd->pShmId,
883         Tcl_NewStringObj(zLock, -1)
884     );
885     tvfsResultCode(p, &rc);
886   }
887 
888   if( rc==SQLITE_OK && p->mask&TESTVFS_SHMLOCK_MASK && tvfsInjectIoerr(p) ){
889     rc = SQLITE_IOERR;
890   }
891 
892   if( rc==SQLITE_OK ){
893     int isLock = (flags & SQLITE_SHM_LOCK);
894     int isExcl = (flags & SQLITE_SHM_EXCLUSIVE);
895     u32 mask = (((1<<n)-1) << ofst);
896     if( isLock ){
897       TestvfsFd *p2;
898       for(p2=pFd->pShm->pFile; p2; p2=p2->pNext){
899         if( p2==pFd ) continue;
900         if( (p2->excllock&mask) || (isExcl && p2->sharedlock&mask) ){
901           rc = SQLITE_BUSY;
902           break;
903         }
904       }
905       if( rc==SQLITE_OK ){
906         if( isExcl )  pFd->excllock |= mask;
907         if( !isExcl ) pFd->sharedlock |= mask;
908       }
909     }else{
910       if( isExcl )  pFd->excllock &= (~mask);
911       if( !isExcl ) pFd->sharedlock &= (~mask);
912     }
913   }
914 
915   return rc;
916 }
917 
918 static void tvfsShmBarrier(sqlite3_file *pFile){
919   TestvfsFd *pFd = tvfsGetFd(pFile);
920   Testvfs *p = (Testvfs *)(pFd->pVfs->pAppData);
921 
922   if( p->pScript && p->mask&TESTVFS_SHMBARRIER_MASK ){
923     tvfsExecTcl(p, "xShmBarrier",
924         Tcl_NewStringObj(pFd->pShm->zFile, -1), pFd->pShmId, 0
925     );
926   }
927 }
928 
929 static int tvfsShmUnmap(
930   sqlite3_file *pFile,
931   int deleteFlag
932 ){
933   int rc = SQLITE_OK;
934   TestvfsFd *pFd = tvfsGetFd(pFile);
935   Testvfs *p = (Testvfs *)(pFd->pVfs->pAppData);
936   TestvfsBuffer *pBuffer = pFd->pShm;
937   TestvfsFd **ppFd;
938 
939   if( !pBuffer ) return SQLITE_OK;
940   assert( pFd->pShmId && pFd->pShm );
941 
942   if( p->pScript && p->mask&TESTVFS_SHMCLOSE_MASK ){
943     tvfsExecTcl(p, "xShmUnmap",
944         Tcl_NewStringObj(pFd->pShm->zFile, -1), pFd->pShmId, 0
945     );
946     tvfsResultCode(p, &rc);
947   }
948 
949   for(ppFd=&pBuffer->pFile; *ppFd!=pFd; ppFd=&((*ppFd)->pNext));
950   assert( (*ppFd)==pFd );
951   *ppFd = pFd->pNext;
952   pFd->pNext = 0;
953 
954   if( pBuffer->pFile==0 ){
955     int i;
956     TestvfsBuffer **pp;
957     for(pp=&p->pBuffer; *pp!=pBuffer; pp=&((*pp)->pNext));
958     *pp = (*pp)->pNext;
959     for(i=0; pBuffer->aPage[i]; i++){
960       ckfree((char *)pBuffer->aPage[i]);
961     }
962     ckfree((char *)pBuffer);
963   }
964   pFd->pShm = 0;
965 
966   return rc;
967 }
968 
969 static int testvfs_obj_cmd(
970   ClientData cd,
971   Tcl_Interp *interp,
972   int objc,
973   Tcl_Obj *CONST objv[]
974 ){
975   Testvfs *p = (Testvfs *)cd;
976 
977   enum DB_enum {
978     CMD_SHM, CMD_DELETE, CMD_FILTER, CMD_IOERR, CMD_SCRIPT,
979     CMD_DEVCHAR, CMD_SECTORSIZE, CMD_FULLERR, CMD_CANTOPENERR
980   };
981   struct TestvfsSubcmd {
982     char *zName;
983     enum DB_enum eCmd;
984   } aSubcmd[] = {
985     { "shm",         CMD_SHM         },
986     { "delete",      CMD_DELETE      },
987     { "filter",      CMD_FILTER      },
988     { "ioerr",       CMD_IOERR       },
989     { "fullerr",     CMD_FULLERR     },
990     { "cantopenerr", CMD_CANTOPENERR },
991     { "script",      CMD_SCRIPT      },
992     { "devchar",     CMD_DEVCHAR     },
993     { "sectorsize",  CMD_SECTORSIZE  },
994     { 0, 0 }
995   };
996   int i;
997 
998   if( objc<2 ){
999     Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
1000     return TCL_ERROR;
1001   }
1002   if( Tcl_GetIndexFromObjStruct(
1003         interp, objv[1], aSubcmd, sizeof(aSubcmd[0]), "subcommand", 0, &i)
1004   ){
1005     return TCL_ERROR;
1006   }
1007   Tcl_ResetResult(interp);
1008 
1009   switch( aSubcmd[i].eCmd ){
1010     case CMD_SHM: {
1011       Tcl_Obj *pObj;
1012       int i, rc;
1013       TestvfsBuffer *pBuffer;
1014       char *zName;
1015       if( objc!=3 && objc!=4 ){
1016         Tcl_WrongNumArgs(interp, 2, objv, "FILE ?VALUE?");
1017         return TCL_ERROR;
1018       }
1019       zName = ckalloc(p->pParent->mxPathname);
1020       rc = p->pParent->xFullPathname(
1021           p->pParent, Tcl_GetString(objv[2]),
1022           p->pParent->mxPathname, zName
1023       );
1024       if( rc!=SQLITE_OK ){
1025         Tcl_AppendResult(interp, "failed to get full path: ",
1026                          Tcl_GetString(objv[2]), 0);
1027         ckfree(zName);
1028         return TCL_ERROR;
1029       }
1030       for(pBuffer=p->pBuffer; pBuffer; pBuffer=pBuffer->pNext){
1031         if( 0==strcmp(pBuffer->zFile, zName) ) break;
1032       }
1033       ckfree(zName);
1034       if( !pBuffer ){
1035         Tcl_AppendResult(interp, "no such file: ", Tcl_GetString(objv[2]), 0);
1036         return TCL_ERROR;
1037       }
1038       if( objc==4 ){
1039         int n;
1040         u8 *a = Tcl_GetByteArrayFromObj(objv[3], &n);
1041         int pgsz = pBuffer->pgsz;
1042         if( pgsz==0 ) pgsz = 65536;
1043         for(i=0; i*pgsz<n; i++){
1044           int nByte = pgsz;
1045           tvfsAllocPage(pBuffer, i, pgsz);
1046           if( n-i*pgsz<pgsz ){
1047             nByte = n;
1048           }
1049           memcpy(pBuffer->aPage[i], &a[i*pgsz], nByte);
1050         }
1051       }
1052 
1053       pObj = Tcl_NewObj();
1054       for(i=0; pBuffer->aPage[i]; i++){
1055         int pgsz = pBuffer->pgsz;
1056         if( pgsz==0 ) pgsz = 65536;
1057         Tcl_AppendObjToObj(pObj, Tcl_NewByteArrayObj(pBuffer->aPage[i], pgsz));
1058       }
1059       Tcl_SetObjResult(interp, pObj);
1060       break;
1061     }
1062 
1063     case CMD_FILTER: {
1064       static struct VfsMethod {
1065         char *zName;
1066         int mask;
1067       } vfsmethod [] = {
1068         { "xShmOpen",      TESTVFS_SHMOPEN_MASK },
1069         { "xShmLock",      TESTVFS_SHMLOCK_MASK },
1070         { "xShmBarrier",   TESTVFS_SHMBARRIER_MASK },
1071         { "xShmUnmap",     TESTVFS_SHMCLOSE_MASK },
1072         { "xShmMap",       TESTVFS_SHMMAP_MASK },
1073         { "xSync",         TESTVFS_SYNC_MASK },
1074         { "xDelete",       TESTVFS_DELETE_MASK },
1075         { "xWrite",        TESTVFS_WRITE_MASK },
1076         { "xRead",         TESTVFS_READ_MASK },
1077         { "xTruncate",     TESTVFS_TRUNCATE_MASK },
1078         { "xOpen",         TESTVFS_OPEN_MASK },
1079         { "xClose",        TESTVFS_CLOSE_MASK },
1080         { "xAccess",       TESTVFS_ACCESS_MASK },
1081         { "xFullPathname", TESTVFS_FULLPATHNAME_MASK },
1082       };
1083       Tcl_Obj **apElem = 0;
1084       int nElem = 0;
1085       int i;
1086       int mask = 0;
1087       if( objc!=3 ){
1088         Tcl_WrongNumArgs(interp, 2, objv, "LIST");
1089         return TCL_ERROR;
1090       }
1091       if( Tcl_ListObjGetElements(interp, objv[2], &nElem, &apElem) ){
1092         return TCL_ERROR;
1093       }
1094       Tcl_ResetResult(interp);
1095       for(i=0; i<nElem; i++){
1096         int iMethod;
1097         char *zElem = Tcl_GetString(apElem[i]);
1098         for(iMethod=0; iMethod<ArraySize(vfsmethod); iMethod++){
1099           if( strcmp(zElem, vfsmethod[iMethod].zName)==0 ){
1100             mask |= vfsmethod[iMethod].mask;
1101             break;
1102           }
1103         }
1104         if( iMethod==ArraySize(vfsmethod) ){
1105           Tcl_AppendResult(interp, "unknown method: ", zElem, 0);
1106           return TCL_ERROR;
1107         }
1108       }
1109       p->mask = mask;
1110       break;
1111     }
1112 
1113     case CMD_SCRIPT: {
1114       if( objc==3 ){
1115         int nByte;
1116         if( p->pScript ){
1117           Tcl_DecrRefCount(p->pScript);
1118           p->pScript = 0;
1119         }
1120         Tcl_GetStringFromObj(objv[2], &nByte);
1121         if( nByte>0 ){
1122           p->pScript = Tcl_DuplicateObj(objv[2]);
1123           Tcl_IncrRefCount(p->pScript);
1124         }
1125       }else if( objc!=2 ){
1126         Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?");
1127         return TCL_ERROR;
1128       }
1129 
1130       Tcl_ResetResult(interp);
1131       if( p->pScript ) Tcl_SetObjResult(interp, p->pScript);
1132 
1133       break;
1134     }
1135 
1136     /*
1137     ** TESTVFS ioerr ?IFAIL PERSIST?
1138     **
1139     **   Where IFAIL is an integer and PERSIST is boolean.
1140     */
1141     case CMD_CANTOPENERR:
1142     case CMD_IOERR:
1143     case CMD_FULLERR: {
1144       TestFaultInject *pTest;
1145       int iRet;
1146 
1147       switch( aSubcmd[i].eCmd ){
1148         case CMD_IOERR: pTest = &p->ioerr_err; break;
1149         case CMD_FULLERR: pTest = &p->full_err; break;
1150         case CMD_CANTOPENERR: pTest = &p->cantopen_err; break;
1151         default: assert(0);
1152       }
1153       iRet = pTest->nFail;
1154       pTest->nFail = 0;
1155       pTest->eFault = 0;
1156       pTest->iCnt = 0;
1157 
1158       if( objc==4 ){
1159         int iCnt, iPersist;
1160         if( TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &iCnt)
1161          || TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[3], &iPersist)
1162         ){
1163           return TCL_ERROR;
1164         }
1165         pTest->eFault = iPersist?FAULT_INJECT_PERSISTENT:FAULT_INJECT_TRANSIENT;
1166         pTest->iCnt = iCnt;
1167       }else if( objc!=2 ){
1168         Tcl_WrongNumArgs(interp, 2, objv, "?CNT PERSIST?");
1169         return TCL_ERROR;
1170       }
1171       Tcl_SetObjResult(interp, Tcl_NewIntObj(iRet));
1172       break;
1173     }
1174 
1175     case CMD_DELETE: {
1176       Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
1177       break;
1178     }
1179 
1180     case CMD_DEVCHAR: {
1181       struct DeviceFlag {
1182         char *zName;
1183         int iValue;
1184       } aFlag[] = {
1185         { "default",               -1 },
1186         { "atomic",                SQLITE_IOCAP_ATOMIC                },
1187         { "atomic512",             SQLITE_IOCAP_ATOMIC512             },
1188         { "atomic1k",              SQLITE_IOCAP_ATOMIC1K              },
1189         { "atomic2k",              SQLITE_IOCAP_ATOMIC2K              },
1190         { "atomic4k",              SQLITE_IOCAP_ATOMIC4K              },
1191         { "atomic8k",              SQLITE_IOCAP_ATOMIC8K              },
1192         { "atomic16k",             SQLITE_IOCAP_ATOMIC16K             },
1193         { "atomic32k",             SQLITE_IOCAP_ATOMIC32K             },
1194         { "atomic64k",             SQLITE_IOCAP_ATOMIC64K             },
1195         { "sequential",            SQLITE_IOCAP_SEQUENTIAL            },
1196         { "safe_append",           SQLITE_IOCAP_SAFE_APPEND           },
1197         { "undeletable_when_open", SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN },
1198         { "powersafe_overwrite",   SQLITE_IOCAP_POWERSAFE_OVERWRITE   },
1199         { 0, 0 }
1200       };
1201       Tcl_Obj *pRet;
1202       int iFlag;
1203 
1204       if( objc>3 ){
1205         Tcl_WrongNumArgs(interp, 2, objv, "?ATTR-LIST?");
1206         return TCL_ERROR;
1207       }
1208       if( objc==3 ){
1209         int j;
1210         int iNew = 0;
1211         Tcl_Obj **flags = 0;
1212         int nFlags = 0;
1213 
1214         if( Tcl_ListObjGetElements(interp, objv[2], &nFlags, &flags) ){
1215           return TCL_ERROR;
1216         }
1217 
1218         for(j=0; j<nFlags; j++){
1219           int idx = 0;
1220           if( Tcl_GetIndexFromObjStruct(interp, flags[j], aFlag,
1221                 sizeof(aFlag[0]), "flag", 0, &idx)
1222           ){
1223             return TCL_ERROR;
1224           }
1225           if( aFlag[idx].iValue<0 && nFlags>1 ){
1226             Tcl_AppendResult(interp, "bad flags: ", Tcl_GetString(objv[2]), 0);
1227             return TCL_ERROR;
1228           }
1229           iNew |= aFlag[idx].iValue;
1230         }
1231 
1232         p->iDevchar = iNew| 0x10000000;
1233       }
1234 
1235       pRet = Tcl_NewObj();
1236       for(iFlag=0; iFlag<sizeof(aFlag)/sizeof(aFlag[0]); iFlag++){
1237         if( p->iDevchar & aFlag[iFlag].iValue ){
1238           Tcl_ListObjAppendElement(
1239               interp, pRet, Tcl_NewStringObj(aFlag[iFlag].zName, -1)
1240           );
1241         }
1242       }
1243       Tcl_SetObjResult(interp, pRet);
1244 
1245       break;
1246     }
1247 
1248     case CMD_SECTORSIZE: {
1249       if( objc>3 ){
1250         Tcl_WrongNumArgs(interp, 2, objv, "?VALUE?");
1251         return TCL_ERROR;
1252       }
1253       if( objc==3 ){
1254         int iNew = 0;
1255         if( Tcl_GetIntFromObj(interp, objv[2], &iNew) ){
1256           return TCL_ERROR;
1257         }
1258         p->iSectorsize = iNew;
1259       }
1260       Tcl_SetObjResult(interp, Tcl_NewIntObj(p->iSectorsize));
1261       break;
1262     }
1263   }
1264 
1265   return TCL_OK;
1266 }
1267 
1268 static void testvfs_obj_del(ClientData cd){
1269   Testvfs *p = (Testvfs *)cd;
1270   if( p->pScript ) Tcl_DecrRefCount(p->pScript);
1271   sqlite3_vfs_unregister(p->pVfs);
1272   ckfree((char *)p->pVfs);
1273   ckfree((char *)p);
1274 }
1275 
1276 /*
1277 ** Usage:  testvfs VFSNAME ?SWITCHES?
1278 **
1279 ** Switches are:
1280 **
1281 **   -noshm   BOOLEAN             (True to omit shm methods. Default false)
1282 **   -default BOOLEAN             (True to make the vfs default. Default false)
1283 **
1284 ** This command creates two things when it is invoked: an SQLite VFS, and
1285 ** a Tcl command. Both are named VFSNAME. The VFS is installed. It is not
1286 ** installed as the default VFS.
1287 **
1288 ** The VFS passes all file I/O calls through to the underlying VFS.
1289 **
1290 ** Whenever the xShmMap method of the VFS
1291 ** is invoked, the SCRIPT is executed as follows:
1292 **
1293 **   SCRIPT xShmMap    FILENAME ID
1294 **
1295 ** The value returned by the invocation of SCRIPT above is interpreted as
1296 ** an SQLite error code and returned to SQLite. Either a symbolic
1297 ** "SQLITE_OK" or numeric "0" value may be returned.
1298 **
1299 ** The contents of the shared-memory buffer associated with a given file
1300 ** may be read and set using the following command:
1301 **
1302 **   VFSNAME shm FILENAME ?NEWVALUE?
1303 **
1304 ** When the xShmLock method is invoked by SQLite, the following script is
1305 ** run:
1306 **
1307 **   SCRIPT xShmLock    FILENAME ID LOCK
1308 **
1309 ** where LOCK is of the form "OFFSET NBYTE lock/unlock shared/exclusive"
1310 */
1311 static int testvfs_cmd(
1312   ClientData cd,
1313   Tcl_Interp *interp,
1314   int objc,
1315   Tcl_Obj *CONST objv[]
1316 ){
1317   static sqlite3_vfs tvfs_vfs = {
1318     2,                            /* iVersion */
1319     0,                            /* szOsFile */
1320     0,                            /* mxPathname */
1321     0,                            /* pNext */
1322     0,                            /* zName */
1323     0,                            /* pAppData */
1324     tvfsOpen,                     /* xOpen */
1325     tvfsDelete,                   /* xDelete */
1326     tvfsAccess,                   /* xAccess */
1327     tvfsFullPathname,             /* xFullPathname */
1328 #ifndef SQLITE_OMIT_LOAD_EXTENSION
1329     tvfsDlOpen,                   /* xDlOpen */
1330     tvfsDlError,                  /* xDlError */
1331     tvfsDlSym,                    /* xDlSym */
1332     tvfsDlClose,                  /* xDlClose */
1333 #else
1334     0,                            /* xDlOpen */
1335     0,                            /* xDlError */
1336     0,                            /* xDlSym */
1337     0,                            /* xDlClose */
1338 #endif /* SQLITE_OMIT_LOAD_EXTENSION */
1339     tvfsRandomness,               /* xRandomness */
1340     tvfsSleep,                    /* xSleep */
1341     tvfsCurrentTime,              /* xCurrentTime */
1342     0,                            /* xGetLastError */
1343     0,                            /* xCurrentTimeInt64 */
1344   };
1345 
1346   Testvfs *p;                     /* New object */
1347   sqlite3_vfs *pVfs;              /* New VFS */
1348   char *zVfs;
1349   int nByte;                      /* Bytes of space to allocate at p */
1350 
1351   int i;
1352   int isNoshm = 0;                /* True if -noshm is passed */
1353   int isDefault = 0;              /* True if -default is passed */
1354   int szOsFile = 0;               /* Value passed to -szosfile */
1355   int mxPathname = -1;            /* Value passed to -mxpathname */
1356   int iVersion = 2;               /* Value passed to -iversion */
1357 
1358   if( objc<2 || 0!=(objc%2) ) goto bad_args;
1359   for(i=2; i<objc; i += 2){
1360     int nSwitch;
1361     char *zSwitch;
1362     zSwitch = Tcl_GetStringFromObj(objv[i], &nSwitch);
1363 
1364     if( nSwitch>2 && 0==strncmp("-noshm", zSwitch, nSwitch) ){
1365       if( Tcl_GetBooleanFromObj(interp, objv[i+1], &isNoshm) ){
1366         return TCL_ERROR;
1367       }
1368     }
1369     else if( nSwitch>2 && 0==strncmp("-default", zSwitch, nSwitch) ){
1370       if( Tcl_GetBooleanFromObj(interp, objv[i+1], &isDefault) ){
1371         return TCL_ERROR;
1372       }
1373     }
1374     else if( nSwitch>2 && 0==strncmp("-szosfile", zSwitch, nSwitch) ){
1375       if( Tcl_GetIntFromObj(interp, objv[i+1], &szOsFile) ){
1376         return TCL_ERROR;
1377       }
1378     }
1379     else if( nSwitch>2 && 0==strncmp("-mxpathname", zSwitch, nSwitch) ){
1380       if( Tcl_GetIntFromObj(interp, objv[i+1], &mxPathname) ){
1381         return TCL_ERROR;
1382       }
1383     }
1384     else if( nSwitch>2 && 0==strncmp("-iversion", zSwitch, nSwitch) ){
1385       if( Tcl_GetIntFromObj(interp, objv[i+1], &iVersion) ){
1386         return TCL_ERROR;
1387       }
1388     }
1389     else{
1390       goto bad_args;
1391     }
1392   }
1393 
1394   if( szOsFile<sizeof(TestvfsFile) ){
1395     szOsFile = sizeof(TestvfsFile);
1396   }
1397 
1398   zVfs = Tcl_GetString(objv[1]);
1399   nByte = sizeof(Testvfs) + (int)strlen(zVfs)+1;
1400   p = (Testvfs *)ckalloc(nByte);
1401   memset(p, 0, nByte);
1402   p->iDevchar = -1;
1403   p->iSectorsize = -1;
1404 
1405   /* Create the new object command before querying SQLite for a default VFS
1406   ** to use for 'real' IO operations. This is because creating the new VFS
1407   ** may delete an existing [testvfs] VFS of the same name. If such a VFS
1408   ** is currently the default, the new [testvfs] may end up calling the
1409   ** methods of a deleted object.
1410   */
1411   Tcl_CreateObjCommand(interp, zVfs, testvfs_obj_cmd, p, testvfs_obj_del);
1412   p->pParent = sqlite3_vfs_find(0);
1413   p->interp = interp;
1414 
1415   p->zName = (char *)&p[1];
1416   memcpy(p->zName, zVfs, strlen(zVfs)+1);
1417 
1418   pVfs = (sqlite3_vfs *)ckalloc(sizeof(sqlite3_vfs));
1419   memcpy(pVfs, &tvfs_vfs, sizeof(sqlite3_vfs));
1420   pVfs->pAppData = (void *)p;
1421   pVfs->iVersion = iVersion;
1422   pVfs->zName = p->zName;
1423   pVfs->mxPathname = p->pParent->mxPathname;
1424   if( mxPathname>=0 && mxPathname<pVfs->mxPathname ){
1425     pVfs->mxPathname = mxPathname;
1426   }
1427   pVfs->szOsFile = szOsFile;
1428   p->pVfs = pVfs;
1429   p->isNoshm = isNoshm;
1430   p->mask = TESTVFS_ALL_MASK;
1431 
1432   sqlite3_vfs_register(pVfs, isDefault);
1433 
1434   return TCL_OK;
1435 
1436  bad_args:
1437   Tcl_WrongNumArgs(interp, 1, objv, "VFSNAME ?-noshm BOOL? ?-default BOOL? ?-mxpathname INT? ?-szosfile INT? ?-iversion INT?");
1438   return TCL_ERROR;
1439 }
1440 
1441 int Sqlitetestvfs_Init(Tcl_Interp *interp){
1442   Tcl_CreateObjCommand(interp, "testvfs", testvfs_cmd, 0, 0);
1443   return TCL_OK;
1444 }
1445 
1446 #endif
1447