1*8cf35eb4Sdan /* 2*8cf35eb4Sdan ** 2010 September 31 3*8cf35eb4Sdan ** 4*8cf35eb4Sdan ** The author disclaims copyright to this source code. In place of 5*8cf35eb4Sdan ** a legal notice, here is a blessing: 6*8cf35eb4Sdan ** 7*8cf35eb4Sdan ** May you do good and not evil. 8*8cf35eb4Sdan ** May you find forgiveness for yourself and forgive others. 9*8cf35eb4Sdan ** May you share freely, never taking more than you give. 10*8cf35eb4Sdan ** 11*8cf35eb4Sdan ************************************************************************* 12*8cf35eb4Sdan ** 13*8cf35eb4Sdan ** This file contains a VFS "shim" - a layer that sits in between the 14*8cf35eb4Sdan ** pager and the real VFS. 15*8cf35eb4Sdan ** 16*8cf35eb4Sdan ** This particular shim enforces a quota system on files. One or more 17*8cf35eb4Sdan ** database files are in a "quota group" that is defined by a GLOB 18*8cf35eb4Sdan ** pattern. A quota is set for the combined size of all files in the 19*8cf35eb4Sdan ** the group. A quota of zero means "no limit". If the total size 20*8cf35eb4Sdan ** of all files in the quota group is met or exceeded, then new 21*8cf35eb4Sdan ** write requests that attempt to enlarge a file fail with SQLITE_FULL. 22*8cf35eb4Sdan ** 23*8cf35eb4Sdan ** However, before returning SQLITE_FULL, the write requests invoke 24*8cf35eb4Sdan ** a callback function that is configurable for each quota group. 25*8cf35eb4Sdan ** This callback has the opportunity to enlarge the quota. If the 26*8cf35eb4Sdan ** callback does enlarge the quota such that the total size of all 27*8cf35eb4Sdan ** files within the group is less than the new quota, then the write 28*8cf35eb4Sdan ** continues as if nothing had happened. 29*8cf35eb4Sdan */ 30*8cf35eb4Sdan #include "sqlite3.h" 31*8cf35eb4Sdan #include <string.h> 32*8cf35eb4Sdan #include <assert.h> 33*8cf35eb4Sdan 34*8cf35eb4Sdan /************************ Object Definitions ******************************/ 35*8cf35eb4Sdan 36*8cf35eb4Sdan /* 37*8cf35eb4Sdan ** This module contains a table of filename patterns that have size 38*8cf35eb4Sdan ** quotas. The quota applies to the sum of the sizes of all open 39*8cf35eb4Sdan ** database files whose names match the GLOB pattern. 40*8cf35eb4Sdan ** 41*8cf35eb4Sdan ** Each quota is an instance of the following object. Quotas must 42*8cf35eb4Sdan ** be established (using sqlite3_quota_set()) prior to opening any 43*8cf35eb4Sdan ** of the database connections that access files governed by the 44*8cf35eb4Sdan ** quota. 45*8cf35eb4Sdan ** 46*8cf35eb4Sdan ** Each entry in the quota table is an instance of the following object. 47*8cf35eb4Sdan */ 48*8cf35eb4Sdan typedef struct quotaGroup quotaGroup; 49*8cf35eb4Sdan struct quotaGroup { 50*8cf35eb4Sdan const char *zPattern; /* Filename pattern to be quotaed */ 51*8cf35eb4Sdan sqlite3_int64 iLimit; /* Upper bound on total file size */ 52*8cf35eb4Sdan sqlite3_int64 iSize; /* Current size of all files */ 53*8cf35eb4Sdan void (*xCallback)( /* Callback invoked when going over quota */ 54*8cf35eb4Sdan const char *zFilename, /* Name of file whose size increases */ 55*8cf35eb4Sdan sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ 56*8cf35eb4Sdan sqlite3_int64 iSize, /* Total size of all files in the group */ 57*8cf35eb4Sdan void *pArg /* Client data */ 58*8cf35eb4Sdan ); 59*8cf35eb4Sdan void *pArg; /* Third argument to the xCallback() */ 60*8cf35eb4Sdan int nRef; /* Number of files in the group references. */ 61*8cf35eb4Sdan quotaGroup *pNext, **ppPrev; /* Doubly linked list of all quota objects */ 62*8cf35eb4Sdan }; 63*8cf35eb4Sdan 64*8cf35eb4Sdan /* 65*8cf35eb4Sdan ** An instance of the following object represents each file that 66*8cf35eb4Sdan ** participates in quota tracking. The sqlite3_file object for the 67*8cf35eb4Sdan ** underlying VFS is appended to this structure. 68*8cf35eb4Sdan */ 69*8cf35eb4Sdan typedef struct quotaFile quotaFile; 70*8cf35eb4Sdan struct quotaFile { 71*8cf35eb4Sdan sqlite3_file base; /* Base class - must be first */ 72*8cf35eb4Sdan const char *zFilename; /* Name of this file */ 73*8cf35eb4Sdan quotaGroup *pGroup; /* Upper bound on file size */ 74*8cf35eb4Sdan sqlite3_int64 iSize; /* Current size of this file */ 75*8cf35eb4Sdan /* The underlying VFS sqlite3_file is appended to this object */ 76*8cf35eb4Sdan }; 77*8cf35eb4Sdan 78*8cf35eb4Sdan /************************* Global Variables **********************************/ 79*8cf35eb4Sdan /* 80*8cf35eb4Sdan ** All global variables used by this file are containing within the following 81*8cf35eb4Sdan ** gQuota structure. 82*8cf35eb4Sdan */ 83*8cf35eb4Sdan static struct { 84*8cf35eb4Sdan /* The pOrigVfs is a pointer to the real underlying VFS implementation. 85*8cf35eb4Sdan ** Most operations pass-through to the real VFS. This value is read-only 86*8cf35eb4Sdan ** during operation. It is only modified at start-time and thus does not 87*8cf35eb4Sdan ** require a mutex. 88*8cf35eb4Sdan */ 89*8cf35eb4Sdan sqlite3_vfs *pOrigVfs; 90*8cf35eb4Sdan 91*8cf35eb4Sdan /* The sThisVfs is the VFS structure used by this shim. It is initialized 92*8cf35eb4Sdan ** at start-time and thus does not require a mutex 93*8cf35eb4Sdan */ 94*8cf35eb4Sdan sqlite3_vfs sThisVfs; 95*8cf35eb4Sdan 96*8cf35eb4Sdan /* The sIoMethods defines the methods used by sqlite3_file objects 97*8cf35eb4Sdan ** associated with this shim. It is initialized at start-time and does 98*8cf35eb4Sdan ** not require a mutex. 99*8cf35eb4Sdan ** 100*8cf35eb4Sdan ** When the underlying VFS is called to open a file, it might return 101*8cf35eb4Sdan ** either a version 1 or a version 2 sqlite3_file object. This shim 102*8cf35eb4Sdan ** has to create a wrapper sqlite3_file of the same version. Hence 103*8cf35eb4Sdan ** there are two I/O method structures, one for version 1 and the other 104*8cf35eb4Sdan ** for version 2. 105*8cf35eb4Sdan */ 106*8cf35eb4Sdan sqlite3_io_methods sIoMethodsV1; 107*8cf35eb4Sdan sqlite3_io_methods sIoMethodsV2; 108*8cf35eb4Sdan 109*8cf35eb4Sdan /* True when this shim as been initialized. 110*8cf35eb4Sdan */ 111*8cf35eb4Sdan int isInitialized; 112*8cf35eb4Sdan 113*8cf35eb4Sdan /* For run-time access any of the other global data structures in this 114*8cf35eb4Sdan ** shim, the following mutex must be held. 115*8cf35eb4Sdan */ 116*8cf35eb4Sdan sqlite3_mutex *pMutex; 117*8cf35eb4Sdan 118*8cf35eb4Sdan /* List of quotaGroup objects. 119*8cf35eb4Sdan */ 120*8cf35eb4Sdan quotaGroup *pGroup; 121*8cf35eb4Sdan 122*8cf35eb4Sdan } gQuota; 123*8cf35eb4Sdan 124*8cf35eb4Sdan /************************* Utility Routines *********************************/ 125*8cf35eb4Sdan /* 126*8cf35eb4Sdan ** Acquire and release the mutex used to serialize access to the 127*8cf35eb4Sdan ** list of quotaGroups. 128*8cf35eb4Sdan */ 129*8cf35eb4Sdan static void quotaEnter(void){ sqlite3_mutex_enter(gQuota.pMutex); } 130*8cf35eb4Sdan static void quotaLeave(void){ sqlite3_mutex_leave(gQuota.pMutex); } 131*8cf35eb4Sdan 132*8cf35eb4Sdan 133*8cf35eb4Sdan /* If the reference count and threshold for a quotaGroup are both 134*8cf35eb4Sdan ** zero, then destroy the quotaGroup. 135*8cf35eb4Sdan */ 136*8cf35eb4Sdan static void quotaGroupDeref(quotaGroup *p){ 137*8cf35eb4Sdan if( p->nRef==0 && p->iLimit==0 ){ 138*8cf35eb4Sdan if( p->pNext ) p->pNext->ppPrev = p->ppPrev; 139*8cf35eb4Sdan if( p->ppPrev ) *p->ppPrev = p->pNext; 140*8cf35eb4Sdan sqlite3_free(p); 141*8cf35eb4Sdan } 142*8cf35eb4Sdan } 143*8cf35eb4Sdan 144*8cf35eb4Sdan /* 145*8cf35eb4Sdan ** Return TRUE if string z matches glob pattern zGlob. 146*8cf35eb4Sdan ** 147*8cf35eb4Sdan ** Globbing rules: 148*8cf35eb4Sdan ** 149*8cf35eb4Sdan ** '*' Matches any sequence of zero or more characters. 150*8cf35eb4Sdan ** 151*8cf35eb4Sdan ** '?' Matches exactly one character. 152*8cf35eb4Sdan ** 153*8cf35eb4Sdan ** [...] Matches one character from the enclosed list of 154*8cf35eb4Sdan ** characters. 155*8cf35eb4Sdan ** 156*8cf35eb4Sdan ** [^...] Matches one character not in the enclosed list. 157*8cf35eb4Sdan ** 158*8cf35eb4Sdan */ 159*8cf35eb4Sdan static int strglob(const char *zGlob, const char *z){ 160*8cf35eb4Sdan int c, c2; 161*8cf35eb4Sdan int invert; 162*8cf35eb4Sdan int seen; 163*8cf35eb4Sdan 164*8cf35eb4Sdan while( (c = (*(zGlob++)))!=0 ){ 165*8cf35eb4Sdan if( c=='*' ){ 166*8cf35eb4Sdan while( (c=(*(zGlob++))) == '*' || c=='?' ){ 167*8cf35eb4Sdan if( c=='?' && (*(z++))==0 ) return 0; 168*8cf35eb4Sdan } 169*8cf35eb4Sdan if( c==0 ){ 170*8cf35eb4Sdan return 1; 171*8cf35eb4Sdan }else if( c=='[' ){ 172*8cf35eb4Sdan while( *z && strglob(zGlob-1,z)==0 ){ 173*8cf35eb4Sdan z++; 174*8cf35eb4Sdan } 175*8cf35eb4Sdan return (*z)!=0; 176*8cf35eb4Sdan } 177*8cf35eb4Sdan while( (c2 = (*(z++)))!=0 ){ 178*8cf35eb4Sdan while( c2!=c ){ 179*8cf35eb4Sdan c2 = *(z++); 180*8cf35eb4Sdan if( c2==0 ) return 0; 181*8cf35eb4Sdan } 182*8cf35eb4Sdan if( strglob(zGlob,z) ) return 1; 183*8cf35eb4Sdan } 184*8cf35eb4Sdan return 0; 185*8cf35eb4Sdan }else if( c=='?' ){ 186*8cf35eb4Sdan if( (*(z++))==0 ) return 0; 187*8cf35eb4Sdan }else if( c=='[' ){ 188*8cf35eb4Sdan int prior_c = 0; 189*8cf35eb4Sdan seen = 0; 190*8cf35eb4Sdan invert = 0; 191*8cf35eb4Sdan c = *(z++); 192*8cf35eb4Sdan if( c==0 ) return 0; 193*8cf35eb4Sdan c2 = *(zGlob++); 194*8cf35eb4Sdan if( c2=='^' ){ 195*8cf35eb4Sdan invert = 1; 196*8cf35eb4Sdan c2 = *(zGlob++); 197*8cf35eb4Sdan } 198*8cf35eb4Sdan if( c2==']' ){ 199*8cf35eb4Sdan if( c==']' ) seen = 1; 200*8cf35eb4Sdan c2 = *(zGlob++); 201*8cf35eb4Sdan } 202*8cf35eb4Sdan while( c2 && c2!=']' ){ 203*8cf35eb4Sdan if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){ 204*8cf35eb4Sdan c2 = *(zGlob++); 205*8cf35eb4Sdan if( c>=prior_c && c<=c2 ) seen = 1; 206*8cf35eb4Sdan prior_c = 0; 207*8cf35eb4Sdan }else{ 208*8cf35eb4Sdan if( c==c2 ){ 209*8cf35eb4Sdan seen = 1; 210*8cf35eb4Sdan } 211*8cf35eb4Sdan prior_c = c2; 212*8cf35eb4Sdan } 213*8cf35eb4Sdan c2 = *(zGlob++); 214*8cf35eb4Sdan } 215*8cf35eb4Sdan if( c2==0 || (seen ^ invert)==0 ) return 0; 216*8cf35eb4Sdan }else{ 217*8cf35eb4Sdan if( c!=(*(z++)) ) return 0; 218*8cf35eb4Sdan } 219*8cf35eb4Sdan } 220*8cf35eb4Sdan return *z==0; 221*8cf35eb4Sdan } 222*8cf35eb4Sdan 223*8cf35eb4Sdan 224*8cf35eb4Sdan /* Find a quotaGroup given the filename. 225*8cf35eb4Sdan ** 226*8cf35eb4Sdan ** Return a pointer to the quotaFile object. Return NULL if not found. 227*8cf35eb4Sdan */ 228*8cf35eb4Sdan static quotaGroup *quotaGroupFind(const char *zFilename){ 229*8cf35eb4Sdan quotaGroup *p; 230*8cf35eb4Sdan for(p=gQuota.pGroup; p && strglob(p->zPattern, zFilename)==0; p=p->pNext){} 231*8cf35eb4Sdan return p; 232*8cf35eb4Sdan } 233*8cf35eb4Sdan 234*8cf35eb4Sdan /* Translate an sqlite3_file* that is really a quotaFile* into 235*8cf35eb4Sdan ** an sqlite3_file* for the underlying original VFS. 236*8cf35eb4Sdan */ 237*8cf35eb4Sdan static sqlite3_file *quotaSubFile(sqlite3_file *pFile){ 238*8cf35eb4Sdan quotaFile *p = (quotaFile*)pFile; 239*8cf35eb4Sdan return (sqlite3_file*)&p[1]; 240*8cf35eb4Sdan } 241*8cf35eb4Sdan 242*8cf35eb4Sdan /************************* VFS Method Wrappers *****************************/ 243*8cf35eb4Sdan /* 244*8cf35eb4Sdan ** This is the xOpen method used for the "quota" VFS. 245*8cf35eb4Sdan ** 246*8cf35eb4Sdan ** Most of the work is done by the underlying original VFS. This method 247*8cf35eb4Sdan ** simply links the new file into the quota group if it is a file that 248*8cf35eb4Sdan ** needs to be tracked. 249*8cf35eb4Sdan */ 250*8cf35eb4Sdan static int quotaOpen( 251*8cf35eb4Sdan sqlite3_vfs *pVfs, 252*8cf35eb4Sdan const char *zName, 253*8cf35eb4Sdan sqlite3_file *pFile, 254*8cf35eb4Sdan int flags, 255*8cf35eb4Sdan int *pOutFlags 256*8cf35eb4Sdan ){ 257*8cf35eb4Sdan int rc; 258*8cf35eb4Sdan quotaFile *pQuotaFile; 259*8cf35eb4Sdan quotaGroup *pGroup; 260*8cf35eb4Sdan sqlite3_file *pSubFile; 261*8cf35eb4Sdan sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs; 262*8cf35eb4Sdan 263*8cf35eb4Sdan /* If the file is not a main database file or a WAL, then use the 264*8cf35eb4Sdan ** normal xOpen method. 265*8cf35eb4Sdan */ 266*8cf35eb4Sdan if( (flags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_WAL))==0 ){ 267*8cf35eb4Sdan return pOrigVfs->xOpen(pOrigVfs, zName, pFile, flags, pOutFlags); 268*8cf35eb4Sdan } 269*8cf35eb4Sdan 270*8cf35eb4Sdan /* If the name of the file does not match any quota group, then 271*8cf35eb4Sdan ** use the normal xOpen method. 272*8cf35eb4Sdan */ 273*8cf35eb4Sdan quotaEnter(); 274*8cf35eb4Sdan pGroup = quotaGroupFind(zName); 275*8cf35eb4Sdan if( pGroup==0 ){ 276*8cf35eb4Sdan rc = pOrigVfs->xOpen(pOrigVfs, zName, pFile, flags, pOutFlags); 277*8cf35eb4Sdan }else{ 278*8cf35eb4Sdan /* If we get to this point, it means the file needs to be quota tracked. 279*8cf35eb4Sdan */ 280*8cf35eb4Sdan pQuotaFile = (quotaFile*)pFile; 281*8cf35eb4Sdan pSubFile = quotaSubFile(pFile); 282*8cf35eb4Sdan rc = pOrigVfs->xOpen(pOrigVfs, zName, pSubFile, flags, pOutFlags); 283*8cf35eb4Sdan if( rc==SQLITE_OK ){ 284*8cf35eb4Sdan pQuotaFile->iSize = 0; 285*8cf35eb4Sdan pQuotaFile->pGroup = pGroup; 286*8cf35eb4Sdan pGroup->nRef++; 287*8cf35eb4Sdan pQuotaFile->zFilename = zName; 288*8cf35eb4Sdan if( pSubFile->pMethods->iVersion==1 ){ 289*8cf35eb4Sdan pQuotaFile->base.pMethods = &gQuota.sIoMethodsV1; 290*8cf35eb4Sdan }else{ 291*8cf35eb4Sdan pQuotaFile->base.pMethods = &gQuota.sIoMethodsV2; 292*8cf35eb4Sdan } 293*8cf35eb4Sdan } 294*8cf35eb4Sdan } 295*8cf35eb4Sdan quotaLeave(); 296*8cf35eb4Sdan return rc; 297*8cf35eb4Sdan } 298*8cf35eb4Sdan 299*8cf35eb4Sdan /************************ I/O Method Wrappers *******************************/ 300*8cf35eb4Sdan 301*8cf35eb4Sdan /* xClose requests get passed through to the original VFS. But we 302*8cf35eb4Sdan ** also have to unlink the quotaFile from the quotaGroup. 303*8cf35eb4Sdan */ 304*8cf35eb4Sdan static int quotaClose(sqlite3_file *pFile){ 305*8cf35eb4Sdan quotaFile *p = (quotaFile*)pFile; 306*8cf35eb4Sdan quotaGroup *pGroup = p->pGroup; 307*8cf35eb4Sdan sqlite3_file *pSubFile = quotaSubFile(pFile); 308*8cf35eb4Sdan int rc; 309*8cf35eb4Sdan rc = pSubFile->pMethods->xClose(pSubFile); 310*8cf35eb4Sdan quotaEnter(); 311*8cf35eb4Sdan pGroup->nRef--; 312*8cf35eb4Sdan pGroup->iSize -= p->iSize; 313*8cf35eb4Sdan quotaGroupDeref(pGroup); 314*8cf35eb4Sdan quotaLeave(); 315*8cf35eb4Sdan return rc; 316*8cf35eb4Sdan } 317*8cf35eb4Sdan 318*8cf35eb4Sdan /* Pass xRead requests directory thru to the original VFS without 319*8cf35eb4Sdan ** further processing. 320*8cf35eb4Sdan */ 321*8cf35eb4Sdan static int quotaRead( 322*8cf35eb4Sdan sqlite3_file *pFile, 323*8cf35eb4Sdan void *pBuf, 324*8cf35eb4Sdan int iAmt, 325*8cf35eb4Sdan sqlite3_int64 iOfst 326*8cf35eb4Sdan ){ 327*8cf35eb4Sdan sqlite3_file *pSubFile = quotaSubFile(pFile); 328*8cf35eb4Sdan return pSubFile->pMethods->xRead(pSubFile, pBuf, iAmt, iOfst); 329*8cf35eb4Sdan } 330*8cf35eb4Sdan 331*8cf35eb4Sdan /* Check xWrite requests to see if they expand the file. If they do, 332*8cf35eb4Sdan ** the perform a quota check before passing them through to the 333*8cf35eb4Sdan ** original VFS. 334*8cf35eb4Sdan */ 335*8cf35eb4Sdan static int quotaWrite( 336*8cf35eb4Sdan sqlite3_file *pFile, 337*8cf35eb4Sdan const void *pBuf, 338*8cf35eb4Sdan int iAmt, 339*8cf35eb4Sdan sqlite3_int64 iOfst 340*8cf35eb4Sdan ){ 341*8cf35eb4Sdan quotaFile *p = (quotaFile*)pFile; 342*8cf35eb4Sdan sqlite3_file *pSubFile = quotaSubFile(pFile); 343*8cf35eb4Sdan sqlite3_int64 iEnd = iOfst+iAmt; 344*8cf35eb4Sdan quotaGroup *pGroup; 345*8cf35eb4Sdan sqlite3_int64 szNew; 346*8cf35eb4Sdan 347*8cf35eb4Sdan if( p->iSize<iEnd ){ 348*8cf35eb4Sdan pGroup = p->pGroup; 349*8cf35eb4Sdan quotaEnter(); 350*8cf35eb4Sdan szNew = pGroup->iSize - p->iSize + iEnd; 351*8cf35eb4Sdan if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ 352*8cf35eb4Sdan if( pGroup->xCallback ){ 353*8cf35eb4Sdan pGroup->xCallback(p->zFilename, &pGroup->iLimit, szNew, 354*8cf35eb4Sdan pGroup->pArg); 355*8cf35eb4Sdan } 356*8cf35eb4Sdan if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ 357*8cf35eb4Sdan quotaLeave(); 358*8cf35eb4Sdan return SQLITE_FULL; 359*8cf35eb4Sdan } 360*8cf35eb4Sdan } 361*8cf35eb4Sdan pGroup->iSize = szNew; 362*8cf35eb4Sdan p->iSize = iEnd; 363*8cf35eb4Sdan quotaLeave(); 364*8cf35eb4Sdan } 365*8cf35eb4Sdan return pSubFile->pMethods->xWrite(pSubFile, pBuf, iAmt, iOfst); 366*8cf35eb4Sdan } 367*8cf35eb4Sdan 368*8cf35eb4Sdan /* Pass xTruncate requests thru to the original VFS. If the 369*8cf35eb4Sdan ** success, update the file size. 370*8cf35eb4Sdan */ 371*8cf35eb4Sdan static int quotaTruncate(sqlite3_file *pFile, sqlite3_int64 size){ 372*8cf35eb4Sdan quotaFile *p = (quotaFile*)pFile; 373*8cf35eb4Sdan sqlite3_file *pSubFile = quotaSubFile(pFile); 374*8cf35eb4Sdan int rc = pSubFile->pMethods->xTruncate(pSubFile, size); 375*8cf35eb4Sdan quotaGroup *pGroup = p->pGroup; 376*8cf35eb4Sdan if( rc==SQLITE_OK ){ 377*8cf35eb4Sdan quotaEnter(); 378*8cf35eb4Sdan pGroup->iSize -= p->iSize; 379*8cf35eb4Sdan p->iSize = size; 380*8cf35eb4Sdan pGroup->iSize += size; 381*8cf35eb4Sdan quotaLeave(); 382*8cf35eb4Sdan } 383*8cf35eb4Sdan return rc; 384*8cf35eb4Sdan } 385*8cf35eb4Sdan 386*8cf35eb4Sdan /* Pass xSync requests through to the original VFS without change 387*8cf35eb4Sdan */ 388*8cf35eb4Sdan static int quotaSync(sqlite3_file *pFile, int flags){ 389*8cf35eb4Sdan sqlite3_file *pSubFile = quotaSubFile(pFile); 390*8cf35eb4Sdan return pSubFile->pMethods->xSync(pSubFile, flags); 391*8cf35eb4Sdan } 392*8cf35eb4Sdan 393*8cf35eb4Sdan /* Pass xFileSize requests through to the original VFS but then 394*8cf35eb4Sdan ** update the quotaGroup with the new size before returning. 395*8cf35eb4Sdan */ 396*8cf35eb4Sdan static int quotaFileSize(sqlite3_file *pFile, sqlite3_int64 *pSize){ 397*8cf35eb4Sdan quotaFile *p = (quotaFile*)pFile; 398*8cf35eb4Sdan sqlite3_file *pSubFile = quotaSubFile(pFile); 399*8cf35eb4Sdan quotaGroup *pGroup; 400*8cf35eb4Sdan sqlite3_int64 sz; 401*8cf35eb4Sdan int rc; 402*8cf35eb4Sdan 403*8cf35eb4Sdan rc = pSubFile->pMethods->xFileSize(pSubFile, &sz); 404*8cf35eb4Sdan if( rc==SQLITE_OK ){ 405*8cf35eb4Sdan pGroup = p->pGroup; 406*8cf35eb4Sdan quotaEnter(); 407*8cf35eb4Sdan pGroup->iSize -= p->iSize; 408*8cf35eb4Sdan p->iSize = sz; 409*8cf35eb4Sdan pGroup->iSize += sz; 410*8cf35eb4Sdan quotaLeave(); 411*8cf35eb4Sdan *pSize = sz; 412*8cf35eb4Sdan } 413*8cf35eb4Sdan return rc; 414*8cf35eb4Sdan } 415*8cf35eb4Sdan 416*8cf35eb4Sdan /* Pass xLock requests through to the original VFS unchanged. 417*8cf35eb4Sdan */ 418*8cf35eb4Sdan static int quotaLock(sqlite3_file *pFile, int lock){ 419*8cf35eb4Sdan sqlite3_file *pSubFile = quotaSubFile(pFile); 420*8cf35eb4Sdan return pSubFile->pMethods->xLock(pSubFile, lock); 421*8cf35eb4Sdan } 422*8cf35eb4Sdan 423*8cf35eb4Sdan /* Pass xUnlock requests through to the original VFS unchanged. 424*8cf35eb4Sdan */ 425*8cf35eb4Sdan static int quotaUnlock(sqlite3_file *pFile, int lock){ 426*8cf35eb4Sdan sqlite3_file *pSubFile = quotaSubFile(pFile); 427*8cf35eb4Sdan return pSubFile->pMethods->xUnlock(pSubFile, lock); 428*8cf35eb4Sdan } 429*8cf35eb4Sdan 430*8cf35eb4Sdan /* Pass xCheckReservedLock requests through to the original VFS unchanged. 431*8cf35eb4Sdan */ 432*8cf35eb4Sdan static int quotaCheckReservedLock(sqlite3_file *pFile, int *pResOut){ 433*8cf35eb4Sdan sqlite3_file *pSubFile = quotaSubFile(pFile); 434*8cf35eb4Sdan return pSubFile->pMethods->xCheckReservedLock(pSubFile, pResOut); 435*8cf35eb4Sdan } 436*8cf35eb4Sdan 437*8cf35eb4Sdan /* Pass xFileControl requests through to the original VFS unchanged. 438*8cf35eb4Sdan */ 439*8cf35eb4Sdan static int quotaFileControl(sqlite3_file *pFile, int op, void *pArg){ 440*8cf35eb4Sdan sqlite3_file *pSubFile = quotaSubFile(pFile); 441*8cf35eb4Sdan return pSubFile->pMethods->xFileControl(pSubFile, op, pArg); 442*8cf35eb4Sdan } 443*8cf35eb4Sdan 444*8cf35eb4Sdan /* Pass xSectorSize requests through to the original VFS unchanged. 445*8cf35eb4Sdan */ 446*8cf35eb4Sdan static int quotaSectorSize(sqlite3_file *pFile){ 447*8cf35eb4Sdan sqlite3_file *pSubFile = quotaSubFile(pFile); 448*8cf35eb4Sdan return pSubFile->pMethods->xSectorSize(pSubFile); 449*8cf35eb4Sdan } 450*8cf35eb4Sdan 451*8cf35eb4Sdan /* Pass xDeviceCharacteristics requests through to the original VFS unchanged. 452*8cf35eb4Sdan */ 453*8cf35eb4Sdan static int quotaDeviceCharacteristics(sqlite3_file *pFile){ 454*8cf35eb4Sdan sqlite3_file *pSubFile = quotaSubFile(pFile); 455*8cf35eb4Sdan return pSubFile->pMethods->xDeviceCharacteristics(pSubFile); 456*8cf35eb4Sdan } 457*8cf35eb4Sdan 458*8cf35eb4Sdan /* Pass xShmMap requests through to the original VFS unchanged. 459*8cf35eb4Sdan */ 460*8cf35eb4Sdan static int quotaShmMap( 461*8cf35eb4Sdan sqlite3_file *pFile, /* Handle open on database file */ 462*8cf35eb4Sdan int iRegion, /* Region to retrieve */ 463*8cf35eb4Sdan int szRegion, /* Size of regions */ 464*8cf35eb4Sdan int bExtend, /* True to extend file if necessary */ 465*8cf35eb4Sdan void volatile **pp /* OUT: Mapped memory */ 466*8cf35eb4Sdan ){ 467*8cf35eb4Sdan sqlite3_file *pSubFile = quotaSubFile(pFile); 468*8cf35eb4Sdan return pSubFile->pMethods->xShmMap(pSubFile, iRegion, szRegion, bExtend, pp); 469*8cf35eb4Sdan } 470*8cf35eb4Sdan 471*8cf35eb4Sdan /* Pass xShmLock requests through to the original VFS unchanged. 472*8cf35eb4Sdan */ 473*8cf35eb4Sdan static int quotaShmLock( 474*8cf35eb4Sdan sqlite3_file *pFile, /* Database file holding the shared memory */ 475*8cf35eb4Sdan int ofst, /* First lock to acquire or release */ 476*8cf35eb4Sdan int n, /* Number of locks to acquire or release */ 477*8cf35eb4Sdan int flags /* What to do with the lock */ 478*8cf35eb4Sdan ){ 479*8cf35eb4Sdan sqlite3_file *pSubFile = quotaSubFile(pFile); 480*8cf35eb4Sdan return pSubFile->pMethods->xShmLock(pSubFile, ofst, n, flags); 481*8cf35eb4Sdan } 482*8cf35eb4Sdan 483*8cf35eb4Sdan /* Pass xShmBarrier requests through to the original VFS unchanged. 484*8cf35eb4Sdan */ 485*8cf35eb4Sdan static void quotaShmBarrier(sqlite3_file *pFile){ 486*8cf35eb4Sdan sqlite3_file *pSubFile = quotaSubFile(pFile); 487*8cf35eb4Sdan pSubFile->pMethods->xShmBarrier(pSubFile); 488*8cf35eb4Sdan } 489*8cf35eb4Sdan 490*8cf35eb4Sdan /* Pass xShmUnmap requests through to the original VFS unchanged. 491*8cf35eb4Sdan */ 492*8cf35eb4Sdan static int quotaShmUnmap(sqlite3_file *pFile, int deleteFlag){ 493*8cf35eb4Sdan sqlite3_file *pSubFile = quotaSubFile(pFile); 494*8cf35eb4Sdan return pSubFile->pMethods->xShmUnmap(pSubFile, deleteFlag); 495*8cf35eb4Sdan } 496*8cf35eb4Sdan 497*8cf35eb4Sdan /************************** Public Interfaces *****************************/ 498*8cf35eb4Sdan /* 499*8cf35eb4Sdan ** Initialize the quota VFS shim. Use the VFS named zOrigVfsName 500*8cf35eb4Sdan ** as the VFS that does the actual work. Use the default if 501*8cf35eb4Sdan ** zOrigVfsName==NULL. 502*8cf35eb4Sdan ** 503*8cf35eb4Sdan ** The quota VFS shim is named "quota". It will become the default 504*8cf35eb4Sdan ** VFS if makeDefault is non-zero. 505*8cf35eb4Sdan ** 506*8cf35eb4Sdan ** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once 507*8cf35eb4Sdan ** during start-up. 508*8cf35eb4Sdan */ 509*8cf35eb4Sdan int sqlite3_quota_initialize(const char *zOrigVfsName, int makeDefault){ 510*8cf35eb4Sdan sqlite3_vfs *pOrigVfs; 511*8cf35eb4Sdan if( gQuota.isInitialized ) return SQLITE_MISUSE; 512*8cf35eb4Sdan pOrigVfs = sqlite3_vfs_find(zOrigVfsName); 513*8cf35eb4Sdan if( pOrigVfs==0 ) return SQLITE_ERROR; 514*8cf35eb4Sdan assert( pOrigVfs!=&gQuota.sThisVfs ); 515*8cf35eb4Sdan gQuota.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); 516*8cf35eb4Sdan if( !gQuota.pMutex ){ 517*8cf35eb4Sdan return SQLITE_NOMEM; 518*8cf35eb4Sdan } 519*8cf35eb4Sdan gQuota.isInitialized = 1; 520*8cf35eb4Sdan gQuota.pOrigVfs = pOrigVfs; 521*8cf35eb4Sdan gQuota.sThisVfs = *pOrigVfs; 522*8cf35eb4Sdan gQuota.sThisVfs.xOpen = quotaOpen; 523*8cf35eb4Sdan gQuota.sThisVfs.szOsFile += sizeof(quotaFile); 524*8cf35eb4Sdan gQuota.sThisVfs.zName = "quota"; 525*8cf35eb4Sdan gQuota.sIoMethodsV1.iVersion = 1; 526*8cf35eb4Sdan gQuota.sIoMethodsV1.xClose = quotaClose; 527*8cf35eb4Sdan gQuota.sIoMethodsV1.xRead = quotaRead; 528*8cf35eb4Sdan gQuota.sIoMethodsV1.xWrite = quotaWrite; 529*8cf35eb4Sdan gQuota.sIoMethodsV1.xTruncate = quotaTruncate; 530*8cf35eb4Sdan gQuota.sIoMethodsV1.xSync = quotaSync; 531*8cf35eb4Sdan gQuota.sIoMethodsV1.xFileSize = quotaFileSize; 532*8cf35eb4Sdan gQuota.sIoMethodsV1.xLock = quotaLock; 533*8cf35eb4Sdan gQuota.sIoMethodsV1.xUnlock = quotaUnlock; 534*8cf35eb4Sdan gQuota.sIoMethodsV1.xCheckReservedLock = quotaCheckReservedLock; 535*8cf35eb4Sdan gQuota.sIoMethodsV1.xFileControl = quotaFileControl; 536*8cf35eb4Sdan gQuota.sIoMethodsV1.xSectorSize = quotaSectorSize; 537*8cf35eb4Sdan gQuota.sIoMethodsV1.xDeviceCharacteristics = quotaDeviceCharacteristics; 538*8cf35eb4Sdan gQuota.sIoMethodsV2 = gQuota.sIoMethodsV1; 539*8cf35eb4Sdan gQuota.sIoMethodsV2.iVersion = 2; 540*8cf35eb4Sdan gQuota.sIoMethodsV2.xShmMap = quotaShmMap; 541*8cf35eb4Sdan gQuota.sIoMethodsV2.xShmLock = quotaShmLock; 542*8cf35eb4Sdan gQuota.sIoMethodsV2.xShmBarrier = quotaShmBarrier; 543*8cf35eb4Sdan gQuota.sIoMethodsV2.xShmUnmap = quotaShmUnmap; 544*8cf35eb4Sdan sqlite3_vfs_register(&gQuota.sThisVfs, makeDefault); 545*8cf35eb4Sdan return SQLITE_OK; 546*8cf35eb4Sdan } 547*8cf35eb4Sdan 548*8cf35eb4Sdan /* 549*8cf35eb4Sdan ** Shutdown the quota system. 550*8cf35eb4Sdan ** 551*8cf35eb4Sdan ** All SQLite database connections must be closed before calling this 552*8cf35eb4Sdan ** routine. 553*8cf35eb4Sdan ** 554*8cf35eb4Sdan ** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly one while 555*8cf35eb4Sdan ** shutting down in order to free all remaining quota groups. 556*8cf35eb4Sdan */ 557*8cf35eb4Sdan int sqlite3_quota_shutdown(void){ 558*8cf35eb4Sdan quotaGroup *p; 559*8cf35eb4Sdan if( gQuota.isInitialized==0 ) return SQLITE_MISUSE; 560*8cf35eb4Sdan for(p=gQuota.pGroup; p; p=p->pNext){ 561*8cf35eb4Sdan if( p->nRef ) return SQLITE_MISUSE; 562*8cf35eb4Sdan } 563*8cf35eb4Sdan while( gQuota.pGroup ){ 564*8cf35eb4Sdan quotaGroup *p = gQuota.pGroup; 565*8cf35eb4Sdan gQuota.pGroup = p->pNext; 566*8cf35eb4Sdan sqlite3_free(p); 567*8cf35eb4Sdan } 568*8cf35eb4Sdan gQuota.isInitialized = 0; 569*8cf35eb4Sdan sqlite3_mutex_free(gQuota.pMutex); 570*8cf35eb4Sdan sqlite3_vfs_unregister(&gQuota.sThisVfs); 571*8cf35eb4Sdan memset(&gQuota, 0, sizeof(gQuota)); 572*8cf35eb4Sdan return SQLITE_OK; 573*8cf35eb4Sdan } 574*8cf35eb4Sdan 575*8cf35eb4Sdan /* 576*8cf35eb4Sdan ** Create or destroy a quota group. 577*8cf35eb4Sdan ** 578*8cf35eb4Sdan ** The quota group is defined by the zPattern. When calling this routine 579*8cf35eb4Sdan ** with a zPattern for a quota group that already exists, this routine 580*8cf35eb4Sdan ** merely updates the iLimit, xCallback, and pArg values for that quota 581*8cf35eb4Sdan ** group. If zPattern is new, then a new quota group is created. 582*8cf35eb4Sdan ** 583*8cf35eb4Sdan ** If the iLimit for a quota group is set to zero, then the quota group 584*8cf35eb4Sdan ** is disabled and will be deleted when the last database connection using 585*8cf35eb4Sdan ** the quota group is closed. 586*8cf35eb4Sdan ** 587*8cf35eb4Sdan ** Calling this routine on a zPattern that does not exist and with a 588*8cf35eb4Sdan ** zero iLimit is a no-op. 589*8cf35eb4Sdan ** 590*8cf35eb4Sdan ** A quota group must exist with a non-zero iLimit prior to opening 591*8cf35eb4Sdan ** database connections if those connections are to participate in the 592*8cf35eb4Sdan ** quota group. Creating a quota group does not affect database connections 593*8cf35eb4Sdan ** that are already open. 594*8cf35eb4Sdan */ 595*8cf35eb4Sdan int sqlite3_quota_set( 596*8cf35eb4Sdan const char *zPattern, /* The filename pattern */ 597*8cf35eb4Sdan sqlite3_int64 iLimit, /* New quota to set for this quota group */ 598*8cf35eb4Sdan void (*xCallback)( /* Callback invoked when going over quota */ 599*8cf35eb4Sdan const char *zFilename, /* Name of file whose size increases */ 600*8cf35eb4Sdan sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ 601*8cf35eb4Sdan sqlite3_int64 iSize, /* Total size of all files in the group */ 602*8cf35eb4Sdan void *pArg /* Client data */ 603*8cf35eb4Sdan ), 604*8cf35eb4Sdan void *pArg /* client data passed thru to callback */ 605*8cf35eb4Sdan ){ 606*8cf35eb4Sdan quotaGroup *pGroup; 607*8cf35eb4Sdan quotaEnter(); 608*8cf35eb4Sdan pGroup = gQuota.pGroup; 609*8cf35eb4Sdan while( pGroup && strcmp(pGroup->zPattern, zPattern)!=0 ){ 610*8cf35eb4Sdan pGroup = pGroup->pNext; 611*8cf35eb4Sdan } 612*8cf35eb4Sdan if( pGroup==0 && iLimit>0 ){ 613*8cf35eb4Sdan int nPattern = strlen(zPattern); 614*8cf35eb4Sdan pGroup = sqlite3_malloc( sizeof(*pGroup) + nPattern + 1 ); 615*8cf35eb4Sdan if( pGroup==0 ){ 616*8cf35eb4Sdan quotaLeave(); 617*8cf35eb4Sdan return SQLITE_NOMEM; 618*8cf35eb4Sdan } 619*8cf35eb4Sdan memset(pGroup, 0, sizeof(*pGroup)); 620*8cf35eb4Sdan pGroup->zPattern = (char*)&pGroup[1]; 621*8cf35eb4Sdan memcpy((char *)pGroup->zPattern, zPattern, nPattern+1); 622*8cf35eb4Sdan if( gQuota.pGroup ) gQuota.pGroup->ppPrev = &pGroup->pNext; 623*8cf35eb4Sdan pGroup->pNext = gQuota.pGroup; 624*8cf35eb4Sdan pGroup->ppPrev = &gQuota.pGroup; 625*8cf35eb4Sdan gQuota.pGroup = pGroup; 626*8cf35eb4Sdan } 627*8cf35eb4Sdan pGroup->iLimit = iLimit; 628*8cf35eb4Sdan pGroup->xCallback = xCallback; 629*8cf35eb4Sdan pGroup->pArg = pArg; 630*8cf35eb4Sdan quotaGroupDeref(pGroup); 631*8cf35eb4Sdan quotaLeave(); 632*8cf35eb4Sdan return SQLITE_OK; 633*8cf35eb4Sdan } 634*8cf35eb4Sdan 635*8cf35eb4Sdan 636*8cf35eb4Sdan /***************************** Test Code ***********************************/ 637*8cf35eb4Sdan #ifdef SQLITE_TEST 638*8cf35eb4Sdan #include <tcl.h> 639*8cf35eb4Sdan 640*8cf35eb4Sdan typedef struct TclQuotaCallback TclQuotaCallback; 641*8cf35eb4Sdan struct TclQuotaCallback { 642*8cf35eb4Sdan Tcl_Interp *interp; 643*8cf35eb4Sdan Tcl_Obj *pScript; 644*8cf35eb4Sdan TclQuotaCallback *pNext; 645*8cf35eb4Sdan }; 646*8cf35eb4Sdan static TclQuotaCallback *pQuotaCallbackList = 0; 647*8cf35eb4Sdan 648*8cf35eb4Sdan extern const char *sqlite3TestErrorName(int); 649*8cf35eb4Sdan 650*8cf35eb4Sdan static void tclQuotaCallback( 651*8cf35eb4Sdan const char *zFilename, /* Name of file whose size increases */ 652*8cf35eb4Sdan sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ 653*8cf35eb4Sdan sqlite3_int64 iSize, /* Total size of all files in the group */ 654*8cf35eb4Sdan void *pArg /* Client data */ 655*8cf35eb4Sdan ){ 656*8cf35eb4Sdan TclQuotaCallback *p; /* Callback script object */ 657*8cf35eb4Sdan Tcl_Obj *pEval; /* Script to evaluate */ 658*8cf35eb4Sdan Tcl_Obj *pVarname; /* Name of variable to pass as 2nd arg */ 659*8cf35eb4Sdan unsigned int rnd; /* Random part of pVarname */ 660*8cf35eb4Sdan int rc; /* Tcl error code */ 661*8cf35eb4Sdan 662*8cf35eb4Sdan p = (TclQuotaCallback *)pArg; 663*8cf35eb4Sdan 664*8cf35eb4Sdan pVarname = Tcl_NewStringObj("::piLimit_", -1); 665*8cf35eb4Sdan Tcl_IncrRefCount(pVarname); 666*8cf35eb4Sdan sqlite3_randomness(sizeof(rnd), (void *)&rnd); 667*8cf35eb4Sdan Tcl_AppendObjToObj(pVarname, Tcl_NewIntObj((int)(rnd&0x7FFFFFFF))); 668*8cf35eb4Sdan Tcl_ObjSetVar2(p->interp, pVarname, 0, Tcl_NewWideIntObj(*piLimit), 0); 669*8cf35eb4Sdan 670*8cf35eb4Sdan pEval = Tcl_DuplicateObj(p->pScript); 671*8cf35eb4Sdan Tcl_IncrRefCount(pEval); 672*8cf35eb4Sdan Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zFilename, -1)); 673*8cf35eb4Sdan Tcl_ListObjAppendElement(0, pEval, pVarname); 674*8cf35eb4Sdan Tcl_ListObjAppendElement(0, pEval, Tcl_NewWideIntObj(iSize)); 675*8cf35eb4Sdan rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL); 676*8cf35eb4Sdan 677*8cf35eb4Sdan if( rc==TCL_OK ){ 678*8cf35eb4Sdan Tcl_Obj *pLimit = Tcl_ObjGetVar2(p->interp, pVarname, 0, 0); 679*8cf35eb4Sdan rc = Tcl_GetWideIntFromObj(p->interp, pLimit, piLimit); 680*8cf35eb4Sdan Tcl_UnsetVar(p->interp, Tcl_GetString(pVarname), 0); 681*8cf35eb4Sdan } 682*8cf35eb4Sdan 683*8cf35eb4Sdan Tcl_DecrRefCount(pEval); 684*8cf35eb4Sdan Tcl_DecrRefCount(pVarname); 685*8cf35eb4Sdan if( rc!=TCL_OK ) Tcl_BackgroundError(p->interp); 686*8cf35eb4Sdan } 687*8cf35eb4Sdan 688*8cf35eb4Sdan /* 689*8cf35eb4Sdan ** tclcmd: sqlite3_quota_initialize NAME MAKEDEFAULT 690*8cf35eb4Sdan */ 691*8cf35eb4Sdan static int test_quota_initialize( 692*8cf35eb4Sdan void * clientData, 693*8cf35eb4Sdan Tcl_Interp *interp, 694*8cf35eb4Sdan int objc, 695*8cf35eb4Sdan Tcl_Obj *CONST objv[] 696*8cf35eb4Sdan ){ 697*8cf35eb4Sdan const char *zName; /* Name of new quota VFS */ 698*8cf35eb4Sdan int makeDefault; /* True to make the new VFS the default */ 699*8cf35eb4Sdan int rc; /* Value returned by quota_initialize() */ 700*8cf35eb4Sdan 701*8cf35eb4Sdan /* Process arguments */ 702*8cf35eb4Sdan if( objc!=3 ){ 703*8cf35eb4Sdan Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT"); 704*8cf35eb4Sdan return TCL_ERROR; 705*8cf35eb4Sdan } 706*8cf35eb4Sdan zName = Tcl_GetString(objv[1]); 707*8cf35eb4Sdan if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR; 708*8cf35eb4Sdan if( zName[0]=='\0' ) zName = 0; 709*8cf35eb4Sdan 710*8cf35eb4Sdan /* Call sqlite3_quota_initialize() */ 711*8cf35eb4Sdan rc = sqlite3_quota_initialize(zName, makeDefault); 712*8cf35eb4Sdan Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); 713*8cf35eb4Sdan 714*8cf35eb4Sdan return TCL_OK; 715*8cf35eb4Sdan } 716*8cf35eb4Sdan 717*8cf35eb4Sdan /* 718*8cf35eb4Sdan ** tclcmd: sqlite3_quota_shutdown 719*8cf35eb4Sdan */ 720*8cf35eb4Sdan static int test_quota_shutdown( 721*8cf35eb4Sdan void * clientData, 722*8cf35eb4Sdan Tcl_Interp *interp, 723*8cf35eb4Sdan int objc, 724*8cf35eb4Sdan Tcl_Obj *CONST objv[] 725*8cf35eb4Sdan ){ 726*8cf35eb4Sdan int rc; /* Value returned by quota_shutdown() */ 727*8cf35eb4Sdan 728*8cf35eb4Sdan if( objc!=1 ){ 729*8cf35eb4Sdan Tcl_WrongNumArgs(interp, 1, objv, ""); 730*8cf35eb4Sdan return TCL_ERROR; 731*8cf35eb4Sdan } 732*8cf35eb4Sdan 733*8cf35eb4Sdan /* Call sqlite3_quota_shutdown() */ 734*8cf35eb4Sdan rc = sqlite3_quota_shutdown(); 735*8cf35eb4Sdan Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); 736*8cf35eb4Sdan 737*8cf35eb4Sdan /* If the quota system was successfully shut down, delete all the quota 738*8cf35eb4Sdan ** callback script objects in the global linked list. 739*8cf35eb4Sdan */ 740*8cf35eb4Sdan if( rc==SQLITE_OK ){ 741*8cf35eb4Sdan TclQuotaCallback *p, *pNext; 742*8cf35eb4Sdan for(p=pQuotaCallbackList; p; p=pNext){ 743*8cf35eb4Sdan pNext = p->pNext; 744*8cf35eb4Sdan Tcl_DecrRefCount(p->pScript); 745*8cf35eb4Sdan ckfree((char *)p); 746*8cf35eb4Sdan } 747*8cf35eb4Sdan pQuotaCallbackList = 0; 748*8cf35eb4Sdan } 749*8cf35eb4Sdan 750*8cf35eb4Sdan return TCL_OK; 751*8cf35eb4Sdan } 752*8cf35eb4Sdan 753*8cf35eb4Sdan /* 754*8cf35eb4Sdan ** tclcmd: sqlite3_quota_set PATTERN LIMIT SCRIPT 755*8cf35eb4Sdan */ 756*8cf35eb4Sdan static int test_quota_set( 757*8cf35eb4Sdan void * clientData, 758*8cf35eb4Sdan Tcl_Interp *interp, 759*8cf35eb4Sdan int objc, 760*8cf35eb4Sdan Tcl_Obj *CONST objv[] 761*8cf35eb4Sdan ){ 762*8cf35eb4Sdan const char *zPattern; /* File pattern to configure */ 763*8cf35eb4Sdan sqlite3_int64 iLimit; /* Initial quota in bytes */ 764*8cf35eb4Sdan Tcl_Obj *pScript; /* Tcl script to invoke to increase quota */ 765*8cf35eb4Sdan int rc; /* Value returned by quota_set() */ 766*8cf35eb4Sdan TclQuotaCallback *p; /* Callback object */ 767*8cf35eb4Sdan 768*8cf35eb4Sdan /* Process arguments */ 769*8cf35eb4Sdan if( objc!=4 ){ 770*8cf35eb4Sdan Tcl_WrongNumArgs(interp, 1, objv, "PATTERN LIMIT SCRIPT"); 771*8cf35eb4Sdan return TCL_ERROR; 772*8cf35eb4Sdan } 773*8cf35eb4Sdan zPattern = Tcl_GetString(objv[1]); 774*8cf35eb4Sdan if( Tcl_GetWideIntFromObj(interp, objv[2], &iLimit) ) return TCL_ERROR; 775*8cf35eb4Sdan pScript = objv[3]; 776*8cf35eb4Sdan 777*8cf35eb4Sdan /* Allocate a TclQuotaCallback object */ 778*8cf35eb4Sdan p = (TclQuotaCallback *)ckalloc(sizeof(TclQuotaCallback)); 779*8cf35eb4Sdan memset(p, 0, sizeof(TclQuotaCallback)); 780*8cf35eb4Sdan 781*8cf35eb4Sdan /* Invoke sqlite3_quota_set() */ 782*8cf35eb4Sdan rc = sqlite3_quota_set(zPattern, iLimit, tclQuotaCallback, (void *)p); 783*8cf35eb4Sdan if( rc!=SQLITE_OK ){ 784*8cf35eb4Sdan ckfree((char *)p); 785*8cf35eb4Sdan }else{ 786*8cf35eb4Sdan Tcl_IncrRefCount(pScript); 787*8cf35eb4Sdan p->interp = interp; 788*8cf35eb4Sdan p->pScript = pScript; 789*8cf35eb4Sdan p->pNext = pQuotaCallbackList; 790*8cf35eb4Sdan pQuotaCallbackList = p; 791*8cf35eb4Sdan } 792*8cf35eb4Sdan 793*8cf35eb4Sdan Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); 794*8cf35eb4Sdan return TCL_OK; 795*8cf35eb4Sdan } 796*8cf35eb4Sdan 797*8cf35eb4Sdan /* 798*8cf35eb4Sdan ** This routine registers the custom TCL commands defined in this 799*8cf35eb4Sdan ** module. This should be the only procedure visible from outside 800*8cf35eb4Sdan ** of this module. 801*8cf35eb4Sdan */ 802*8cf35eb4Sdan int Sqlitequota_Init(Tcl_Interp *interp){ 803*8cf35eb4Sdan static struct { 804*8cf35eb4Sdan char *zName; 805*8cf35eb4Sdan Tcl_ObjCmdProc *xProc; 806*8cf35eb4Sdan } aCmd[] = { 807*8cf35eb4Sdan { "sqlite3_quota_initialize", test_quota_initialize }, 808*8cf35eb4Sdan { "sqlite3_quota_shutdown", test_quota_shutdown }, 809*8cf35eb4Sdan { "sqlite3_quota_set", test_quota_set }, 810*8cf35eb4Sdan }; 811*8cf35eb4Sdan int i; 812*8cf35eb4Sdan 813*8cf35eb4Sdan for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){ 814*8cf35eb4Sdan Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0); 815*8cf35eb4Sdan } 816*8cf35eb4Sdan 817*8cf35eb4Sdan return TCL_OK; 818*8cf35eb4Sdan } 819*8cf35eb4Sdan #endif 820