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