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