1 /* 2 ** 2008 April 10 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 an SQLite vfs wrapper that 14 ** adds instrumentation to all vfs and file methods. C and Tcl interfaces 15 ** are provided to control the instrumentation. 16 */ 17 18 /* 19 ** This module contains code for a wrapper VFS that causes a log of 20 ** most VFS calls to be written into a nominated file on disk. The log 21 ** is stored in a compressed binary format to reduce the amount of IO 22 ** overhead introduced into the application by logging. 23 ** 24 ** All calls on sqlite3_file objects except xFileControl() are logged. 25 ** Additionally, calls to the xAccess(), xOpen(), and xDelete() 26 ** methods are logged. The other sqlite3_vfs object methods (xDlXXX, 27 ** xRandomness, xSleep, xCurrentTime, xGetLastError and xCurrentTimeInt64) 28 ** are not logged. 29 ** 30 ** The binary log files are read using a virtual table implementation 31 ** also contained in this file. 32 ** 33 ** CREATING LOG FILES: 34 ** 35 ** int sqlite3_vfslog_new( 36 ** const char *zVfs, // Name of new VFS 37 ** const char *zParentVfs, // Name of parent VFS (or NULL) 38 ** const char *zLog // Name of log file to write to 39 ** ); 40 ** 41 ** int sqlite3_vfslog_finalize(const char *zVfs); 42 ** 43 ** ANNOTATING LOG FILES: 44 ** 45 ** To write an arbitrary message into a log file: 46 ** 47 ** int sqlite3_vfslog_annotate(const char *zVfs, const char *zMsg); 48 ** 49 ** READING LOG FILES: 50 ** 51 ** Log files are read using the "vfslog" virtual table implementation 52 ** in this file. To register the virtual table with SQLite, use: 53 ** 54 ** int sqlite3_vfslog_register(sqlite3 *db); 55 ** 56 ** Then, if the log file is named "vfs.log", the following SQL command: 57 ** 58 ** CREATE VIRTUAL TABLE v USING vfslog('vfs.log'); 59 ** 60 ** creates a virtual table with 6 columns, as follows: 61 ** 62 ** CREATE TABLE v( 63 ** event TEXT, // "xOpen", "xRead" etc. 64 ** file TEXT, // Name of file this call applies to 65 ** clicks INTEGER, // Time spent in call 66 ** rc INTEGER, // Return value 67 ** size INTEGER, // Bytes read or written 68 ** offset INTEGER // File offset read or written 69 ** ); 70 */ 71 72 #include "sqlite3.h" 73 74 #include "os_setup.h" 75 #if SQLITE_OS_WIN 76 # include "os_win.h" 77 #endif 78 79 #include <string.h> 80 #include <assert.h> 81 82 83 /* 84 ** Maximum pathname length supported by the vfslog backend. 85 */ 86 #define INST_MAX_PATHNAME 512 87 88 #define OS_ACCESS 1 89 #define OS_CHECKRESERVEDLOCK 2 90 #define OS_CLOSE 3 91 #define OS_CURRENTTIME 4 92 #define OS_DELETE 5 93 #define OS_DEVCHAR 6 94 #define OS_FILECONTROL 7 95 #define OS_FILESIZE 8 96 #define OS_FULLPATHNAME 9 97 #define OS_LOCK 11 98 #define OS_OPEN 12 99 #define OS_RANDOMNESS 13 100 #define OS_READ 14 101 #define OS_SECTORSIZE 15 102 #define OS_SLEEP 16 103 #define OS_SYNC 17 104 #define OS_TRUNCATE 18 105 #define OS_UNLOCK 19 106 #define OS_WRITE 20 107 #define OS_SHMUNMAP 22 108 #define OS_SHMMAP 23 109 #define OS_SHMLOCK 25 110 #define OS_SHMBARRIER 26 111 #define OS_ANNOTATE 28 112 113 #define OS_NUMEVENTS 29 114 115 #define VFSLOG_BUFFERSIZE 8192 116 117 typedef struct VfslogVfs VfslogVfs; 118 typedef struct VfslogFile VfslogFile; 119 120 struct VfslogVfs { 121 sqlite3_vfs base; /* VFS methods */ 122 sqlite3_vfs *pVfs; /* Parent VFS */ 123 int iNextFileId; /* Next file id */ 124 sqlite3_file *pLog; /* Log file handle */ 125 sqlite3_int64 iOffset; /* Log file offset of start of write buffer */ 126 int nBuf; /* Number of valid bytes in aBuf[] */ 127 char aBuf[VFSLOG_BUFFERSIZE]; /* Write buffer */ 128 }; 129 130 struct VfslogFile { 131 sqlite3_file base; /* IO methods */ 132 sqlite3_file *pReal; /* Underlying file handle */ 133 sqlite3_vfs *pVfslog; /* Associated VsflogVfs object */ 134 int iFileId; /* File id number */ 135 }; 136 137 #define REALVFS(p) (((VfslogVfs *)(p))->pVfs) 138 139 140 141 /* 142 ** Method declarations for vfslog_file. 143 */ 144 static int vfslogClose(sqlite3_file*); 145 static int vfslogRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); 146 static int vfslogWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst); 147 static int vfslogTruncate(sqlite3_file*, sqlite3_int64 size); 148 static int vfslogSync(sqlite3_file*, int flags); 149 static int vfslogFileSize(sqlite3_file*, sqlite3_int64 *pSize); 150 static int vfslogLock(sqlite3_file*, int); 151 static int vfslogUnlock(sqlite3_file*, int); 152 static int vfslogCheckReservedLock(sqlite3_file*, int *pResOut); 153 static int vfslogFileControl(sqlite3_file*, int op, void *pArg); 154 static int vfslogSectorSize(sqlite3_file*); 155 static int vfslogDeviceCharacteristics(sqlite3_file*); 156 157 static int vfslogShmLock(sqlite3_file *pFile, int ofst, int n, int flags); 158 static int vfslogShmMap(sqlite3_file *pFile,int,int,int,volatile void **); 159 static void vfslogShmBarrier(sqlite3_file*); 160 static int vfslogShmUnmap(sqlite3_file *pFile, int deleteFlag); 161 162 /* 163 ** Method declarations for vfslog_vfs. 164 */ 165 static int vfslogOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *); 166 static int vfslogDelete(sqlite3_vfs*, const char *zName, int syncDir); 167 static int vfslogAccess(sqlite3_vfs*, const char *zName, int flags, int *); 168 static int vfslogFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut); 169 static void *vfslogDlOpen(sqlite3_vfs*, const char *zFilename); 170 static void vfslogDlError(sqlite3_vfs*, int nByte, char *zErrMsg); 171 static void (*vfslogDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void); 172 static void vfslogDlClose(sqlite3_vfs*, void*); 173 static int vfslogRandomness(sqlite3_vfs*, int nByte, char *zOut); 174 static int vfslogSleep(sqlite3_vfs*, int microseconds); 175 static int vfslogCurrentTime(sqlite3_vfs*, double*); 176 177 static int vfslogGetLastError(sqlite3_vfs*, int, char *); 178 static int vfslogCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); 179 180 static sqlite3_vfs vfslog_vfs = { 181 1, /* iVersion */ 182 sizeof(VfslogFile), /* szOsFile */ 183 INST_MAX_PATHNAME, /* mxPathname */ 184 0, /* pNext */ 185 0, /* zName */ 186 0, /* pAppData */ 187 vfslogOpen, /* xOpen */ 188 vfslogDelete, /* xDelete */ 189 vfslogAccess, /* xAccess */ 190 vfslogFullPathname, /* xFullPathname */ 191 vfslogDlOpen, /* xDlOpen */ 192 vfslogDlError, /* xDlError */ 193 vfslogDlSym, /* xDlSym */ 194 vfslogDlClose, /* xDlClose */ 195 vfslogRandomness, /* xRandomness */ 196 vfslogSleep, /* xSleep */ 197 vfslogCurrentTime, /* xCurrentTime */ 198 vfslogGetLastError, /* xGetLastError */ 199 vfslogCurrentTimeInt64 /* xCurrentTime */ 200 }; 201 202 static sqlite3_io_methods vfslog_io_methods = { 203 2, /* iVersion */ 204 vfslogClose, /* xClose */ 205 vfslogRead, /* xRead */ 206 vfslogWrite, /* xWrite */ 207 vfslogTruncate, /* xTruncate */ 208 vfslogSync, /* xSync */ 209 vfslogFileSize, /* xFileSize */ 210 vfslogLock, /* xLock */ 211 vfslogUnlock, /* xUnlock */ 212 vfslogCheckReservedLock, /* xCheckReservedLock */ 213 vfslogFileControl, /* xFileControl */ 214 vfslogSectorSize, /* xSectorSize */ 215 vfslogDeviceCharacteristics, /* xDeviceCharacteristics */ 216 vfslogShmMap, /* xShmMap */ 217 vfslogShmLock, /* xShmLock */ 218 vfslogShmBarrier, /* xShmBarrier */ 219 vfslogShmUnmap /* xShmUnmap */ 220 }; 221 222 #if SQLITE_OS_UNIX && !defined(NO_GETTOD) 223 #include <sys/time.h> 224 static sqlite3_uint64 vfslog_time(){ 225 struct timeval sTime; 226 gettimeofday(&sTime, 0); 227 return sTime.tv_usec + (sqlite3_uint64)sTime.tv_sec * 1000000; 228 } 229 #elif SQLITE_OS_WIN 230 #include <time.h> 231 static sqlite3_uint64 vfslog_time(){ 232 FILETIME ft; 233 sqlite3_uint64 u64time = 0; 234 235 GetSystemTimeAsFileTime(&ft); 236 237 u64time |= ft.dwHighDateTime; 238 u64time <<= 32; 239 u64time |= ft.dwLowDateTime; 240 241 /* ft is 100-nanosecond intervals, we want microseconds */ 242 return u64time /(sqlite3_uint64)10; 243 } 244 #else 245 static sqlite3_uint64 vfslog_time(){ 246 return 0; 247 } 248 #endif 249 250 static void vfslog_call(sqlite3_vfs *, int, int, sqlite3_int64, int, int, int); 251 static void vfslog_string(sqlite3_vfs *, const char *); 252 253 /* 254 ** Close an vfslog-file. 255 */ 256 static int vfslogClose(sqlite3_file *pFile){ 257 sqlite3_uint64 t; 258 int rc = SQLITE_OK; 259 VfslogFile *p = (VfslogFile *)pFile; 260 261 t = vfslog_time(); 262 if( p->pReal->pMethods ){ 263 rc = p->pReal->pMethods->xClose(p->pReal); 264 } 265 t = vfslog_time() - t; 266 vfslog_call(p->pVfslog, OS_CLOSE, p->iFileId, t, rc, 0, 0); 267 return rc; 268 } 269 270 /* 271 ** Read data from an vfslog-file. 272 */ 273 static int vfslogRead( 274 sqlite3_file *pFile, 275 void *zBuf, 276 int iAmt, 277 sqlite_int64 iOfst 278 ){ 279 int rc; 280 sqlite3_uint64 t; 281 VfslogFile *p = (VfslogFile *)pFile; 282 t = vfslog_time(); 283 rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst); 284 t = vfslog_time() - t; 285 vfslog_call(p->pVfslog, OS_READ, p->iFileId, t, rc, iAmt, (int)iOfst); 286 return rc; 287 } 288 289 /* 290 ** Write data to an vfslog-file. 291 */ 292 static int vfslogWrite( 293 sqlite3_file *pFile, 294 const void *z, 295 int iAmt, 296 sqlite_int64 iOfst 297 ){ 298 int rc; 299 sqlite3_uint64 t; 300 VfslogFile *p = (VfslogFile *)pFile; 301 t = vfslog_time(); 302 rc = p->pReal->pMethods->xWrite(p->pReal, z, iAmt, iOfst); 303 t = vfslog_time() - t; 304 vfslog_call(p->pVfslog, OS_WRITE, p->iFileId, t, rc, iAmt, (int)iOfst); 305 return rc; 306 } 307 308 /* 309 ** Truncate an vfslog-file. 310 */ 311 static int vfslogTruncate(sqlite3_file *pFile, sqlite_int64 size){ 312 int rc; 313 sqlite3_uint64 t; 314 VfslogFile *p = (VfslogFile *)pFile; 315 t = vfslog_time(); 316 rc = p->pReal->pMethods->xTruncate(p->pReal, size); 317 t = vfslog_time() - t; 318 vfslog_call(p->pVfslog, OS_TRUNCATE, p->iFileId, t, rc, 0, (int)size); 319 return rc; 320 } 321 322 /* 323 ** Sync an vfslog-file. 324 */ 325 static int vfslogSync(sqlite3_file *pFile, int flags){ 326 int rc; 327 sqlite3_uint64 t; 328 VfslogFile *p = (VfslogFile *)pFile; 329 t = vfslog_time(); 330 rc = p->pReal->pMethods->xSync(p->pReal, flags); 331 t = vfslog_time() - t; 332 vfslog_call(p->pVfslog, OS_SYNC, p->iFileId, t, rc, flags, 0); 333 return rc; 334 } 335 336 /* 337 ** Return the current file-size of an vfslog-file. 338 */ 339 static int vfslogFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ 340 int rc; 341 sqlite3_uint64 t; 342 VfslogFile *p = (VfslogFile *)pFile; 343 t = vfslog_time(); 344 rc = p->pReal->pMethods->xFileSize(p->pReal, pSize); 345 t = vfslog_time() - t; 346 vfslog_call(p->pVfslog, OS_FILESIZE, p->iFileId, t, rc, 0, (int)*pSize); 347 return rc; 348 } 349 350 /* 351 ** Lock an vfslog-file. 352 */ 353 static int vfslogLock(sqlite3_file *pFile, int eLock){ 354 int rc; 355 sqlite3_uint64 t; 356 VfslogFile *p = (VfslogFile *)pFile; 357 t = vfslog_time(); 358 rc = p->pReal->pMethods->xLock(p->pReal, eLock); 359 t = vfslog_time() - t; 360 vfslog_call(p->pVfslog, OS_LOCK, p->iFileId, t, rc, eLock, 0); 361 return rc; 362 } 363 364 /* 365 ** Unlock an vfslog-file. 366 */ 367 static int vfslogUnlock(sqlite3_file *pFile, int eLock){ 368 int rc; 369 sqlite3_uint64 t; 370 VfslogFile *p = (VfslogFile *)pFile; 371 t = vfslog_time(); 372 rc = p->pReal->pMethods->xUnlock(p->pReal, eLock); 373 t = vfslog_time() - t; 374 vfslog_call(p->pVfslog, OS_UNLOCK, p->iFileId, t, rc, eLock, 0); 375 return rc; 376 } 377 378 /* 379 ** Check if another file-handle holds a RESERVED lock on an vfslog-file. 380 */ 381 static int vfslogCheckReservedLock(sqlite3_file *pFile, int *pResOut){ 382 int rc; 383 sqlite3_uint64 t; 384 VfslogFile *p = (VfslogFile *)pFile; 385 t = vfslog_time(); 386 rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut); 387 t = vfslog_time() - t; 388 vfslog_call(p->pVfslog, OS_CHECKRESERVEDLOCK, p->iFileId, t, rc, *pResOut, 0); 389 return rc; 390 } 391 392 /* 393 ** File control method. For custom operations on an vfslog-file. 394 */ 395 static int vfslogFileControl(sqlite3_file *pFile, int op, void *pArg){ 396 VfslogFile *p = (VfslogFile *)pFile; 397 int rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg); 398 if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){ 399 *(char**)pArg = sqlite3_mprintf("vfslog/%z", *(char**)pArg); 400 } 401 return rc; 402 } 403 404 /* 405 ** Return the sector-size in bytes for an vfslog-file. 406 */ 407 static int vfslogSectorSize(sqlite3_file *pFile){ 408 int rc; 409 sqlite3_uint64 t; 410 VfslogFile *p = (VfslogFile *)pFile; 411 t = vfslog_time(); 412 rc = p->pReal->pMethods->xSectorSize(p->pReal); 413 t = vfslog_time() - t; 414 vfslog_call(p->pVfslog, OS_SECTORSIZE, p->iFileId, t, rc, 0, 0); 415 return rc; 416 } 417 418 /* 419 ** Return the device characteristic flags supported by an vfslog-file. 420 */ 421 static int vfslogDeviceCharacteristics(sqlite3_file *pFile){ 422 int rc; 423 sqlite3_uint64 t; 424 VfslogFile *p = (VfslogFile *)pFile; 425 t = vfslog_time(); 426 rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal); 427 t = vfslog_time() - t; 428 vfslog_call(p->pVfslog, OS_DEVCHAR, p->iFileId, t, rc, 0, 0); 429 return rc; 430 } 431 432 static int vfslogShmLock(sqlite3_file *pFile, int ofst, int n, int flags){ 433 int rc; 434 sqlite3_uint64 t; 435 VfslogFile *p = (VfslogFile *)pFile; 436 t = vfslog_time(); 437 rc = p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags); 438 t = vfslog_time() - t; 439 vfslog_call(p->pVfslog, OS_SHMLOCK, p->iFileId, t, rc, 0, 0); 440 return rc; 441 } 442 static int vfslogShmMap( 443 sqlite3_file *pFile, 444 int iRegion, 445 int szRegion, 446 int isWrite, 447 volatile void **pp 448 ){ 449 int rc; 450 sqlite3_uint64 t; 451 VfslogFile *p = (VfslogFile *)pFile; 452 t = vfslog_time(); 453 rc = p->pReal->pMethods->xShmMap(p->pReal, iRegion, szRegion, isWrite, pp); 454 t = vfslog_time() - t; 455 vfslog_call(p->pVfslog, OS_SHMMAP, p->iFileId, t, rc, 0, 0); 456 return rc; 457 } 458 static void vfslogShmBarrier(sqlite3_file *pFile){ 459 sqlite3_uint64 t; 460 VfslogFile *p = (VfslogFile *)pFile; 461 t = vfslog_time(); 462 p->pReal->pMethods->xShmBarrier(p->pReal); 463 t = vfslog_time() - t; 464 vfslog_call(p->pVfslog, OS_SHMBARRIER, p->iFileId, t, SQLITE_OK, 0, 0); 465 } 466 static int vfslogShmUnmap(sqlite3_file *pFile, int deleteFlag){ 467 int rc; 468 sqlite3_uint64 t; 469 VfslogFile *p = (VfslogFile *)pFile; 470 t = vfslog_time(); 471 rc = p->pReal->pMethods->xShmUnmap(p->pReal, deleteFlag); 472 t = vfslog_time() - t; 473 vfslog_call(p->pVfslog, OS_SHMUNMAP, p->iFileId, t, rc, 0, 0); 474 return rc; 475 } 476 477 478 /* 479 ** Open an vfslog file handle. 480 */ 481 static int vfslogOpen( 482 sqlite3_vfs *pVfs, 483 const char *zName, 484 sqlite3_file *pFile, 485 int flags, 486 int *pOutFlags 487 ){ 488 int rc; 489 sqlite3_uint64 t; 490 VfslogFile *p = (VfslogFile *)pFile; 491 VfslogVfs *pLog = (VfslogVfs *)pVfs; 492 493 pFile->pMethods = &vfslog_io_methods; 494 p->pReal = (sqlite3_file *)&p[1]; 495 p->pVfslog = pVfs; 496 p->iFileId = ++pLog->iNextFileId; 497 498 t = vfslog_time(); 499 rc = REALVFS(pVfs)->xOpen(REALVFS(pVfs), zName, p->pReal, flags, pOutFlags); 500 t = vfslog_time() - t; 501 502 vfslog_call(pVfs, OS_OPEN, p->iFileId, t, rc, 0, 0); 503 vfslog_string(pVfs, zName); 504 return rc; 505 } 506 507 /* 508 ** Delete the file located at zPath. If the dirSync argument is true, 509 ** ensure the file-system modifications are synced to disk before 510 ** returning. 511 */ 512 static int vfslogDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ 513 int rc; 514 sqlite3_uint64 t; 515 t = vfslog_time(); 516 rc = REALVFS(pVfs)->xDelete(REALVFS(pVfs), zPath, dirSync); 517 t = vfslog_time() - t; 518 vfslog_call(pVfs, OS_DELETE, 0, t, rc, dirSync, 0); 519 vfslog_string(pVfs, zPath); 520 return rc; 521 } 522 523 /* 524 ** Test for access permissions. Return true if the requested permission 525 ** is available, or false otherwise. 526 */ 527 static int vfslogAccess( 528 sqlite3_vfs *pVfs, 529 const char *zPath, 530 int flags, 531 int *pResOut 532 ){ 533 int rc; 534 sqlite3_uint64 t; 535 t = vfslog_time(); 536 rc = REALVFS(pVfs)->xAccess(REALVFS(pVfs), zPath, flags, pResOut); 537 t = vfslog_time() - t; 538 vfslog_call(pVfs, OS_ACCESS, 0, t, rc, flags, *pResOut); 539 vfslog_string(pVfs, zPath); 540 return rc; 541 } 542 543 /* 544 ** Populate buffer zOut with the full canonical pathname corresponding 545 ** to the pathname in zPath. zOut is guaranteed to point to a buffer 546 ** of at least (INST_MAX_PATHNAME+1) bytes. 547 */ 548 static int vfslogFullPathname( 549 sqlite3_vfs *pVfs, 550 const char *zPath, 551 int nOut, 552 char *zOut 553 ){ 554 return REALVFS(pVfs)->xFullPathname(REALVFS(pVfs), zPath, nOut, zOut); 555 } 556 557 /* 558 ** Open the dynamic library located at zPath and return a handle. 559 */ 560 static void *vfslogDlOpen(sqlite3_vfs *pVfs, const char *zPath){ 561 return REALVFS(pVfs)->xDlOpen(REALVFS(pVfs), zPath); 562 } 563 564 /* 565 ** Populate the buffer zErrMsg (size nByte bytes) with a human readable 566 ** utf-8 string describing the most recent error encountered associated 567 ** with dynamic libraries. 568 */ 569 static void vfslogDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){ 570 REALVFS(pVfs)->xDlError(REALVFS(pVfs), nByte, zErrMsg); 571 } 572 573 /* 574 ** Return a pointer to the symbol zSymbol in the dynamic library pHandle. 575 */ 576 static void (*vfslogDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){ 577 return REALVFS(pVfs)->xDlSym(REALVFS(pVfs), p, zSym); 578 } 579 580 /* 581 ** Close the dynamic library handle pHandle. 582 */ 583 static void vfslogDlClose(sqlite3_vfs *pVfs, void *pHandle){ 584 REALVFS(pVfs)->xDlClose(REALVFS(pVfs), pHandle); 585 } 586 587 /* 588 ** Populate the buffer pointed to by zBufOut with nByte bytes of 589 ** random data. 590 */ 591 static int vfslogRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ 592 return REALVFS(pVfs)->xRandomness(REALVFS(pVfs), nByte, zBufOut); 593 } 594 595 /* 596 ** Sleep for nMicro microseconds. Return the number of microseconds 597 ** actually slept. 598 */ 599 static int vfslogSleep(sqlite3_vfs *pVfs, int nMicro){ 600 return REALVFS(pVfs)->xSleep(REALVFS(pVfs), nMicro); 601 } 602 603 /* 604 ** Return the current time as a Julian Day number in *pTimeOut. 605 */ 606 static int vfslogCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ 607 return REALVFS(pVfs)->xCurrentTime(REALVFS(pVfs), pTimeOut); 608 } 609 610 static int vfslogGetLastError(sqlite3_vfs *pVfs, int a, char *b){ 611 return REALVFS(pVfs)->xGetLastError(REALVFS(pVfs), a, b); 612 } 613 static int vfslogCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){ 614 return REALVFS(pVfs)->xCurrentTimeInt64(REALVFS(pVfs), p); 615 } 616 617 static void vfslog_flush(VfslogVfs *p){ 618 #ifdef SQLITE_TEST 619 extern int sqlite3_io_error_pending; 620 extern int sqlite3_io_error_persist; 621 extern int sqlite3_diskfull_pending; 622 623 int pending = sqlite3_io_error_pending; 624 int persist = sqlite3_io_error_persist; 625 int diskfull = sqlite3_diskfull_pending; 626 627 sqlite3_io_error_pending = 0; 628 sqlite3_io_error_persist = 0; 629 sqlite3_diskfull_pending = 0; 630 #endif 631 632 if( p->nBuf ){ 633 p->pLog->pMethods->xWrite(p->pLog, p->aBuf, p->nBuf, p->iOffset); 634 p->iOffset += p->nBuf; 635 p->nBuf = 0; 636 } 637 638 #ifdef SQLITE_TEST 639 sqlite3_io_error_pending = pending; 640 sqlite3_io_error_persist = persist; 641 sqlite3_diskfull_pending = diskfull; 642 #endif 643 } 644 645 static void put32bits(unsigned char *p, unsigned int v){ 646 p[0] = v>>24; 647 p[1] = (unsigned char)(v>>16); 648 p[2] = (unsigned char)(v>>8); 649 p[3] = (unsigned char)v; 650 } 651 652 static void vfslog_call( 653 sqlite3_vfs *pVfs, 654 int eEvent, 655 int iFileid, 656 sqlite3_int64 nClick, 657 int return_code, 658 int size, 659 int offset 660 ){ 661 VfslogVfs *p = (VfslogVfs *)pVfs; 662 unsigned char *zRec; 663 if( (24+p->nBuf)>sizeof(p->aBuf) ){ 664 vfslog_flush(p); 665 } 666 zRec = (unsigned char *)&p->aBuf[p->nBuf]; 667 put32bits(&zRec[0], eEvent); 668 put32bits(&zRec[4], iFileid); 669 put32bits(&zRec[8], (unsigned int)(nClick&0xffff)); 670 put32bits(&zRec[12], return_code); 671 put32bits(&zRec[16], size); 672 put32bits(&zRec[20], offset); 673 p->nBuf += 24; 674 } 675 676 static void vfslog_string(sqlite3_vfs *pVfs, const char *zStr){ 677 VfslogVfs *p = (VfslogVfs *)pVfs; 678 unsigned char *zRec; 679 int nStr = zStr ? (int)strlen(zStr) : 0; 680 if( (4+nStr+p->nBuf)>sizeof(p->aBuf) ){ 681 vfslog_flush(p); 682 } 683 zRec = (unsigned char *)&p->aBuf[p->nBuf]; 684 put32bits(&zRec[0], nStr); 685 if( zStr ){ 686 memcpy(&zRec[4], zStr, nStr); 687 } 688 p->nBuf += (4 + nStr); 689 } 690 691 static void vfslog_finalize(VfslogVfs *p){ 692 if( p->pLog->pMethods ){ 693 vfslog_flush(p); 694 p->pLog->pMethods->xClose(p->pLog); 695 } 696 sqlite3_free(p); 697 } 698 699 int sqlite3_vfslog_finalize(const char *zVfs){ 700 sqlite3_vfs *pVfs; 701 pVfs = sqlite3_vfs_find(zVfs); 702 if( !pVfs || pVfs->xOpen!=vfslogOpen ){ 703 return SQLITE_ERROR; 704 } 705 sqlite3_vfs_unregister(pVfs); 706 vfslog_finalize((VfslogVfs *)pVfs); 707 return SQLITE_OK; 708 } 709 710 int sqlite3_vfslog_new( 711 const char *zVfs, /* New VFS name */ 712 const char *zParentVfs, /* Parent VFS name (or NULL) */ 713 const char *zLog /* Log file name */ 714 ){ 715 VfslogVfs *p; 716 sqlite3_vfs *pParent; 717 int nByte; 718 int flags; 719 int rc; 720 char *zFile; 721 int nVfs; 722 723 pParent = sqlite3_vfs_find(zParentVfs); 724 if( !pParent ){ 725 return SQLITE_ERROR; 726 } 727 728 nVfs = (int)strlen(zVfs); 729 nByte = sizeof(VfslogVfs) + pParent->szOsFile + nVfs+1+pParent->mxPathname+1; 730 p = (VfslogVfs *)sqlite3_malloc(nByte); 731 memset(p, 0, nByte); 732 733 p->pVfs = pParent; 734 p->pLog = (sqlite3_file *)&p[1]; 735 memcpy(&p->base, &vfslog_vfs, sizeof(sqlite3_vfs)); 736 p->base.zName = &((char *)p->pLog)[pParent->szOsFile]; 737 p->base.szOsFile += pParent->szOsFile; 738 memcpy((char *)p->base.zName, zVfs, nVfs); 739 740 zFile = (char *)&p->base.zName[nVfs+1]; 741 pParent->xFullPathname(pParent, zLog, pParent->mxPathname, zFile); 742 743 flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_MASTER_JOURNAL; 744 pParent->xDelete(pParent, zFile, 0); 745 rc = pParent->xOpen(pParent, zFile, p->pLog, flags, &flags); 746 if( rc==SQLITE_OK ){ 747 memcpy(p->aBuf, "sqlite_ostrace1.....", 20); 748 p->iOffset = 0; 749 p->nBuf = 20; 750 rc = sqlite3_vfs_register((sqlite3_vfs *)p, 1); 751 } 752 if( rc ){ 753 vfslog_finalize(p); 754 } 755 return rc; 756 } 757 758 int sqlite3_vfslog_annotate(const char *zVfs, const char *zMsg){ 759 sqlite3_vfs *pVfs; 760 pVfs = sqlite3_vfs_find(zVfs); 761 if( !pVfs || pVfs->xOpen!=vfslogOpen ){ 762 return SQLITE_ERROR; 763 } 764 vfslog_call(pVfs, OS_ANNOTATE, 0, 0, 0, 0, 0); 765 vfslog_string(pVfs, zMsg); 766 return SQLITE_OK; 767 } 768 769 static const char *vfslog_eventname(int eEvent){ 770 const char *zEvent = 0; 771 772 switch( eEvent ){ 773 case OS_CLOSE: zEvent = "xClose"; break; 774 case OS_READ: zEvent = "xRead"; break; 775 case OS_WRITE: zEvent = "xWrite"; break; 776 case OS_TRUNCATE: zEvent = "xTruncate"; break; 777 case OS_SYNC: zEvent = "xSync"; break; 778 case OS_FILESIZE: zEvent = "xFilesize"; break; 779 case OS_LOCK: zEvent = "xLock"; break; 780 case OS_UNLOCK: zEvent = "xUnlock"; break; 781 case OS_CHECKRESERVEDLOCK: zEvent = "xCheckResLock"; break; 782 case OS_FILECONTROL: zEvent = "xFileControl"; break; 783 case OS_SECTORSIZE: zEvent = "xSectorSize"; break; 784 case OS_DEVCHAR: zEvent = "xDeviceChar"; break; 785 case OS_OPEN: zEvent = "xOpen"; break; 786 case OS_DELETE: zEvent = "xDelete"; break; 787 case OS_ACCESS: zEvent = "xAccess"; break; 788 case OS_FULLPATHNAME: zEvent = "xFullPathname"; break; 789 case OS_RANDOMNESS: zEvent = "xRandomness"; break; 790 case OS_SLEEP: zEvent = "xSleep"; break; 791 case OS_CURRENTTIME: zEvent = "xCurrentTime"; break; 792 793 case OS_SHMUNMAP: zEvent = "xShmUnmap"; break; 794 case OS_SHMLOCK: zEvent = "xShmLock"; break; 795 case OS_SHMBARRIER: zEvent = "xShmBarrier"; break; 796 case OS_SHMMAP: zEvent = "xShmMap"; break; 797 798 case OS_ANNOTATE: zEvent = "annotation"; break; 799 } 800 801 return zEvent; 802 } 803 804 typedef struct VfslogVtab VfslogVtab; 805 typedef struct VfslogCsr VfslogCsr; 806 807 /* 808 ** Virtual table type for the vfslog reader module. 809 */ 810 struct VfslogVtab { 811 sqlite3_vtab base; /* Base class */ 812 sqlite3_file *pFd; /* File descriptor open on vfslog file */ 813 sqlite3_int64 nByte; /* Size of file in bytes */ 814 char *zFile; /* File name for pFd */ 815 }; 816 817 /* 818 ** Virtual table cursor type for the vfslog reader module. 819 */ 820 struct VfslogCsr { 821 sqlite3_vtab_cursor base; /* Base class */ 822 sqlite3_int64 iRowid; /* Current rowid. */ 823 sqlite3_int64 iOffset; /* Offset of next record in file */ 824 char *zTransient; /* Transient 'file' string */ 825 int nFile; /* Size of array azFile[] */ 826 char **azFile; /* File strings */ 827 unsigned char aBuf[1024]; /* Current vfs log entry (read from file) */ 828 }; 829 830 static unsigned int get32bits(unsigned char *p){ 831 return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; 832 } 833 834 /* 835 ** The argument must point to a buffer containing a nul-terminated string. 836 ** If the string begins with an SQL quote character it is overwritten by 837 ** the dequoted version. Otherwise the buffer is left unmodified. 838 */ 839 static void dequote(char *z){ 840 char quote; /* Quote character (if any ) */ 841 quote = z[0]; 842 if( quote=='[' || quote=='\'' || quote=='"' || quote=='`' ){ 843 int iIn = 1; /* Index of next byte to read from input */ 844 int iOut = 0; /* Index of next byte to write to output */ 845 if( quote=='[' ) quote = ']'; 846 while( z[iIn] ){ 847 if( z[iIn]==quote ){ 848 if( z[iIn+1]!=quote ) break; 849 z[iOut++] = quote; 850 iIn += 2; 851 }else{ 852 z[iOut++] = z[iIn++]; 853 } 854 } 855 z[iOut] = '\0'; 856 } 857 } 858 859 #ifndef SQLITE_OMIT_VIRTUALTABLE 860 /* 861 ** Connect to or create a vfslog virtual table. 862 */ 863 static int vlogConnect( 864 sqlite3 *db, 865 void *pAux, 866 int argc, const char *const*argv, 867 sqlite3_vtab **ppVtab, 868 char **pzErr 869 ){ 870 sqlite3_vfs *pVfs; /* VFS used to read log file */ 871 int flags; /* flags passed to pVfs->xOpen() */ 872 VfslogVtab *p; 873 int rc; 874 int nByte; 875 char *zFile; 876 877 *ppVtab = 0; 878 pVfs = sqlite3_vfs_find(0); 879 nByte = sizeof(VfslogVtab) + pVfs->szOsFile + pVfs->mxPathname; 880 p = sqlite3_malloc(nByte); 881 if( p==0 ) return SQLITE_NOMEM; 882 memset(p, 0, nByte); 883 884 p->pFd = (sqlite3_file *)&p[1]; 885 p->zFile = &((char *)p->pFd)[pVfs->szOsFile]; 886 887 zFile = sqlite3_mprintf("%s", argv[3]); 888 if( !zFile ){ 889 sqlite3_free(p); 890 return SQLITE_NOMEM; 891 } 892 dequote(zFile); 893 pVfs->xFullPathname(pVfs, zFile, pVfs->mxPathname, p->zFile); 894 sqlite3_free(zFile); 895 896 flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_MASTER_JOURNAL; 897 rc = pVfs->xOpen(pVfs, p->zFile, p->pFd, flags, &flags); 898 899 if( rc==SQLITE_OK ){ 900 p->pFd->pMethods->xFileSize(p->pFd, &p->nByte); 901 sqlite3_declare_vtab(db, 902 "CREATE TABLE xxx(event, file, click, rc, size, offset)" 903 ); 904 *ppVtab = &p->base; 905 }else{ 906 sqlite3_free(p); 907 } 908 909 return rc; 910 } 911 912 /* 913 ** There is no "best-index". This virtual table always does a linear 914 ** scan of the binary VFS log file. 915 */ 916 static int vlogBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ 917 pIdxInfo->estimatedCost = 10.0; 918 return SQLITE_OK; 919 } 920 921 /* 922 ** Disconnect from or destroy a vfslog virtual table. 923 */ 924 static int vlogDisconnect(sqlite3_vtab *pVtab){ 925 VfslogVtab *p = (VfslogVtab *)pVtab; 926 if( p->pFd->pMethods ){ 927 p->pFd->pMethods->xClose(p->pFd); 928 p->pFd->pMethods = 0; 929 } 930 sqlite3_free(p); 931 return SQLITE_OK; 932 } 933 934 /* 935 ** Open a new vfslog cursor. 936 */ 937 static int vlogOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ 938 VfslogCsr *pCsr; /* Newly allocated cursor object */ 939 940 pCsr = sqlite3_malloc(sizeof(VfslogCsr)); 941 if( !pCsr ) return SQLITE_NOMEM; 942 memset(pCsr, 0, sizeof(VfslogCsr)); 943 *ppCursor = &pCsr->base; 944 return SQLITE_OK; 945 } 946 947 /* 948 ** Close a vfslog cursor. 949 */ 950 static int vlogClose(sqlite3_vtab_cursor *pCursor){ 951 VfslogCsr *p = (VfslogCsr *)pCursor; 952 int i; 953 for(i=0; i<p->nFile; i++){ 954 sqlite3_free(p->azFile[i]); 955 } 956 sqlite3_free(p->azFile); 957 sqlite3_free(p->zTransient); 958 sqlite3_free(p); 959 return SQLITE_OK; 960 } 961 962 /* 963 ** Move a vfslog cursor to the next entry in the file. 964 */ 965 static int vlogNext(sqlite3_vtab_cursor *pCursor){ 966 VfslogCsr *pCsr = (VfslogCsr *)pCursor; 967 VfslogVtab *p = (VfslogVtab *)pCursor->pVtab; 968 int rc = SQLITE_OK; 969 int nRead; 970 971 sqlite3_free(pCsr->zTransient); 972 pCsr->zTransient = 0; 973 974 nRead = 24; 975 if( pCsr->iOffset+nRead<=p->nByte ){ 976 int eEvent; 977 rc = p->pFd->pMethods->xRead(p->pFd, pCsr->aBuf, nRead, pCsr->iOffset); 978 979 eEvent = get32bits(pCsr->aBuf); 980 if( (rc==SQLITE_OK) 981 && (eEvent==OS_OPEN || eEvent==OS_DELETE || eEvent==OS_ACCESS) 982 ){ 983 char buf[4]; 984 rc = p->pFd->pMethods->xRead(p->pFd, buf, 4, pCsr->iOffset+nRead); 985 nRead += 4; 986 if( rc==SQLITE_OK ){ 987 int nStr = get32bits((unsigned char *)buf); 988 char *zStr = sqlite3_malloc(nStr+1); 989 rc = p->pFd->pMethods->xRead(p->pFd, zStr, nStr, pCsr->iOffset+nRead); 990 zStr[nStr] = '\0'; 991 nRead += nStr; 992 993 if( eEvent==OS_OPEN ){ 994 int iFileid = get32bits(&pCsr->aBuf[4]); 995 if( iFileid>=pCsr->nFile ){ 996 int nNew = sizeof(pCsr->azFile[0])*(iFileid+1); 997 pCsr->azFile = (char **)sqlite3_realloc(pCsr->azFile, nNew); 998 nNew -= sizeof(pCsr->azFile[0])*pCsr->nFile; 999 memset(&pCsr->azFile[pCsr->nFile], 0, nNew); 1000 pCsr->nFile = iFileid+1; 1001 } 1002 sqlite3_free(pCsr->azFile[iFileid]); 1003 pCsr->azFile[iFileid] = zStr; 1004 }else{ 1005 pCsr->zTransient = zStr; 1006 } 1007 } 1008 } 1009 } 1010 1011 pCsr->iRowid += 1; 1012 pCsr->iOffset += nRead; 1013 return rc; 1014 } 1015 1016 static int vlogEof(sqlite3_vtab_cursor *pCursor){ 1017 VfslogCsr *pCsr = (VfslogCsr *)pCursor; 1018 VfslogVtab *p = (VfslogVtab *)pCursor->pVtab; 1019 return (pCsr->iOffset>=p->nByte); 1020 } 1021 1022 static int vlogFilter( 1023 sqlite3_vtab_cursor *pCursor, 1024 int idxNum, const char *idxStr, 1025 int argc, sqlite3_value **argv 1026 ){ 1027 VfslogCsr *pCsr = (VfslogCsr *)pCursor; 1028 pCsr->iRowid = 0; 1029 pCsr->iOffset = 20; 1030 return vlogNext(pCursor); 1031 } 1032 1033 static int vlogColumn( 1034 sqlite3_vtab_cursor *pCursor, 1035 sqlite3_context *ctx, 1036 int i 1037 ){ 1038 unsigned int val; 1039 VfslogCsr *pCsr = (VfslogCsr *)pCursor; 1040 1041 assert( i<7 ); 1042 val = get32bits(&pCsr->aBuf[4*i]); 1043 1044 switch( i ){ 1045 case 0: { 1046 sqlite3_result_text(ctx, vfslog_eventname(val), -1, SQLITE_STATIC); 1047 break; 1048 } 1049 case 1: { 1050 char *zStr = pCsr->zTransient; 1051 if( val!=0 && val<(unsigned)pCsr->nFile ){ 1052 zStr = pCsr->azFile[val]; 1053 } 1054 sqlite3_result_text(ctx, zStr, -1, SQLITE_TRANSIENT); 1055 break; 1056 } 1057 default: 1058 sqlite3_result_int(ctx, val); 1059 break; 1060 } 1061 1062 return SQLITE_OK; 1063 } 1064 1065 static int vlogRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ 1066 VfslogCsr *pCsr = (VfslogCsr *)pCursor; 1067 *pRowid = pCsr->iRowid; 1068 return SQLITE_OK; 1069 } 1070 1071 int sqlite3_vfslog_register(sqlite3 *db){ 1072 static sqlite3_module vfslog_module = { 1073 0, /* iVersion */ 1074 vlogConnect, /* xCreate */ 1075 vlogConnect, /* xConnect */ 1076 vlogBestIndex, /* xBestIndex */ 1077 vlogDisconnect, /* xDisconnect */ 1078 vlogDisconnect, /* xDestroy */ 1079 vlogOpen, /* xOpen - open a cursor */ 1080 vlogClose, /* xClose - close a cursor */ 1081 vlogFilter, /* xFilter - configure scan constraints */ 1082 vlogNext, /* xNext - advance a cursor */ 1083 vlogEof, /* xEof - check for end of scan */ 1084 vlogColumn, /* xColumn - read data */ 1085 vlogRowid, /* xRowid - read data */ 1086 0, /* xUpdate */ 1087 0, /* xBegin */ 1088 0, /* xSync */ 1089 0, /* xCommit */ 1090 0, /* xRollback */ 1091 0, /* xFindMethod */ 1092 0, /* xRename */ 1093 }; 1094 1095 sqlite3_create_module(db, "vfslog", &vfslog_module, 0); 1096 return SQLITE_OK; 1097 } 1098 #endif /* SQLITE_OMIT_VIRTUALTABLE */ 1099 1100 /************************************************************************** 1101 *************************************************************************** 1102 ** Tcl interface starts here. 1103 */ 1104 1105 #if defined(SQLITE_TEST) || defined(TCLSH) 1106 1107 #if defined(INCLUDE_SQLITE_TCL_H) 1108 # include "sqlite_tcl.h" 1109 #else 1110 # include "tcl.h" 1111 # ifndef SQLITE_TCLAPI 1112 # define SQLITE_TCLAPI 1113 # endif 1114 #endif 1115 1116 static int SQLITE_TCLAPI test_vfslog( 1117 void *clientData, 1118 Tcl_Interp *interp, 1119 int objc, 1120 Tcl_Obj *CONST objv[] 1121 ){ 1122 struct SqliteDb { sqlite3 *db; }; 1123 sqlite3 *db; 1124 Tcl_CmdInfo cmdInfo; 1125 int rc = SQLITE_ERROR; 1126 1127 static const char *strs[] = { "annotate", "finalize", "new", "register", 0 }; 1128 enum VL_enum { VL_ANNOTATE, VL_FINALIZE, VL_NEW, VL_REGISTER }; 1129 int iSub; 1130 1131 if( objc<2 ){ 1132 Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND ..."); 1133 return TCL_ERROR; 1134 } 1135 if( Tcl_GetIndexFromObj(interp, objv[1], strs, "sub-command", 0, &iSub) ){ 1136 return TCL_ERROR; 1137 } 1138 1139 switch( (enum VL_enum)iSub ){ 1140 case VL_ANNOTATE: { 1141 char *zVfs; 1142 char *zMsg; 1143 if( objc!=4 ){ 1144 Tcl_WrongNumArgs(interp, 3, objv, "VFS"); 1145 return TCL_ERROR; 1146 } 1147 zVfs = Tcl_GetString(objv[2]); 1148 zMsg = Tcl_GetString(objv[3]); 1149 rc = sqlite3_vfslog_annotate(zVfs, zMsg); 1150 if( rc!=SQLITE_OK ){ 1151 Tcl_AppendResult(interp, "failed", 0); 1152 return TCL_ERROR; 1153 } 1154 break; 1155 } 1156 case VL_FINALIZE: { 1157 char *zVfs; 1158 if( objc!=3 ){ 1159 Tcl_WrongNumArgs(interp, 2, objv, "VFS"); 1160 return TCL_ERROR; 1161 } 1162 zVfs = Tcl_GetString(objv[2]); 1163 rc = sqlite3_vfslog_finalize(zVfs); 1164 if( rc!=SQLITE_OK ){ 1165 Tcl_AppendResult(interp, "failed", 0); 1166 return TCL_ERROR; 1167 } 1168 break; 1169 }; 1170 1171 case VL_NEW: { 1172 char *zVfs; 1173 char *zParent; 1174 char *zLog; 1175 if( objc!=5 ){ 1176 Tcl_WrongNumArgs(interp, 2, objv, "VFS PARENT LOGFILE"); 1177 return TCL_ERROR; 1178 } 1179 zVfs = Tcl_GetString(objv[2]); 1180 zParent = Tcl_GetString(objv[3]); 1181 zLog = Tcl_GetString(objv[4]); 1182 if( *zParent=='\0' ) zParent = 0; 1183 rc = sqlite3_vfslog_new(zVfs, zParent, zLog); 1184 if( rc!=SQLITE_OK ){ 1185 Tcl_AppendResult(interp, "failed", 0); 1186 return TCL_ERROR; 1187 } 1188 break; 1189 }; 1190 1191 case VL_REGISTER: { 1192 char *zDb; 1193 if( objc!=3 ){ 1194 Tcl_WrongNumArgs(interp, 2, objv, "DB"); 1195 return TCL_ERROR; 1196 } 1197 #ifdef SQLITE_OMIT_VIRTUALTABLE 1198 Tcl_AppendResult(interp, "vfslog not available because of " 1199 "SQLITE_OMIT_VIRTUALTABLE", (void*)0); 1200 return TCL_ERROR; 1201 #else 1202 zDb = Tcl_GetString(objv[2]); 1203 if( Tcl_GetCommandInfo(interp, zDb, &cmdInfo) ){ 1204 db = ((struct SqliteDb*)cmdInfo.objClientData)->db; 1205 rc = sqlite3_vfslog_register(db); 1206 } 1207 if( rc!=SQLITE_OK ){ 1208 Tcl_AppendResult(interp, "bad sqlite3 handle: ", zDb, (void*)0); 1209 return TCL_ERROR; 1210 } 1211 break; 1212 #endif 1213 } 1214 } 1215 1216 return TCL_OK; 1217 } 1218 1219 int SqlitetestOsinst_Init(Tcl_Interp *interp){ 1220 Tcl_CreateObjCommand(interp, "vfslog", test_vfslog, 0, 0); 1221 return TCL_OK; 1222 } 1223 1224 #endif /* SQLITE_TEST */ 1225