1748a52cdSdrh /* 2748a52cdSdrh ** 2010 September 31 3748a52cdSdrh ** 4748a52cdSdrh ** The author disclaims copyright to this source code. In place of 5748a52cdSdrh ** a legal notice, here is a blessing: 6748a52cdSdrh ** 7748a52cdSdrh ** May you do good and not evil. 8748a52cdSdrh ** May you find forgiveness for yourself and forgive others. 9748a52cdSdrh ** May you share freely, never taking more than you give. 10748a52cdSdrh ** 11748a52cdSdrh ************************************************************************* 12748a52cdSdrh ** 13748a52cdSdrh ** This file contains a VFS "shim" - a layer that sits in between the 14748a52cdSdrh ** pager and the real VFS. 15748a52cdSdrh ** 16748a52cdSdrh ** This particular shim enforces a quota system on files. One or more 17748a52cdSdrh ** database files are in a "quota group" that is defined by a GLOB 18748a52cdSdrh ** pattern. A quota is set for the combined size of all files in the 19748a52cdSdrh ** the group. A quota of zero means "no limit". If the total size 209237a258Sdrh ** of all files in the quota group is greater than the limit, then 219237a258Sdrh ** write requests that attempt to enlarge a file fail with SQLITE_FULL. 22748a52cdSdrh ** 23748a52cdSdrh ** However, before returning SQLITE_FULL, the write requests invoke 24748a52cdSdrh ** a callback function that is configurable for each quota group. 25748a52cdSdrh ** This callback has the opportunity to enlarge the quota. If the 26748a52cdSdrh ** callback does enlarge the quota such that the total size of all 27748a52cdSdrh ** files within the group is less than the new quota, then the write 28748a52cdSdrh ** continues as if nothing had happened. 29748a52cdSdrh */ 3060f21e4bSdrh #include "test_quota.h" 318cf35eb4Sdan #include <string.h> 328cf35eb4Sdan #include <assert.h> 33748a52cdSdrh 343778ac15Sdrh /* 353778ac15Sdrh ** For an build without mutexes, no-op the mutex calls. 363778ac15Sdrh */ 373778ac15Sdrh #if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0 383778ac15Sdrh #define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8) 393778ac15Sdrh #define sqlite3_mutex_free(X) 403778ac15Sdrh #define sqlite3_mutex_enter(X) 413778ac15Sdrh #define sqlite3_mutex_try(X) SQLITE_OK 423778ac15Sdrh #define sqlite3_mutex_leave(X) 433778ac15Sdrh #define sqlite3_mutex_held(X) ((void)(X),1) 443778ac15Sdrh #define sqlite3_mutex_notheld(X) ((void)(X),1) 453778ac15Sdrh #endif /* SQLITE_THREADSAFE==0 */ 463778ac15Sdrh 473778ac15Sdrh 48748a52cdSdrh /************************ Object Definitions ******************************/ 49748a52cdSdrh 50745de2e4Sdrh /* Forward declaration of all object types */ 51748a52cdSdrh typedef struct quotaGroup quotaGroup; 52ef9659baSdrh typedef struct quotaConn quotaConn; 53745de2e4Sdrh typedef struct quotaFile quotaFile; 54745de2e4Sdrh 558cf35eb4Sdan /* 56549dc1baSdrh ** A "quota group" is a collection of files whose collective size we want 57549dc1baSdrh ** to limit. Each quota group is defined by a GLOB pattern. 588cf35eb4Sdan ** 59549dc1baSdrh ** There is an instance of the following object for each defined quota 60549dc1baSdrh ** group. This object records the GLOB pattern that defines which files 61549dc1baSdrh ** belong to the quota group. The object also remembers the size limit 62549dc1baSdrh ** for the group (the quota) and the callback to be invoked when the 63549dc1baSdrh ** sum of the sizes of the files within the group goes over the limit. 648cf35eb4Sdan ** 65549dc1baSdrh ** A quota group must be established (using sqlite3_quota_set(...)) 66549dc1baSdrh ** prior to opening any of the database connections that access files 67549dc1baSdrh ** within the quota group. 688cf35eb4Sdan */ 69748a52cdSdrh struct quotaGroup { 70748a52cdSdrh const char *zPattern; /* Filename pattern to be quotaed */ 71748a52cdSdrh sqlite3_int64 iLimit; /* Upper bound on total file size */ 72748a52cdSdrh sqlite3_int64 iSize; /* Current size of all files */ 73748a52cdSdrh void (*xCallback)( /* Callback invoked when going over quota */ 74748a52cdSdrh const char *zFilename, /* Name of file whose size increases */ 75748a52cdSdrh sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ 76748a52cdSdrh sqlite3_int64 iSize, /* Total size of all files in the group */ 77748a52cdSdrh void *pArg /* Client data */ 78748a52cdSdrh ); 79748a52cdSdrh void *pArg; /* Third argument to the xCallback() */ 80ef9659baSdrh void (*xDestroy)(void*); /* Optional destructor for pArg */ 81748a52cdSdrh quotaGroup *pNext, **ppPrev; /* Doubly linked list of all quota objects */ 8275c19174Sdrh quotaFile *pFiles; /* Files within this group */ 83748a52cdSdrh }; 84748a52cdSdrh 85748a52cdSdrh /* 86745de2e4Sdrh ** An instance of this structure represents a single file that is part 87745de2e4Sdrh ** of a quota group. A single file can be opened multiple times. In 88745de2e4Sdrh ** order keep multiple openings of the same file from causing the size 89549dc1baSdrh ** of the file to count against the quota multiple times, each file 90745de2e4Sdrh ** has a unique instance of this object and multiple open connections 91745de2e4Sdrh ** to the same file each point to a single instance of this object. 92748a52cdSdrh */ 93748a52cdSdrh struct quotaFile { 94745de2e4Sdrh char *zFilename; /* Name of this file */ 9575c19174Sdrh quotaGroup *pGroup; /* Quota group to which this file belongs */ 968cf35eb4Sdan sqlite3_int64 iSize; /* Current size of this file */ 97745de2e4Sdrh int nRef; /* Number of times this file is open */ 98796af156Sdrh int deleteOnClose; /* True to delete this file when it closes */ 99745de2e4Sdrh quotaFile *pNext, **ppPrev; /* Linked list of files in the same group */ 100745de2e4Sdrh }; 101745de2e4Sdrh 102745de2e4Sdrh /* 103745de2e4Sdrh ** An instance of the following object represents each open connection 104549dc1baSdrh ** to a file that participates in quota tracking. This object is a 105549dc1baSdrh ** subclass of sqlite3_file. The sqlite3_file object for the underlying 106549dc1baSdrh ** VFS is appended to this structure. 107745de2e4Sdrh */ 108ef9659baSdrh struct quotaConn { 109748a52cdSdrh sqlite3_file base; /* Base class - must be first */ 110745de2e4Sdrh quotaFile *pFile; /* The underlying file */ 111748a52cdSdrh /* The underlying VFS sqlite3_file is appended to this object */ 112748a52cdSdrh }; 113748a52cdSdrh 11460f21e4bSdrh /* 11560f21e4bSdrh ** An instance of the following object records the state of an 11660f21e4bSdrh ** open file. This object is opaque to all users - the internal 11760f21e4bSdrh ** structure is only visible to the functions below. 11860f21e4bSdrh */ 11960f21e4bSdrh struct quota_FILE { 12060f21e4bSdrh FILE *f; /* Open stdio file pointer */ 12160f21e4bSdrh sqlite3_int64 iOfst; /* Current offset into the file */ 12260f21e4bSdrh quotaFile *pFile; /* The file record in the quota system */ 12360f21e4bSdrh }; 12460f21e4bSdrh 12560f21e4bSdrh 126748a52cdSdrh /************************* Global Variables **********************************/ 127748a52cdSdrh /* 128748a52cdSdrh ** All global variables used by this file are containing within the following 129748a52cdSdrh ** gQuota structure. 130748a52cdSdrh */ 131748a52cdSdrh static struct { 132549dc1baSdrh /* The pOrigVfs is the real, original underlying VFS implementation. 133748a52cdSdrh ** Most operations pass-through to the real VFS. This value is read-only 134748a52cdSdrh ** during operation. It is only modified at start-time and thus does not 135748a52cdSdrh ** require a mutex. 136748a52cdSdrh */ 137748a52cdSdrh sqlite3_vfs *pOrigVfs; 138748a52cdSdrh 139748a52cdSdrh /* The sThisVfs is the VFS structure used by this shim. It is initialized 140748a52cdSdrh ** at start-time and thus does not require a mutex 141748a52cdSdrh */ 142748a52cdSdrh sqlite3_vfs sThisVfs; 143748a52cdSdrh 144748a52cdSdrh /* The sIoMethods defines the methods used by sqlite3_file objects 145748a52cdSdrh ** associated with this shim. It is initialized at start-time and does 146748a52cdSdrh ** not require a mutex. 147748a52cdSdrh ** 148748a52cdSdrh ** When the underlying VFS is called to open a file, it might return 149748a52cdSdrh ** either a version 1 or a version 2 sqlite3_file object. This shim 150748a52cdSdrh ** has to create a wrapper sqlite3_file of the same version. Hence 151748a52cdSdrh ** there are two I/O method structures, one for version 1 and the other 152748a52cdSdrh ** for version 2. 153748a52cdSdrh */ 154748a52cdSdrh sqlite3_io_methods sIoMethodsV1; 155748a52cdSdrh sqlite3_io_methods sIoMethodsV2; 156748a52cdSdrh 157748a52cdSdrh /* True when this shim as been initialized. 158748a52cdSdrh */ 159748a52cdSdrh int isInitialized; 160748a52cdSdrh 161748a52cdSdrh /* For run-time access any of the other global data structures in this 162748a52cdSdrh ** shim, the following mutex must be held. 163748a52cdSdrh */ 164748a52cdSdrh sqlite3_mutex *pMutex; 165748a52cdSdrh 166748a52cdSdrh /* List of quotaGroup objects. 167748a52cdSdrh */ 168748a52cdSdrh quotaGroup *pGroup; 169748a52cdSdrh 170748a52cdSdrh } gQuota; 171748a52cdSdrh 172748a52cdSdrh /************************* Utility Routines *********************************/ 173748a52cdSdrh /* 174748a52cdSdrh ** Acquire and release the mutex used to serialize access to the 175748a52cdSdrh ** list of quotaGroups. 176748a52cdSdrh */ 177748a52cdSdrh static void quotaEnter(void){ sqlite3_mutex_enter(gQuota.pMutex); } 178748a52cdSdrh static void quotaLeave(void){ sqlite3_mutex_leave(gQuota.pMutex); } 179748a52cdSdrh 180796af156Sdrh /* Count the number of open files in a quotaGroup 181796af156Sdrh */ 182796af156Sdrh static int quotaGroupOpenFileCount(quotaGroup *pGroup){ 183796af156Sdrh int N = 0; 184796af156Sdrh quotaFile *pFile = pGroup->pFiles; 185796af156Sdrh while( pFile ){ 186796af156Sdrh if( pFile->nRef ) N++; 187796af156Sdrh pFile = pFile->pNext; 188796af156Sdrh } 189796af156Sdrh return N; 190796af156Sdrh } 191796af156Sdrh 192796af156Sdrh /* Remove a file from a quota group. 193796af156Sdrh */ 194796af156Sdrh static void quotaRemoveFile(quotaFile *pFile){ 195796af156Sdrh quotaGroup *pGroup = pFile->pGroup; 196796af156Sdrh pGroup->iSize -= pFile->iSize; 197796af156Sdrh *pFile->ppPrev = pFile->pNext; 198796af156Sdrh if( pFile->pNext ) pFile->pNext->ppPrev = pFile->ppPrev; 199796af156Sdrh sqlite3_free(pFile); 200796af156Sdrh } 201796af156Sdrh 202796af156Sdrh /* Remove all files from a quota group. It is always the case that 203796af156Sdrh ** all files will be closed when this routine is called. 204796af156Sdrh */ 205796af156Sdrh static void quotaRemoveAllFiles(quotaGroup *pGroup){ 206796af156Sdrh while( pGroup->pFiles ){ 207796af156Sdrh assert( pGroup->pFiles->nRef==0 ); 208796af156Sdrh quotaRemoveFile(pGroup->pFiles); 209796af156Sdrh } 210796af156Sdrh } 211796af156Sdrh 212748a52cdSdrh 213748a52cdSdrh /* If the reference count and threshold for a quotaGroup are both 214748a52cdSdrh ** zero, then destroy the quotaGroup. 215748a52cdSdrh */ 21675c19174Sdrh static void quotaGroupDeref(quotaGroup *pGroup){ 217796af156Sdrh if( pGroup->iLimit==0 && quotaGroupOpenFileCount(pGroup)==0 ){ 218796af156Sdrh quotaRemoveAllFiles(pGroup); 2199fc7702cSdan *pGroup->ppPrev = pGroup->pNext; 22075c19174Sdrh if( pGroup->pNext ) pGroup->pNext->ppPrev = pGroup->ppPrev; 22175c19174Sdrh if( pGroup->xDestroy ) pGroup->xDestroy(pGroup->pArg); 22275c19174Sdrh sqlite3_free(pGroup); 223748a52cdSdrh } 224748a52cdSdrh } 225748a52cdSdrh 226748a52cdSdrh /* 227748a52cdSdrh ** Return TRUE if string z matches glob pattern zGlob. 228748a52cdSdrh ** 229748a52cdSdrh ** Globbing rules: 230748a52cdSdrh ** 231748a52cdSdrh ** '*' Matches any sequence of zero or more characters. 232748a52cdSdrh ** 233748a52cdSdrh ** '?' Matches exactly one character. 234748a52cdSdrh ** 235748a52cdSdrh ** [...] Matches one character from the enclosed list of 236748a52cdSdrh ** characters. 237748a52cdSdrh ** 238748a52cdSdrh ** [^...] Matches one character not in the enclosed list. 239748a52cdSdrh ** 240a76e8589Sdrh ** / Matches "/" or "\\" 241a76e8589Sdrh ** 242748a52cdSdrh */ 24361cc423aSdrh static int quotaStrglob(const char *zGlob, const char *z){ 244c8ccda60Sdrh int c, c2, cx; 245748a52cdSdrh int invert; 246748a52cdSdrh int seen; 247748a52cdSdrh 248748a52cdSdrh while( (c = (*(zGlob++)))!=0 ){ 249748a52cdSdrh if( c=='*' ){ 250748a52cdSdrh while( (c=(*(zGlob++))) == '*' || c=='?' ){ 251748a52cdSdrh if( c=='?' && (*(z++))==0 ) return 0; 252748a52cdSdrh } 253748a52cdSdrh if( c==0 ){ 254748a52cdSdrh return 1; 255748a52cdSdrh }else if( c=='[' ){ 25661cc423aSdrh while( *z && quotaStrglob(zGlob-1,z)==0 ){ 257748a52cdSdrh z++; 258748a52cdSdrh } 259748a52cdSdrh return (*z)!=0; 260748a52cdSdrh } 261c8ccda60Sdrh cx = (c=='/') ? '\\' : c; 262748a52cdSdrh while( (c2 = (*(z++)))!=0 ){ 263c8ccda60Sdrh while( c2!=c && c2!=cx ){ 264748a52cdSdrh c2 = *(z++); 265748a52cdSdrh if( c2==0 ) return 0; 266748a52cdSdrh } 26761cc423aSdrh if( quotaStrglob(zGlob,z) ) return 1; 268748a52cdSdrh } 269748a52cdSdrh return 0; 270748a52cdSdrh }else if( c=='?' ){ 271748a52cdSdrh if( (*(z++))==0 ) return 0; 272748a52cdSdrh }else if( c=='[' ){ 273748a52cdSdrh int prior_c = 0; 274748a52cdSdrh seen = 0; 275748a52cdSdrh invert = 0; 276748a52cdSdrh c = *(z++); 277748a52cdSdrh if( c==0 ) return 0; 278748a52cdSdrh c2 = *(zGlob++); 279748a52cdSdrh if( c2=='^' ){ 280748a52cdSdrh invert = 1; 281748a52cdSdrh c2 = *(zGlob++); 282748a52cdSdrh } 283748a52cdSdrh if( c2==']' ){ 284748a52cdSdrh if( c==']' ) seen = 1; 285748a52cdSdrh c2 = *(zGlob++); 286748a52cdSdrh } 287748a52cdSdrh while( c2 && c2!=']' ){ 288748a52cdSdrh if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){ 289748a52cdSdrh c2 = *(zGlob++); 290748a52cdSdrh if( c>=prior_c && c<=c2 ) seen = 1; 291748a52cdSdrh prior_c = 0; 292748a52cdSdrh }else{ 293748a52cdSdrh if( c==c2 ){ 294748a52cdSdrh seen = 1; 295748a52cdSdrh } 296748a52cdSdrh prior_c = c2; 297748a52cdSdrh } 298748a52cdSdrh c2 = *(zGlob++); 299748a52cdSdrh } 300748a52cdSdrh if( c2==0 || (seen ^ invert)==0 ) return 0; 301a76e8589Sdrh }else if( c=='/' ){ 302a76e8589Sdrh if( z[0]!='/' && z[0]!='\\' ) return 0; 303a76e8589Sdrh z++; 304748a52cdSdrh }else{ 305748a52cdSdrh if( c!=(*(z++)) ) return 0; 306748a52cdSdrh } 307748a52cdSdrh } 308748a52cdSdrh return *z==0; 309748a52cdSdrh } 310748a52cdSdrh 311748a52cdSdrh 312748a52cdSdrh /* Find a quotaGroup given the filename. 3138cf35eb4Sdan ** 314ef9659baSdrh ** Return a pointer to the quotaGroup object. Return NULL if not found. 315748a52cdSdrh */ 316748a52cdSdrh static quotaGroup *quotaGroupFind(const char *zFilename){ 317748a52cdSdrh quotaGroup *p; 31861cc423aSdrh for(p=gQuota.pGroup; p && quotaStrglob(p->zPattern, zFilename)==0; 31961cc423aSdrh p=p->pNext){} 320748a52cdSdrh return p; 321748a52cdSdrh } 322748a52cdSdrh 323ef9659baSdrh /* Translate an sqlite3_file* that is really a quotaConn* into 324549dc1baSdrh ** the sqlite3_file* for the underlying original VFS. 325748a52cdSdrh */ 326ef9659baSdrh static sqlite3_file *quotaSubOpen(sqlite3_file *pConn){ 327ef9659baSdrh quotaConn *p = (quotaConn*)pConn; 328748a52cdSdrh return (sqlite3_file*)&p[1]; 329748a52cdSdrh } 330748a52cdSdrh 331796af156Sdrh /* Find a file in a quota group and return a pointer to that file. 332796af156Sdrh ** Return NULL if the file is not in the group. 333796af156Sdrh */ 33460f21e4bSdrh static quotaFile *quotaFindFile( 33560f21e4bSdrh quotaGroup *pGroup, /* Group in which to look for the file */ 33660f21e4bSdrh const char *zName, /* Full pathname of the file */ 33760f21e4bSdrh int createFlag /* Try to create the file if not found */ 33860f21e4bSdrh ){ 339796af156Sdrh quotaFile *pFile = pGroup->pFiles; 340796af156Sdrh while( pFile && strcmp(pFile->zFilename, zName)!=0 ){ 341796af156Sdrh pFile = pFile->pNext; 342796af156Sdrh } 34360f21e4bSdrh if( pFile==0 && createFlag ){ 34460f21e4bSdrh int nName = strlen(zName); 34560f21e4bSdrh pFile = (quotaFile *)sqlite3_malloc( sizeof(*pFile) + nName + 1 ); 34660f21e4bSdrh if( pFile ){ 34760f21e4bSdrh memset(pFile, 0, sizeof(*pFile)); 34860f21e4bSdrh pFile->zFilename = (char*)&pFile[1]; 34960f21e4bSdrh memcpy(pFile->zFilename, zName, nName+1); 35060f21e4bSdrh pFile->pNext = pGroup->pFiles; 35160f21e4bSdrh if( pGroup->pFiles ) pGroup->pFiles->ppPrev = &pFile->pNext; 35260f21e4bSdrh pFile->ppPrev = &pGroup->pFiles; 35360f21e4bSdrh pGroup->pFiles = pFile; 35460f21e4bSdrh pFile->pGroup = pGroup; 35560f21e4bSdrh } 35660f21e4bSdrh } 357796af156Sdrh return pFile; 358796af156Sdrh } 359796af156Sdrh 360a76e8589Sdrh /* 361a76e8589Sdrh ** Figure out if we are dealing with Unix, Windows, or some other 362a76e8589Sdrh ** operating system. After the following block of preprocess macros, 363a76e8589Sdrh ** all of SQLITE_OS_UNIX, SQLITE_OS_WIN, SQLITE_OS_OS2, and SQLITE_OS_OTHER 364a76e8589Sdrh ** will defined to either 1 or 0. One of the four will be 1. The other 365a76e8589Sdrh ** three will be 0. 366a76e8589Sdrh */ 367a76e8589Sdrh #if defined(SQLITE_OS_OTHER) 368a76e8589Sdrh # if SQLITE_OS_OTHER==1 369a76e8589Sdrh # undef SQLITE_OS_UNIX 370a76e8589Sdrh # define SQLITE_OS_UNIX 0 371a76e8589Sdrh # undef SQLITE_OS_WIN 372a76e8589Sdrh # define SQLITE_OS_WIN 0 373a76e8589Sdrh # undef SQLITE_OS_OS2 374a76e8589Sdrh # define SQLITE_OS_OS2 0 375a76e8589Sdrh # else 376a76e8589Sdrh # undef SQLITE_OS_OTHER 377a76e8589Sdrh # endif 378a76e8589Sdrh #endif 379a76e8589Sdrh #if !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_OTHER) 380a76e8589Sdrh # define SQLITE_OS_OTHER 0 381a76e8589Sdrh # ifndef SQLITE_OS_WIN 382a76e8589Sdrh # if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) \ 383a76e8589Sdrh || defined(__MINGW32__) || defined(__BORLANDC__) 384a76e8589Sdrh # define SQLITE_OS_WIN 1 385a76e8589Sdrh # define SQLITE_OS_UNIX 0 386a76e8589Sdrh # define SQLITE_OS_OS2 0 387a76e8589Sdrh # elif defined(__EMX__) || defined(_OS2) || defined(OS2) \ 388a76e8589Sdrh || defined(_OS2_) || defined(__OS2__) 389a76e8589Sdrh # define SQLITE_OS_WIN 0 390a76e8589Sdrh # define SQLITE_OS_UNIX 0 391a76e8589Sdrh # define SQLITE_OS_OS2 1 392a76e8589Sdrh # else 393a76e8589Sdrh # define SQLITE_OS_WIN 0 394a76e8589Sdrh # define SQLITE_OS_UNIX 1 395a76e8589Sdrh # define SQLITE_OS_OS2 0 396a76e8589Sdrh # endif 397a76e8589Sdrh # else 398a76e8589Sdrh # define SQLITE_OS_UNIX 0 399a76e8589Sdrh # define SQLITE_OS_OS2 0 400a76e8589Sdrh # endif 401a76e8589Sdrh #else 402a76e8589Sdrh # ifndef SQLITE_OS_WIN 403a76e8589Sdrh # define SQLITE_OS_WIN 0 404a76e8589Sdrh # endif 405a76e8589Sdrh #endif 406a76e8589Sdrh 407a76e8589Sdrh 408a76e8589Sdrh /* 409a76e8589Sdrh ** Translate UTF8 to MBCS for use in fopen() calls. Return a pointer to the 410a76e8589Sdrh ** translated text.. Call quota_mbcs_free() to deallocate any memory 411a76e8589Sdrh ** used to store the returned pointer when done. 412a76e8589Sdrh */ 413a76e8589Sdrh static char *quota_utf8_to_mbcs(const char *zUtf8){ 414a76e8589Sdrh #if SQLITE_OS_WIN 415a76e8589Sdrh extern char *sqlite3_win32_utf8_to_mbcs(const char*); 416a76e8589Sdrh return sqlite3_win32_utf8_to_mbcs(zUtf8); 417a76e8589Sdrh #else 418a76e8589Sdrh return (char*)zUtf8; /* No-op on unix */ 419a76e8589Sdrh #endif 420a76e8589Sdrh } 421a76e8589Sdrh 422a76e8589Sdrh /* 423a76e8589Sdrh ** Deallocate any memory allocated by quota_utf8_to_mbcs(). 424a76e8589Sdrh */ 425a76e8589Sdrh static void quota_mbcs_free(char *zOld){ 426a76e8589Sdrh #if SQLITE_OS_WIN 427c8ccda60Sdrh sqlite3_free(zOld); 428a76e8589Sdrh #else 429a76e8589Sdrh /* No-op on unix */ 430a76e8589Sdrh #endif 431a76e8589Sdrh } 432a76e8589Sdrh 433748a52cdSdrh /************************* VFS Method Wrappers *****************************/ 434748a52cdSdrh /* 435748a52cdSdrh ** This is the xOpen method used for the "quota" VFS. 436748a52cdSdrh ** 437748a52cdSdrh ** Most of the work is done by the underlying original VFS. This method 438549dc1baSdrh ** simply links the new file into the appropriate quota group if it is a 439549dc1baSdrh ** file that needs to be tracked. 440748a52cdSdrh */ 441748a52cdSdrh static int quotaOpen( 442549dc1baSdrh sqlite3_vfs *pVfs, /* The quota VFS */ 443549dc1baSdrh const char *zName, /* Name of file to be opened */ 444ef9659baSdrh sqlite3_file *pConn, /* Fill in this file descriptor */ 445549dc1baSdrh int flags, /* Flags to control the opening */ 446549dc1baSdrh int *pOutFlags /* Flags showing results of opening */ 447748a52cdSdrh ){ 448549dc1baSdrh int rc; /* Result code */ 449ef9659baSdrh quotaConn *pQuotaOpen; /* The new quota file descriptor */ 450549dc1baSdrh quotaFile *pFile; /* Corresponding quotaFile obj */ 451549dc1baSdrh quotaGroup *pGroup; /* The group file belongs to */ 452549dc1baSdrh sqlite3_file *pSubOpen; /* Real file descriptor */ 453549dc1baSdrh sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs; /* Real VFS */ 454748a52cdSdrh 455748a52cdSdrh /* If the file is not a main database file or a WAL, then use the 456748a52cdSdrh ** normal xOpen method. 457748a52cdSdrh */ 4588cf35eb4Sdan if( (flags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_WAL))==0 ){ 459ef9659baSdrh return pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags); 460748a52cdSdrh } 461748a52cdSdrh 462748a52cdSdrh /* If the name of the file does not match any quota group, then 463748a52cdSdrh ** use the normal xOpen method. 464748a52cdSdrh */ 465748a52cdSdrh quotaEnter(); 4668cf35eb4Sdan pGroup = quotaGroupFind(zName); 467748a52cdSdrh if( pGroup==0 ){ 468ef9659baSdrh rc = pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags); 469748a52cdSdrh }else{ 4708cf35eb4Sdan /* If we get to this point, it means the file needs to be quota tracked. 4718cf35eb4Sdan */ 472ef9659baSdrh pQuotaOpen = (quotaConn*)pConn; 473ef9659baSdrh pSubOpen = quotaSubOpen(pConn); 474745de2e4Sdrh rc = pOrigVfs->xOpen(pOrigVfs, zName, pSubOpen, flags, pOutFlags); 4758cf35eb4Sdan if( rc==SQLITE_OK ){ 47660f21e4bSdrh pFile = quotaFindFile(pGroup, zName, 1); 477745de2e4Sdrh if( pFile==0 ){ 478745de2e4Sdrh quotaLeave(); 479745de2e4Sdrh pSubOpen->pMethods->xClose(pSubOpen); 480745de2e4Sdrh return SQLITE_NOMEM; 481745de2e4Sdrh } 482796af156Sdrh pFile->deleteOnClose = (flags & SQLITE_OPEN_DELETEONCLOSE)!=0; 483745de2e4Sdrh pFile->nRef++; 484745de2e4Sdrh pQuotaOpen->pFile = pFile; 485745de2e4Sdrh if( pSubOpen->pMethods->iVersion==1 ){ 486745de2e4Sdrh pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV1; 4878cf35eb4Sdan }else{ 488745de2e4Sdrh pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV2; 489748a52cdSdrh } 490748a52cdSdrh } 4918cf35eb4Sdan } 4928cf35eb4Sdan quotaLeave(); 493748a52cdSdrh return rc; 494748a52cdSdrh } 495748a52cdSdrh 496796af156Sdrh /* 497796af156Sdrh ** This is the xDelete method used for the "quota" VFS. 498796af156Sdrh ** 499796af156Sdrh ** If the file being deleted is part of the quota group, then reduce 500796af156Sdrh ** the size of the quota group accordingly. And remove the file from 501796af156Sdrh ** the set of files in the quota group. 502796af156Sdrh */ 503796af156Sdrh static int quotaDelete( 504796af156Sdrh sqlite3_vfs *pVfs, /* The quota VFS */ 505796af156Sdrh const char *zName, /* Name of file to be deleted */ 506796af156Sdrh int syncDir /* Do a directory sync after deleting */ 507796af156Sdrh ){ 508796af156Sdrh int rc; /* Result code */ 509796af156Sdrh quotaFile *pFile; /* Files in the quota */ 510796af156Sdrh quotaGroup *pGroup; /* The group file belongs to */ 511796af156Sdrh sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs; /* Real VFS */ 512796af156Sdrh 513796af156Sdrh /* Do the actual file delete */ 514796af156Sdrh rc = pOrigVfs->xDelete(pOrigVfs, zName, syncDir); 515796af156Sdrh 516796af156Sdrh /* If the file just deleted is a member of a quota group, then remove 517796af156Sdrh ** it from that quota group. 518796af156Sdrh */ 519796af156Sdrh if( rc==SQLITE_OK ){ 520796af156Sdrh quotaEnter(); 521796af156Sdrh pGroup = quotaGroupFind(zName); 522796af156Sdrh if( pGroup ){ 52360f21e4bSdrh pFile = quotaFindFile(pGroup, zName, 0); 524796af156Sdrh if( pFile ){ 525796af156Sdrh if( pFile->nRef ){ 526796af156Sdrh pFile->deleteOnClose = 1; 527796af156Sdrh }else{ 528796af156Sdrh quotaRemoveFile(pFile); 529796af156Sdrh quotaGroupDeref(pGroup); 530796af156Sdrh } 531796af156Sdrh } 532796af156Sdrh } 533796af156Sdrh quotaLeave(); 534796af156Sdrh } 535796af156Sdrh return rc; 536796af156Sdrh } 537796af156Sdrh 538796af156Sdrh 539748a52cdSdrh /************************ I/O Method Wrappers *******************************/ 540748a52cdSdrh 541748a52cdSdrh /* xClose requests get passed through to the original VFS. But we 542ef9659baSdrh ** also have to unlink the quotaConn from the quotaFile and quotaGroup. 543ef9659baSdrh ** The quotaFile and/or quotaGroup are freed if they are no longer in use. 544748a52cdSdrh */ 545ef9659baSdrh static int quotaClose(sqlite3_file *pConn){ 546ef9659baSdrh quotaConn *p = (quotaConn*)pConn; 547745de2e4Sdrh quotaFile *pFile = p->pFile; 548ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 549748a52cdSdrh int rc; 550745de2e4Sdrh rc = pSubOpen->pMethods->xClose(pSubOpen); 551748a52cdSdrh quotaEnter(); 552745de2e4Sdrh pFile->nRef--; 553745de2e4Sdrh if( pFile->nRef==0 ){ 554745de2e4Sdrh quotaGroup *pGroup = pFile->pGroup; 55569b2232dSdrh if( pFile->deleteOnClose ){ 55669b2232dSdrh gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0); 55769b2232dSdrh quotaRemoveFile(pFile); 55869b2232dSdrh } 559748a52cdSdrh quotaGroupDeref(pGroup); 560745de2e4Sdrh } 561748a52cdSdrh quotaLeave(); 562748a52cdSdrh return rc; 563748a52cdSdrh } 564748a52cdSdrh 565748a52cdSdrh /* Pass xRead requests directory thru to the original VFS without 566748a52cdSdrh ** further processing. 567748a52cdSdrh */ 568748a52cdSdrh static int quotaRead( 569ef9659baSdrh sqlite3_file *pConn, 570748a52cdSdrh void *pBuf, 571748a52cdSdrh int iAmt, 572748a52cdSdrh sqlite3_int64 iOfst 573748a52cdSdrh ){ 574ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 575745de2e4Sdrh return pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst); 576748a52cdSdrh } 577748a52cdSdrh 578748a52cdSdrh /* Check xWrite requests to see if they expand the file. If they do, 579748a52cdSdrh ** the perform a quota check before passing them through to the 580748a52cdSdrh ** original VFS. 581748a52cdSdrh */ 582748a52cdSdrh static int quotaWrite( 583ef9659baSdrh sqlite3_file *pConn, 5848cf35eb4Sdan const void *pBuf, 585748a52cdSdrh int iAmt, 586748a52cdSdrh sqlite3_int64 iOfst 587748a52cdSdrh ){ 588ef9659baSdrh quotaConn *p = (quotaConn*)pConn; 589ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 590748a52cdSdrh sqlite3_int64 iEnd = iOfst+iAmt; 591748a52cdSdrh quotaGroup *pGroup; 592745de2e4Sdrh quotaFile *pFile = p->pFile; 593748a52cdSdrh sqlite3_int64 szNew; 594748a52cdSdrh 595745de2e4Sdrh if( pFile->iSize<iEnd ){ 596745de2e4Sdrh pGroup = pFile->pGroup; 597748a52cdSdrh quotaEnter(); 598745de2e4Sdrh szNew = pGroup->iSize - pFile->iSize + iEnd; 5998cf35eb4Sdan if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ 600748a52cdSdrh if( pGroup->xCallback ){ 601745de2e4Sdrh pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew, 602748a52cdSdrh pGroup->pArg); 603748a52cdSdrh } 6048cf35eb4Sdan if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ 605748a52cdSdrh quotaLeave(); 606748a52cdSdrh return SQLITE_FULL; 607748a52cdSdrh } 608748a52cdSdrh } 609748a52cdSdrh pGroup->iSize = szNew; 610745de2e4Sdrh pFile->iSize = iEnd; 611748a52cdSdrh quotaLeave(); 612748a52cdSdrh } 613745de2e4Sdrh return pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst); 614748a52cdSdrh } 615748a52cdSdrh 616748a52cdSdrh /* Pass xTruncate requests thru to the original VFS. If the 617748a52cdSdrh ** success, update the file size. 618748a52cdSdrh */ 619ef9659baSdrh static int quotaTruncate(sqlite3_file *pConn, sqlite3_int64 size){ 620ef9659baSdrh quotaConn *p = (quotaConn*)pConn; 621ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 622745de2e4Sdrh int rc = pSubOpen->pMethods->xTruncate(pSubOpen, size); 623745de2e4Sdrh quotaFile *pFile = p->pFile; 624745de2e4Sdrh quotaGroup *pGroup; 625748a52cdSdrh if( rc==SQLITE_OK ){ 626748a52cdSdrh quotaEnter(); 627745de2e4Sdrh pGroup = pFile->pGroup; 628745de2e4Sdrh pGroup->iSize -= pFile->iSize; 629745de2e4Sdrh pFile->iSize = size; 630748a52cdSdrh pGroup->iSize += size; 631748a52cdSdrh quotaLeave(); 632748a52cdSdrh } 633748a52cdSdrh return rc; 634748a52cdSdrh } 635748a52cdSdrh 636748a52cdSdrh /* Pass xSync requests through to the original VFS without change 637748a52cdSdrh */ 638ef9659baSdrh static int quotaSync(sqlite3_file *pConn, int flags){ 639ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 640745de2e4Sdrh return pSubOpen->pMethods->xSync(pSubOpen, flags); 641748a52cdSdrh } 642748a52cdSdrh 643748a52cdSdrh /* Pass xFileSize requests through to the original VFS but then 644748a52cdSdrh ** update the quotaGroup with the new size before returning. 645748a52cdSdrh */ 646ef9659baSdrh static int quotaFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){ 647ef9659baSdrh quotaConn *p = (quotaConn*)pConn; 648ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 649745de2e4Sdrh quotaFile *pFile = p->pFile; 650748a52cdSdrh quotaGroup *pGroup; 651748a52cdSdrh sqlite3_int64 sz; 652748a52cdSdrh int rc; 653748a52cdSdrh 654745de2e4Sdrh rc = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); 655748a52cdSdrh if( rc==SQLITE_OK ){ 656748a52cdSdrh quotaEnter(); 657745de2e4Sdrh pGroup = pFile->pGroup; 658745de2e4Sdrh pGroup->iSize -= pFile->iSize; 659745de2e4Sdrh pFile->iSize = sz; 660748a52cdSdrh pGroup->iSize += sz; 661748a52cdSdrh quotaLeave(); 662748a52cdSdrh *pSize = sz; 663748a52cdSdrh } 664748a52cdSdrh return rc; 665748a52cdSdrh } 666748a52cdSdrh 667748a52cdSdrh /* Pass xLock requests through to the original VFS unchanged. 668748a52cdSdrh */ 669ef9659baSdrh static int quotaLock(sqlite3_file *pConn, int lock){ 670ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 671745de2e4Sdrh return pSubOpen->pMethods->xLock(pSubOpen, lock); 672748a52cdSdrh } 673748a52cdSdrh 674748a52cdSdrh /* Pass xUnlock requests through to the original VFS unchanged. 675748a52cdSdrh */ 676ef9659baSdrh static int quotaUnlock(sqlite3_file *pConn, int lock){ 677ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 678745de2e4Sdrh return pSubOpen->pMethods->xUnlock(pSubOpen, lock); 679748a52cdSdrh } 680748a52cdSdrh 681748a52cdSdrh /* Pass xCheckReservedLock requests through to the original VFS unchanged. 682748a52cdSdrh */ 683ef9659baSdrh static int quotaCheckReservedLock(sqlite3_file *pConn, int *pResOut){ 684ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 685745de2e4Sdrh return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut); 686748a52cdSdrh } 687748a52cdSdrh 688748a52cdSdrh /* Pass xFileControl requests through to the original VFS unchanged. 689748a52cdSdrh */ 690ef9659baSdrh static int quotaFileControl(sqlite3_file *pConn, int op, void *pArg){ 691ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 692745de2e4Sdrh return pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); 693748a52cdSdrh } 694748a52cdSdrh 695748a52cdSdrh /* Pass xSectorSize requests through to the original VFS unchanged. 696748a52cdSdrh */ 697ef9659baSdrh static int quotaSectorSize(sqlite3_file *pConn){ 698ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 699745de2e4Sdrh return pSubOpen->pMethods->xSectorSize(pSubOpen); 700748a52cdSdrh } 701748a52cdSdrh 702748a52cdSdrh /* Pass xDeviceCharacteristics requests through to the original VFS unchanged. 703748a52cdSdrh */ 704ef9659baSdrh static int quotaDeviceCharacteristics(sqlite3_file *pConn){ 705ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 706745de2e4Sdrh return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen); 707748a52cdSdrh } 708748a52cdSdrh 709748a52cdSdrh /* Pass xShmMap requests through to the original VFS unchanged. 710748a52cdSdrh */ 711748a52cdSdrh static int quotaShmMap( 712ef9659baSdrh sqlite3_file *pConn, /* Handle open on database file */ 713748a52cdSdrh int iRegion, /* Region to retrieve */ 714748a52cdSdrh int szRegion, /* Size of regions */ 715748a52cdSdrh int bExtend, /* True to extend file if necessary */ 716748a52cdSdrh void volatile **pp /* OUT: Mapped memory */ 717748a52cdSdrh ){ 718ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 719745de2e4Sdrh return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend, pp); 720748a52cdSdrh } 721748a52cdSdrh 722748a52cdSdrh /* Pass xShmLock requests through to the original VFS unchanged. 723748a52cdSdrh */ 724748a52cdSdrh static int quotaShmLock( 725ef9659baSdrh sqlite3_file *pConn, /* Database file holding the shared memory */ 726748a52cdSdrh int ofst, /* First lock to acquire or release */ 727748a52cdSdrh int n, /* Number of locks to acquire or release */ 728748a52cdSdrh int flags /* What to do with the lock */ 729748a52cdSdrh ){ 730ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 731745de2e4Sdrh return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags); 732748a52cdSdrh } 733748a52cdSdrh 734748a52cdSdrh /* Pass xShmBarrier requests through to the original VFS unchanged. 735748a52cdSdrh */ 736ef9659baSdrh static void quotaShmBarrier(sqlite3_file *pConn){ 737ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 738745de2e4Sdrh pSubOpen->pMethods->xShmBarrier(pSubOpen); 739748a52cdSdrh } 740748a52cdSdrh 741748a52cdSdrh /* Pass xShmUnmap requests through to the original VFS unchanged. 742748a52cdSdrh */ 743ef9659baSdrh static int quotaShmUnmap(sqlite3_file *pConn, int deleteFlag){ 744ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 745745de2e4Sdrh return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag); 746748a52cdSdrh } 747748a52cdSdrh 748748a52cdSdrh /************************** Public Interfaces *****************************/ 749748a52cdSdrh /* 750748a52cdSdrh ** Initialize the quota VFS shim. Use the VFS named zOrigVfsName 751748a52cdSdrh ** as the VFS that does the actual work. Use the default if 752748a52cdSdrh ** zOrigVfsName==NULL. 753748a52cdSdrh ** 754748a52cdSdrh ** The quota VFS shim is named "quota". It will become the default 755748a52cdSdrh ** VFS if makeDefault is non-zero. 756748a52cdSdrh ** 757748a52cdSdrh ** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once 758748a52cdSdrh ** during start-up. 759748a52cdSdrh */ 760748a52cdSdrh int sqlite3_quota_initialize(const char *zOrigVfsName, int makeDefault){ 761748a52cdSdrh sqlite3_vfs *pOrigVfs; 7628cf35eb4Sdan if( gQuota.isInitialized ) return SQLITE_MISUSE; 763748a52cdSdrh pOrigVfs = sqlite3_vfs_find(zOrigVfsName); 764748a52cdSdrh if( pOrigVfs==0 ) return SQLITE_ERROR; 7658cf35eb4Sdan assert( pOrigVfs!=&gQuota.sThisVfs ); 7668cf35eb4Sdan gQuota.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); 7678cf35eb4Sdan if( !gQuota.pMutex ){ 7688cf35eb4Sdan return SQLITE_NOMEM; 7698cf35eb4Sdan } 7708cf35eb4Sdan gQuota.isInitialized = 1; 771748a52cdSdrh gQuota.pOrigVfs = pOrigVfs; 772748a52cdSdrh gQuota.sThisVfs = *pOrigVfs; 773748a52cdSdrh gQuota.sThisVfs.xOpen = quotaOpen; 774796af156Sdrh gQuota.sThisVfs.xDelete = quotaDelete; 775ef9659baSdrh gQuota.sThisVfs.szOsFile += sizeof(quotaConn); 776748a52cdSdrh gQuota.sThisVfs.zName = "quota"; 777748a52cdSdrh gQuota.sIoMethodsV1.iVersion = 1; 778748a52cdSdrh gQuota.sIoMethodsV1.xClose = quotaClose; 779748a52cdSdrh gQuota.sIoMethodsV1.xRead = quotaRead; 780748a52cdSdrh gQuota.sIoMethodsV1.xWrite = quotaWrite; 781748a52cdSdrh gQuota.sIoMethodsV1.xTruncate = quotaTruncate; 782748a52cdSdrh gQuota.sIoMethodsV1.xSync = quotaSync; 783748a52cdSdrh gQuota.sIoMethodsV1.xFileSize = quotaFileSize; 784748a52cdSdrh gQuota.sIoMethodsV1.xLock = quotaLock; 785748a52cdSdrh gQuota.sIoMethodsV1.xUnlock = quotaUnlock; 786748a52cdSdrh gQuota.sIoMethodsV1.xCheckReservedLock = quotaCheckReservedLock; 787748a52cdSdrh gQuota.sIoMethodsV1.xFileControl = quotaFileControl; 7888cf35eb4Sdan gQuota.sIoMethodsV1.xSectorSize = quotaSectorSize; 789748a52cdSdrh gQuota.sIoMethodsV1.xDeviceCharacteristics = quotaDeviceCharacteristics; 790748a52cdSdrh gQuota.sIoMethodsV2 = gQuota.sIoMethodsV1; 791748a52cdSdrh gQuota.sIoMethodsV2.iVersion = 2; 792748a52cdSdrh gQuota.sIoMethodsV2.xShmMap = quotaShmMap; 793748a52cdSdrh gQuota.sIoMethodsV2.xShmLock = quotaShmLock; 794748a52cdSdrh gQuota.sIoMethodsV2.xShmBarrier = quotaShmBarrier; 795748a52cdSdrh gQuota.sIoMethodsV2.xShmUnmap = quotaShmUnmap; 7968cf35eb4Sdan sqlite3_vfs_register(&gQuota.sThisVfs, makeDefault); 7978cf35eb4Sdan return SQLITE_OK; 798748a52cdSdrh } 799748a52cdSdrh 800748a52cdSdrh /* 801748a52cdSdrh ** Shutdown the quota system. 802748a52cdSdrh ** 803748a52cdSdrh ** All SQLite database connections must be closed before calling this 804748a52cdSdrh ** routine. 805748a52cdSdrh ** 806796af156Sdrh ** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once while 807748a52cdSdrh ** shutting down in order to free all remaining quota groups. 808748a52cdSdrh */ 809748a52cdSdrh int sqlite3_quota_shutdown(void){ 81075c19174Sdrh quotaGroup *pGroup; 811748a52cdSdrh if( gQuota.isInitialized==0 ) return SQLITE_MISUSE; 81275c19174Sdrh for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){ 813796af156Sdrh if( quotaGroupOpenFileCount(pGroup)>0 ) return SQLITE_MISUSE; 8148cf35eb4Sdan } 8158cf35eb4Sdan while( gQuota.pGroup ){ 81675c19174Sdrh pGroup = gQuota.pGroup; 81775c19174Sdrh gQuota.pGroup = pGroup->pNext; 8189fc7702cSdan pGroup->iLimit = 0; 819796af156Sdrh assert( quotaGroupOpenFileCount(pGroup)==0 ); 8209fc7702cSdan quotaGroupDeref(pGroup); 8218cf35eb4Sdan } 822748a52cdSdrh gQuota.isInitialized = 0; 823748a52cdSdrh sqlite3_mutex_free(gQuota.pMutex); 8248cf35eb4Sdan sqlite3_vfs_unregister(&gQuota.sThisVfs); 825748a52cdSdrh memset(&gQuota, 0, sizeof(gQuota)); 8268cf35eb4Sdan return SQLITE_OK; 827748a52cdSdrh } 828748a52cdSdrh 829748a52cdSdrh /* 830748a52cdSdrh ** Create or destroy a quota group. 831748a52cdSdrh ** 832748a52cdSdrh ** The quota group is defined by the zPattern. When calling this routine 833748a52cdSdrh ** with a zPattern for a quota group that already exists, this routine 834748a52cdSdrh ** merely updates the iLimit, xCallback, and pArg values for that quota 835748a52cdSdrh ** group. If zPattern is new, then a new quota group is created. 836748a52cdSdrh ** 837748a52cdSdrh ** If the iLimit for a quota group is set to zero, then the quota group 838748a52cdSdrh ** is disabled and will be deleted when the last database connection using 839748a52cdSdrh ** the quota group is closed. 840748a52cdSdrh ** 841748a52cdSdrh ** Calling this routine on a zPattern that does not exist and with a 842748a52cdSdrh ** zero iLimit is a no-op. 843748a52cdSdrh ** 844748a52cdSdrh ** A quota group must exist with a non-zero iLimit prior to opening 845748a52cdSdrh ** database connections if those connections are to participate in the 8468cf35eb4Sdan ** quota group. Creating a quota group does not affect database connections 847748a52cdSdrh ** that are already open. 848748a52cdSdrh */ 849748a52cdSdrh int sqlite3_quota_set( 850748a52cdSdrh const char *zPattern, /* The filename pattern */ 851748a52cdSdrh sqlite3_int64 iLimit, /* New quota to set for this quota group */ 852748a52cdSdrh void (*xCallback)( /* Callback invoked when going over quota */ 853748a52cdSdrh const char *zFilename, /* Name of file whose size increases */ 854748a52cdSdrh sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ 855748a52cdSdrh sqlite3_int64 iSize, /* Total size of all files in the group */ 856748a52cdSdrh void *pArg /* Client data */ 8578cf35eb4Sdan ), 858ef9659baSdrh void *pArg, /* client data passed thru to callback */ 859ef9659baSdrh void (*xDestroy)(void*) /* Optional destructor for pArg */ 860748a52cdSdrh ){ 861748a52cdSdrh quotaGroup *pGroup; 862748a52cdSdrh quotaEnter(); 863748a52cdSdrh pGroup = gQuota.pGroup; 864748a52cdSdrh while( pGroup && strcmp(pGroup->zPattern, zPattern)!=0 ){ 865748a52cdSdrh pGroup = pGroup->pNext; 866748a52cdSdrh } 86761cc423aSdrh if( pGroup==0 ){ 868748a52cdSdrh int nPattern = strlen(zPattern); 86961cc423aSdrh if( iLimit<=0 ){ 87061cc423aSdrh quotaLeave(); 87161cc423aSdrh return SQLITE_OK; 87261cc423aSdrh } 873b0a32832Sdan pGroup = (quotaGroup *)sqlite3_malloc( sizeof(*pGroup) + nPattern + 1 ); 874748a52cdSdrh if( pGroup==0 ){ 875748a52cdSdrh quotaLeave(); 876748a52cdSdrh return SQLITE_NOMEM; 877748a52cdSdrh } 878748a52cdSdrh memset(pGroup, 0, sizeof(*pGroup)); 879748a52cdSdrh pGroup->zPattern = (char*)&pGroup[1]; 8808cf35eb4Sdan memcpy((char *)pGroup->zPattern, zPattern, nPattern+1); 881748a52cdSdrh if( gQuota.pGroup ) gQuota.pGroup->ppPrev = &pGroup->pNext; 8828cf35eb4Sdan pGroup->pNext = gQuota.pGroup; 8838cf35eb4Sdan pGroup->ppPrev = &gQuota.pGroup; 8848cf35eb4Sdan gQuota.pGroup = pGroup; 885748a52cdSdrh } 886748a52cdSdrh pGroup->iLimit = iLimit; 887748a52cdSdrh pGroup->xCallback = xCallback; 888ef9659baSdrh if( pGroup->xDestroy && pGroup->pArg!=pArg ){ 889ef9659baSdrh pGroup->xDestroy(pGroup->pArg); 890ef9659baSdrh } 891748a52cdSdrh pGroup->pArg = pArg; 892ef9659baSdrh pGroup->xDestroy = xDestroy; 893748a52cdSdrh quotaGroupDeref(pGroup); 894748a52cdSdrh quotaLeave(); 895748a52cdSdrh return SQLITE_OK; 896748a52cdSdrh } 897748a52cdSdrh 898b6020c4eSdrh /* 899b6020c4eSdrh ** Bring the named file under quota management. Or if it is already under 900b6020c4eSdrh ** management, update its size. 901b6020c4eSdrh */ 902b6020c4eSdrh int sqlite3_quota_file(const char *zFilename){ 903b6020c4eSdrh char *zFull; 904b6020c4eSdrh sqlite3_file *fd; 905b6020c4eSdrh int rc; 906b6020c4eSdrh int outFlags = 0; 907b6020c4eSdrh sqlite3_int64 iSize; 908*663cebfeSdrh fd = (sqlite3_file*)sqlite3_malloc(gQuota.sThisVfs.szOsFile + 909*663cebfeSdrh gQuota.sThisVfs.mxPathname+1); 910b6020c4eSdrh if( fd==0 ) return SQLITE_NOMEM; 911b6020c4eSdrh zFull = gQuota.sThisVfs.szOsFile + (char*)fd; 912b6020c4eSdrh rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename, 913b6020c4eSdrh gQuota.sThisVfs.mxPathname+1, zFull); 914b6020c4eSdrh if( rc==SQLITE_OK ){ 915b6020c4eSdrh rc = quotaOpen(&gQuota.sThisVfs, zFull, fd, 916b6020c4eSdrh SQLITE_OPEN_READONLY | SQLITE_OPEN_MAIN_DB, &outFlags); 917b6020c4eSdrh } 918b6020c4eSdrh if( rc==SQLITE_OK ){ 91926541c9aSdrh fd->pMethods->xFileSize(fd, &iSize); 92026541c9aSdrh fd->pMethods->xClose(fd); 921b6020c4eSdrh }else if( rc==SQLITE_CANTOPEN ){ 922b6020c4eSdrh quotaGroup *pGroup; 923b6020c4eSdrh quotaFile *pFile; 924b6020c4eSdrh quotaEnter(); 925b6020c4eSdrh pGroup = quotaGroupFind(zFull); 926b6020c4eSdrh if( pGroup ){ 92760f21e4bSdrh pFile = quotaFindFile(pGroup, zFull, 0); 928b6020c4eSdrh if( pFile ) quotaRemoveFile(pFile); 929b6020c4eSdrh } 930b6020c4eSdrh quotaLeave(); 931b6020c4eSdrh } 932b6020c4eSdrh sqlite3_free(fd); 933b6020c4eSdrh return rc; 934b6020c4eSdrh } 935b6020c4eSdrh 93660f21e4bSdrh /* 93760f21e4bSdrh ** Open a potentially quotaed file for I/O. 93860f21e4bSdrh */ 93960f21e4bSdrh quota_FILE *sqlite3_quota_fopen(const char *zFilename, const char *zMode){ 94060f21e4bSdrh quota_FILE *p = 0; 94160f21e4bSdrh char *zFull = 0; 942a76e8589Sdrh char *zFullTranslated; 94360f21e4bSdrh int rc; 94460f21e4bSdrh quotaGroup *pGroup; 94560f21e4bSdrh quotaFile *pFile; 94660f21e4bSdrh 947*663cebfeSdrh zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1); 948a76e8589Sdrh if( zFull==0 ) return 0; 94960f21e4bSdrh rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename, 95060f21e4bSdrh gQuota.sThisVfs.mxPathname+1, zFull); 95160f21e4bSdrh if( rc ) goto quota_fopen_error; 952*663cebfeSdrh p = (quota_FILE*)sqlite3_malloc(sizeof(*p)); 95360f21e4bSdrh if( p==0 ) goto quota_fopen_error; 95460f21e4bSdrh memset(p, 0, sizeof(*p)); 955a76e8589Sdrh zFullTranslated = quota_utf8_to_mbcs(zFull); 956a76e8589Sdrh if( zFullTranslated==0 ) goto quota_fopen_error; 957a76e8589Sdrh p->f = fopen(zFullTranslated, zMode); 958a76e8589Sdrh quota_mbcs_free(zFullTranslated); 95960f21e4bSdrh if( p->f==0 ) goto quota_fopen_error; 96060f21e4bSdrh quotaEnter(); 96160f21e4bSdrh pGroup = quotaGroupFind(zFull); 96260f21e4bSdrh if( pGroup ){ 96360f21e4bSdrh pFile = quotaFindFile(pGroup, zFull, 1); 96460f21e4bSdrh if( pFile==0 ){ 96560f21e4bSdrh quotaLeave(); 96660f21e4bSdrh goto quota_fopen_error; 96760f21e4bSdrh } 96860f21e4bSdrh pFile->nRef++; 96960f21e4bSdrh p->pFile = pFile; 97060f21e4bSdrh } 97160f21e4bSdrh quotaLeave(); 97260f21e4bSdrh sqlite3_free(zFull); 97360f21e4bSdrh return p; 97460f21e4bSdrh 97560f21e4bSdrh quota_fopen_error: 97660f21e4bSdrh sqlite3_free(zFull); 97760f21e4bSdrh if( p && p->f ) fclose(p->f); 97860f21e4bSdrh sqlite3_free(p); 97960f21e4bSdrh return 0; 98060f21e4bSdrh } 98160f21e4bSdrh 98260f21e4bSdrh /* 98360f21e4bSdrh ** Read content from a quota_FILE 98460f21e4bSdrh */ 98560f21e4bSdrh size_t sqlite3_quota_fread( 98660f21e4bSdrh void *pBuf, /* Store the content here */ 98760f21e4bSdrh size_t size, /* Size of each element */ 98860f21e4bSdrh size_t nmemb, /* Number of elements to read */ 98960f21e4bSdrh quota_FILE *p /* Read from this quota_FILE object */ 99060f21e4bSdrh ){ 99160f21e4bSdrh return fread(pBuf, size, nmemb, p->f); 99260f21e4bSdrh } 99360f21e4bSdrh 99460f21e4bSdrh /* 99560f21e4bSdrh ** Write content into a quota_FILE. Invoke the quota callback and block 99660f21e4bSdrh ** the write if we exceed quota. 99760f21e4bSdrh */ 99860f21e4bSdrh size_t sqlite3_quota_fwrite( 99960f21e4bSdrh void *pBuf, /* Take content to write from here */ 100060f21e4bSdrh size_t size, /* Size of each element */ 100160f21e4bSdrh size_t nmemb, /* Number of elements */ 100260f21e4bSdrh quota_FILE *p /* Write to this quota_FILE objecct */ 100360f21e4bSdrh ){ 100460f21e4bSdrh sqlite3_int64 iOfst; 100560f21e4bSdrh sqlite3_int64 iEnd; 100660f21e4bSdrh sqlite3_int64 szNew; 100760f21e4bSdrh quotaFile *pFile; 100860f21e4bSdrh 100960f21e4bSdrh iOfst = ftell(p->f); 101060f21e4bSdrh iEnd = iOfst + size*nmemb; 101160f21e4bSdrh pFile = p->pFile; 1012c8ccda60Sdrh if( pFile && pFile->iSize<iEnd ){ 101360f21e4bSdrh quotaGroup *pGroup = pFile->pGroup; 101460f21e4bSdrh quotaEnter(); 101560f21e4bSdrh szNew = pGroup->iSize - pFile->iSize + iEnd; 101660f21e4bSdrh if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ 101760f21e4bSdrh if( pGroup->xCallback ){ 101860f21e4bSdrh pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew, 101960f21e4bSdrh pGroup->pArg); 102060f21e4bSdrh } 102160f21e4bSdrh if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ 102260f21e4bSdrh iEnd = pGroup->iLimit - pGroup->iSize + pFile->iSize; 102360f21e4bSdrh nmemb = (iEnd - iOfst)/size; 102460f21e4bSdrh iEnd = iOfst + size*nmemb; 102560f21e4bSdrh szNew = pGroup->iSize - pFile->iSize + iEnd; 102660f21e4bSdrh } 102760f21e4bSdrh } 102860f21e4bSdrh pGroup->iSize = szNew; 102960f21e4bSdrh pFile->iSize = iEnd; 103060f21e4bSdrh quotaLeave(); 103160f21e4bSdrh } 103260f21e4bSdrh return fwrite(pBuf, size, nmemb, p->f); 103360f21e4bSdrh } 103460f21e4bSdrh 103560f21e4bSdrh /* 103660f21e4bSdrh ** Close an open quota_FILE stream. 103760f21e4bSdrh */ 103860f21e4bSdrh int sqlite3_quota_fclose(quota_FILE *p){ 103960f21e4bSdrh int rc; 104060f21e4bSdrh quotaFile *pFile; 104160f21e4bSdrh rc = fclose(p->f); 104260f21e4bSdrh pFile = p->pFile; 1043c8ccda60Sdrh if( pFile ){ 104460f21e4bSdrh quotaEnter(); 104560f21e4bSdrh pFile->nRef--; 104660f21e4bSdrh if( pFile->nRef==0 ){ 104760f21e4bSdrh quotaGroup *pGroup = pFile->pGroup; 104869b2232dSdrh if( pFile->deleteOnClose ){ 104969b2232dSdrh gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0); 105069b2232dSdrh quotaRemoveFile(pFile); 105169b2232dSdrh } 105260f21e4bSdrh quotaGroupDeref(pGroup); 105360f21e4bSdrh } 105460f21e4bSdrh quotaLeave(); 1055c8ccda60Sdrh } 105660f21e4bSdrh sqlite3_free(p); 105760f21e4bSdrh return rc; 105860f21e4bSdrh } 105960f21e4bSdrh 106060f21e4bSdrh /* 106169b2232dSdrh ** Flush memory buffers for a quota_FILE to disk. 106269b2232dSdrh */ 106369b2232dSdrh int sqlite3_quota_fflush(quota_FILE *p){ 106469b2232dSdrh return fflush(p->f); 106569b2232dSdrh } 106669b2232dSdrh 106769b2232dSdrh /* 106860f21e4bSdrh ** Seek on a quota_FILE stream. 106960f21e4bSdrh */ 107060f21e4bSdrh int sqlite3_quota_fseek(quota_FILE *p, long offset, int whence){ 107160f21e4bSdrh return fseek(p->f, offset, whence); 107260f21e4bSdrh } 107360f21e4bSdrh 107460f21e4bSdrh /* 107560f21e4bSdrh ** rewind a quota_FILE stream. 107660f21e4bSdrh */ 107760f21e4bSdrh void sqlite3_quota_rewind(quota_FILE *p){ 107860f21e4bSdrh rewind(p->f); 107960f21e4bSdrh } 108060f21e4bSdrh 108160f21e4bSdrh /* 108260f21e4bSdrh ** Tell the current location of a quota_FILE stream. 108360f21e4bSdrh */ 108460f21e4bSdrh long sqlite3_quota_ftell(quota_FILE *p){ 108560f21e4bSdrh return ftell(p->f); 108660f21e4bSdrh } 108760f21e4bSdrh 108860f21e4bSdrh /* 108969b2232dSdrh ** Remove a managed file. Update quotas accordingly. 109060f21e4bSdrh */ 109160f21e4bSdrh int sqlite3_quota_remove(const char *zFilename){ 109269b2232dSdrh char *zFull; /* Full pathname for zFilename */ 109369b2232dSdrh int nFull; /* Number of bytes in zFilename */ 109469b2232dSdrh int rc; /* Result code */ 109569b2232dSdrh quotaGroup *pGroup; /* Group containing zFilename */ 109669b2232dSdrh quotaFile *pFile; /* A file in the group */ 109769b2232dSdrh quotaFile *pNextFile; /* next file in the group */ 109869b2232dSdrh int diff; /* Difference between filenames */ 109969b2232dSdrh char c; /* First character past end of pattern */ 110069b2232dSdrh 1101*663cebfeSdrh zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1); 110269b2232dSdrh if( zFull==0 ) return SQLITE_NOMEM; 110369b2232dSdrh rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename, 110469b2232dSdrh gQuota.sThisVfs.mxPathname+1, zFull); 110569b2232dSdrh if( rc ){ 110669b2232dSdrh sqlite3_free(zFull); 110760f21e4bSdrh return rc; 110860f21e4bSdrh } 110960f21e4bSdrh 111069b2232dSdrh /* Figure out the length of the full pathname. If the name ends with 111169b2232dSdrh ** / (or \ on windows) then remove the trailing /. 111269b2232dSdrh */ 111369b2232dSdrh nFull = strlen(zFull); 111469b2232dSdrh if( nFull>0 && (zFull[nFull-1]=='/' || zFull[nFull-1]=='\\') ){ 111569b2232dSdrh nFull--; 111669b2232dSdrh zFull[nFull] = 0; 111769b2232dSdrh } 111869b2232dSdrh 111969b2232dSdrh quotaEnter(); 112069b2232dSdrh pGroup = quotaGroupFind(zFull); 112169b2232dSdrh if( pGroup ){ 112269b2232dSdrh for(pFile=pGroup->pFiles; pFile && rc==SQLITE_OK; pFile=pNextFile){ 112369b2232dSdrh pNextFile = pFile->pNext; 112469b2232dSdrh diff = memcmp(zFull, pFile->zFilename, nFull); 112569b2232dSdrh if( diff==0 && ((c = pFile->zFilename[nFull])==0 || c=='/' || c=='\\') ){ 112669b2232dSdrh if( pFile->nRef ){ 112769b2232dSdrh pFile->deleteOnClose = 1; 112869b2232dSdrh }else{ 112969b2232dSdrh rc = gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0); 113069b2232dSdrh quotaRemoveFile(pFile); 113169b2232dSdrh quotaGroupDeref(pGroup); 113269b2232dSdrh } 113369b2232dSdrh } 113469b2232dSdrh } 113569b2232dSdrh } 113669b2232dSdrh quotaLeave(); 113769b2232dSdrh sqlite3_free(zFull); 113869b2232dSdrh return rc; 113969b2232dSdrh } 1140748a52cdSdrh 1141748a52cdSdrh /***************************** Test Code ***********************************/ 1142748a52cdSdrh #ifdef SQLITE_TEST 1143748a52cdSdrh #include <tcl.h> 11448cf35eb4Sdan 1145ef9659baSdrh /* 1146ef9659baSdrh ** Argument passed to a TCL quota-over-limit callback. 1147ef9659baSdrh */ 11488cf35eb4Sdan typedef struct TclQuotaCallback TclQuotaCallback; 11498cf35eb4Sdan struct TclQuotaCallback { 1150ef9659baSdrh Tcl_Interp *interp; /* Interpreter in which to run the script */ 1151ef9659baSdrh Tcl_Obj *pScript; /* Script to be run */ 11528cf35eb4Sdan }; 11538cf35eb4Sdan 11548cf35eb4Sdan extern const char *sqlite3TestErrorName(int); 11558cf35eb4Sdan 1156ef9659baSdrh 1157ef9659baSdrh /* 1158ef9659baSdrh ** This is the callback from a quota-over-limit. 1159ef9659baSdrh */ 11608cf35eb4Sdan static void tclQuotaCallback( 11618cf35eb4Sdan const char *zFilename, /* Name of file whose size increases */ 11628cf35eb4Sdan sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ 11638cf35eb4Sdan sqlite3_int64 iSize, /* Total size of all files in the group */ 11648cf35eb4Sdan void *pArg /* Client data */ 11658cf35eb4Sdan ){ 11668cf35eb4Sdan TclQuotaCallback *p; /* Callback script object */ 11678cf35eb4Sdan Tcl_Obj *pEval; /* Script to evaluate */ 11688cf35eb4Sdan Tcl_Obj *pVarname; /* Name of variable to pass as 2nd arg */ 11698cf35eb4Sdan unsigned int rnd; /* Random part of pVarname */ 11708cf35eb4Sdan int rc; /* Tcl error code */ 11718cf35eb4Sdan 11728cf35eb4Sdan p = (TclQuotaCallback *)pArg; 11733d3e60e4Sdrh if( p==0 ) return; 11748cf35eb4Sdan 11758cf35eb4Sdan pVarname = Tcl_NewStringObj("::piLimit_", -1); 11768cf35eb4Sdan Tcl_IncrRefCount(pVarname); 11778cf35eb4Sdan sqlite3_randomness(sizeof(rnd), (void *)&rnd); 11788cf35eb4Sdan Tcl_AppendObjToObj(pVarname, Tcl_NewIntObj((int)(rnd&0x7FFFFFFF))); 11798cf35eb4Sdan Tcl_ObjSetVar2(p->interp, pVarname, 0, Tcl_NewWideIntObj(*piLimit), 0); 11808cf35eb4Sdan 11818cf35eb4Sdan pEval = Tcl_DuplicateObj(p->pScript); 11828cf35eb4Sdan Tcl_IncrRefCount(pEval); 11838cf35eb4Sdan Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zFilename, -1)); 11848cf35eb4Sdan Tcl_ListObjAppendElement(0, pEval, pVarname); 11858cf35eb4Sdan Tcl_ListObjAppendElement(0, pEval, Tcl_NewWideIntObj(iSize)); 11868cf35eb4Sdan rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL); 11878cf35eb4Sdan 11888cf35eb4Sdan if( rc==TCL_OK ){ 11898cf35eb4Sdan Tcl_Obj *pLimit = Tcl_ObjGetVar2(p->interp, pVarname, 0, 0); 11908cf35eb4Sdan rc = Tcl_GetWideIntFromObj(p->interp, pLimit, piLimit); 11918cf35eb4Sdan Tcl_UnsetVar(p->interp, Tcl_GetString(pVarname), 0); 11928cf35eb4Sdan } 11938cf35eb4Sdan 11948cf35eb4Sdan Tcl_DecrRefCount(pEval); 11958cf35eb4Sdan Tcl_DecrRefCount(pVarname); 11968cf35eb4Sdan if( rc!=TCL_OK ) Tcl_BackgroundError(p->interp); 11978cf35eb4Sdan } 11988cf35eb4Sdan 11998cf35eb4Sdan /* 1200ef9659baSdrh ** Destructor for a TCL quota-over-limit callback. 1201ef9659baSdrh */ 1202ef9659baSdrh static void tclCallbackDestructor(void *pObj){ 1203ef9659baSdrh TclQuotaCallback *p = (TclQuotaCallback*)pObj; 1204ef9659baSdrh if( p ){ 1205ef9659baSdrh Tcl_DecrRefCount(p->pScript); 12069fc7702cSdan sqlite3_free((char *)p); 1207ef9659baSdrh } 1208ef9659baSdrh } 1209ef9659baSdrh 1210ef9659baSdrh /* 12118cf35eb4Sdan ** tclcmd: sqlite3_quota_initialize NAME MAKEDEFAULT 12128cf35eb4Sdan */ 12138cf35eb4Sdan static int test_quota_initialize( 12148cf35eb4Sdan void * clientData, 12158cf35eb4Sdan Tcl_Interp *interp, 12168cf35eb4Sdan int objc, 12178cf35eb4Sdan Tcl_Obj *CONST objv[] 12188cf35eb4Sdan ){ 12198cf35eb4Sdan const char *zName; /* Name of new quota VFS */ 12208cf35eb4Sdan int makeDefault; /* True to make the new VFS the default */ 12218cf35eb4Sdan int rc; /* Value returned by quota_initialize() */ 12228cf35eb4Sdan 12238cf35eb4Sdan /* Process arguments */ 12248cf35eb4Sdan if( objc!=3 ){ 12258cf35eb4Sdan Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT"); 12268cf35eb4Sdan return TCL_ERROR; 12278cf35eb4Sdan } 12288cf35eb4Sdan zName = Tcl_GetString(objv[1]); 12298cf35eb4Sdan if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR; 12308cf35eb4Sdan if( zName[0]=='\0' ) zName = 0; 12318cf35eb4Sdan 12328cf35eb4Sdan /* Call sqlite3_quota_initialize() */ 12338cf35eb4Sdan rc = sqlite3_quota_initialize(zName, makeDefault); 12348cf35eb4Sdan Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); 12358cf35eb4Sdan 12368cf35eb4Sdan return TCL_OK; 12378cf35eb4Sdan } 12388cf35eb4Sdan 12398cf35eb4Sdan /* 12408cf35eb4Sdan ** tclcmd: sqlite3_quota_shutdown 12418cf35eb4Sdan */ 12428cf35eb4Sdan static int test_quota_shutdown( 12438cf35eb4Sdan void * clientData, 12448cf35eb4Sdan Tcl_Interp *interp, 12458cf35eb4Sdan int objc, 12468cf35eb4Sdan Tcl_Obj *CONST objv[] 12478cf35eb4Sdan ){ 12488cf35eb4Sdan int rc; /* Value returned by quota_shutdown() */ 12498cf35eb4Sdan 12508cf35eb4Sdan if( objc!=1 ){ 12518cf35eb4Sdan Tcl_WrongNumArgs(interp, 1, objv, ""); 12528cf35eb4Sdan return TCL_ERROR; 12538cf35eb4Sdan } 12548cf35eb4Sdan 12558cf35eb4Sdan /* Call sqlite3_quota_shutdown() */ 12568cf35eb4Sdan rc = sqlite3_quota_shutdown(); 12578cf35eb4Sdan Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); 12588cf35eb4Sdan 12598cf35eb4Sdan return TCL_OK; 12608cf35eb4Sdan } 12618cf35eb4Sdan 12628cf35eb4Sdan /* 12638cf35eb4Sdan ** tclcmd: sqlite3_quota_set PATTERN LIMIT SCRIPT 12648cf35eb4Sdan */ 12658cf35eb4Sdan static int test_quota_set( 12668cf35eb4Sdan void * clientData, 12678cf35eb4Sdan Tcl_Interp *interp, 12688cf35eb4Sdan int objc, 12698cf35eb4Sdan Tcl_Obj *CONST objv[] 12708cf35eb4Sdan ){ 12718cf35eb4Sdan const char *zPattern; /* File pattern to configure */ 12728cf35eb4Sdan sqlite3_int64 iLimit; /* Initial quota in bytes */ 12738cf35eb4Sdan Tcl_Obj *pScript; /* Tcl script to invoke to increase quota */ 12748cf35eb4Sdan int rc; /* Value returned by quota_set() */ 12758cf35eb4Sdan TclQuotaCallback *p; /* Callback object */ 1276ef9659baSdrh int nScript; /* Length of callback script */ 12779fc7702cSdan void (*xDestroy)(void*); /* Optional destructor for pArg */ 127806bfd36aSdan void (*xCallback)(const char *, sqlite3_int64 *, sqlite3_int64, void *); 12798cf35eb4Sdan 12808cf35eb4Sdan /* Process arguments */ 12818cf35eb4Sdan if( objc!=4 ){ 12828cf35eb4Sdan Tcl_WrongNumArgs(interp, 1, objv, "PATTERN LIMIT SCRIPT"); 12838cf35eb4Sdan return TCL_ERROR; 12848cf35eb4Sdan } 12858cf35eb4Sdan zPattern = Tcl_GetString(objv[1]); 12868cf35eb4Sdan if( Tcl_GetWideIntFromObj(interp, objv[2], &iLimit) ) return TCL_ERROR; 12878cf35eb4Sdan pScript = objv[3]; 1288ef9659baSdrh Tcl_GetStringFromObj(pScript, &nScript); 12898cf35eb4Sdan 1290ef9659baSdrh if( nScript>0 ){ 12918cf35eb4Sdan /* Allocate a TclQuotaCallback object */ 12929fc7702cSdan p = (TclQuotaCallback *)sqlite3_malloc(sizeof(TclQuotaCallback)); 12939fc7702cSdan if( !p ){ 12949fc7702cSdan Tcl_SetResult(interp, (char *)"SQLITE_NOMEM", TCL_STATIC); 12959fc7702cSdan return TCL_OK; 12969fc7702cSdan } 12978cf35eb4Sdan memset(p, 0, sizeof(TclQuotaCallback)); 12988cf35eb4Sdan p->interp = interp; 1299ef9659baSdrh Tcl_IncrRefCount(pScript); 13008cf35eb4Sdan p->pScript = pScript; 13019fc7702cSdan xDestroy = tclCallbackDestructor; 130206bfd36aSdan xCallback = tclQuotaCallback; 1303ef9659baSdrh }else{ 1304ef9659baSdrh p = 0; 13059fc7702cSdan xDestroy = 0; 130606bfd36aSdan xCallback = 0; 13078cf35eb4Sdan } 13089fc7702cSdan 1309ef9659baSdrh /* Invoke sqlite3_quota_set() */ 131006bfd36aSdan rc = sqlite3_quota_set(zPattern, iLimit, xCallback, (void*)p, xDestroy); 13118cf35eb4Sdan 13128cf35eb4Sdan Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); 13138cf35eb4Sdan return TCL_OK; 13148cf35eb4Sdan } 13158cf35eb4Sdan 13168cf35eb4Sdan /* 1317b6020c4eSdrh ** tclcmd: sqlite3_quota_file FILENAME 1318b6020c4eSdrh */ 1319b6020c4eSdrh static int test_quota_file( 1320b6020c4eSdrh void * clientData, 1321b6020c4eSdrh Tcl_Interp *interp, 1322b6020c4eSdrh int objc, 1323b6020c4eSdrh Tcl_Obj *CONST objv[] 1324b6020c4eSdrh ){ 1325b6020c4eSdrh const char *zFilename; /* File pattern to configure */ 1326b6020c4eSdrh int rc; /* Value returned by quota_file() */ 1327b6020c4eSdrh 1328b6020c4eSdrh /* Process arguments */ 1329b6020c4eSdrh if( objc!=2 ){ 1330b6020c4eSdrh Tcl_WrongNumArgs(interp, 1, objv, "FILENAME"); 1331b6020c4eSdrh return TCL_ERROR; 1332b6020c4eSdrh } 1333b6020c4eSdrh zFilename = Tcl_GetString(objv[1]); 1334b6020c4eSdrh 1335b6020c4eSdrh /* Invoke sqlite3_quota_file() */ 1336b6020c4eSdrh rc = sqlite3_quota_file(zFilename); 1337b6020c4eSdrh 1338b6020c4eSdrh Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); 1339b6020c4eSdrh return TCL_OK; 1340b6020c4eSdrh } 1341b6020c4eSdrh 1342b6020c4eSdrh /* 1343ef9659baSdrh ** tclcmd: sqlite3_quota_dump 1344ef9659baSdrh */ 1345ef9659baSdrh static int test_quota_dump( 1346ef9659baSdrh void * clientData, 1347ef9659baSdrh Tcl_Interp *interp, 1348ef9659baSdrh int objc, 1349ef9659baSdrh Tcl_Obj *CONST objv[] 1350ef9659baSdrh ){ 1351ef9659baSdrh Tcl_Obj *pResult; 1352ef9659baSdrh Tcl_Obj *pGroupTerm; 1353ef9659baSdrh Tcl_Obj *pFileTerm; 1354ef9659baSdrh quotaGroup *pGroup; 1355ef9659baSdrh quotaFile *pFile; 1356ef9659baSdrh 1357ef9659baSdrh pResult = Tcl_NewObj(); 1358ef9659baSdrh quotaEnter(); 1359ef9659baSdrh for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){ 1360ef9659baSdrh pGroupTerm = Tcl_NewObj(); 1361ef9659baSdrh Tcl_ListObjAppendElement(interp, pGroupTerm, 1362ef9659baSdrh Tcl_NewStringObj(pGroup->zPattern, -1)); 1363ef9659baSdrh Tcl_ListObjAppendElement(interp, pGroupTerm, 1364ef9659baSdrh Tcl_NewWideIntObj(pGroup->iLimit)); 1365ef9659baSdrh Tcl_ListObjAppendElement(interp, pGroupTerm, 1366ef9659baSdrh Tcl_NewWideIntObj(pGroup->iSize)); 136775c19174Sdrh for(pFile=pGroup->pFiles; pFile; pFile=pFile->pNext){ 1368c8ccda60Sdrh int i; 1369c8ccda60Sdrh char zTemp[1000]; 1370ef9659baSdrh pFileTerm = Tcl_NewObj(); 1371c8ccda60Sdrh sqlite3_snprintf(sizeof(zTemp), zTemp, "%s", pFile->zFilename); 1372c8ccda60Sdrh for(i=0; zTemp[i]; i++){ if( zTemp[i]=='\\' ) zTemp[i] = '/'; } 1373ef9659baSdrh Tcl_ListObjAppendElement(interp, pFileTerm, 1374c8ccda60Sdrh Tcl_NewStringObj(zTemp, -1)); 1375ef9659baSdrh Tcl_ListObjAppendElement(interp, pFileTerm, 1376ef9659baSdrh Tcl_NewWideIntObj(pFile->iSize)); 1377ef9659baSdrh Tcl_ListObjAppendElement(interp, pFileTerm, 1378ef9659baSdrh Tcl_NewWideIntObj(pFile->nRef)); 1379796af156Sdrh Tcl_ListObjAppendElement(interp, pFileTerm, 1380796af156Sdrh Tcl_NewWideIntObj(pFile->deleteOnClose)); 1381ef9659baSdrh Tcl_ListObjAppendElement(interp, pGroupTerm, pFileTerm); 1382ef9659baSdrh } 1383ef9659baSdrh Tcl_ListObjAppendElement(interp, pResult, pGroupTerm); 1384ef9659baSdrh } 1385ef9659baSdrh quotaLeave(); 1386ef9659baSdrh Tcl_SetObjResult(interp, pResult); 1387ef9659baSdrh return TCL_OK; 1388ef9659baSdrh } 1389ef9659baSdrh 1390748a52cdSdrh /* 1391a76e8589Sdrh ** tclcmd: sqlite3_quota_fopen FILENAME MODE 1392a76e8589Sdrh */ 1393a76e8589Sdrh static int test_quota_fopen( 1394a76e8589Sdrh void * clientData, 1395a76e8589Sdrh Tcl_Interp *interp, 1396a76e8589Sdrh int objc, 1397a76e8589Sdrh Tcl_Obj *CONST objv[] 1398a76e8589Sdrh ){ 1399a76e8589Sdrh const char *zFilename; /* File pattern to configure */ 1400a76e8589Sdrh const char *zMode; /* Mode string */ 1401a76e8589Sdrh quota_FILE *p; /* Open string object */ 1402a76e8589Sdrh char zReturn[50]; /* Name of pointer to return */ 1403a76e8589Sdrh 1404a76e8589Sdrh /* Process arguments */ 1405a76e8589Sdrh if( objc!=3 ){ 1406a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "FILENAME MODE"); 1407a76e8589Sdrh return TCL_ERROR; 1408a76e8589Sdrh } 1409a76e8589Sdrh zFilename = Tcl_GetString(objv[1]); 1410a76e8589Sdrh zMode = Tcl_GetString(objv[2]); 1411a76e8589Sdrh p = sqlite3_quota_fopen(zFilename, zMode); 1412a76e8589Sdrh sqlite3_snprintf(sizeof(zReturn), zReturn, "%p", p); 1413a76e8589Sdrh Tcl_SetResult(interp, zReturn, TCL_VOLATILE); 1414a76e8589Sdrh return TCL_OK; 1415a76e8589Sdrh } 1416a76e8589Sdrh 1417a76e8589Sdrh /* Defined in test1.c */ 1418a76e8589Sdrh extern void *sqlite3TestTextToPtr(const char*); 1419a76e8589Sdrh 1420a76e8589Sdrh /* 1421a76e8589Sdrh ** tclcmd: sqlite3_quota_fread HANDLE SIZE NELEM 1422a76e8589Sdrh */ 1423a76e8589Sdrh static int test_quota_fread( 1424a76e8589Sdrh void * clientData, 1425a76e8589Sdrh Tcl_Interp *interp, 1426a76e8589Sdrh int objc, 1427a76e8589Sdrh Tcl_Obj *CONST objv[] 1428a76e8589Sdrh ){ 1429a76e8589Sdrh quota_FILE *p; 1430a76e8589Sdrh char *zBuf; 1431a76e8589Sdrh int sz; 1432a76e8589Sdrh int nElem; 1433a76e8589Sdrh int got; 1434a76e8589Sdrh 1435a76e8589Sdrh if( objc!=4 ){ 1436a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM"); 1437a76e8589Sdrh return TCL_ERROR; 1438a76e8589Sdrh } 1439a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1440a76e8589Sdrh if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR; 1441a76e8589Sdrh if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR; 1442*663cebfeSdrh zBuf = (char*)sqlite3_malloc( sz*nElem + 1 ); 1443a76e8589Sdrh if( zBuf==0 ){ 1444a76e8589Sdrh Tcl_SetResult(interp, "out of memory", TCL_STATIC); 1445a76e8589Sdrh return TCL_ERROR; 1446a76e8589Sdrh } 1447a76e8589Sdrh got = sqlite3_quota_fread(zBuf, sz, nElem, p); 1448a76e8589Sdrh if( got<0 ) got = 0; 1449a76e8589Sdrh zBuf[got*sz] = 0; 1450a76e8589Sdrh Tcl_SetResult(interp, zBuf, TCL_VOLATILE); 1451a76e8589Sdrh sqlite3_free(zBuf); 1452a76e8589Sdrh return TCL_OK; 1453a76e8589Sdrh } 1454a76e8589Sdrh 1455a76e8589Sdrh /* 1456a76e8589Sdrh ** tclcmd: sqlite3_quota_fwrite HANDLE SIZE NELEM CONTENT 1457a76e8589Sdrh */ 1458a76e8589Sdrh static int test_quota_fwrite( 1459a76e8589Sdrh void * clientData, 1460a76e8589Sdrh Tcl_Interp *interp, 1461a76e8589Sdrh int objc, 1462a76e8589Sdrh Tcl_Obj *CONST objv[] 1463a76e8589Sdrh ){ 1464a76e8589Sdrh quota_FILE *p; 1465a76e8589Sdrh char *zBuf; 1466a76e8589Sdrh int sz; 1467a76e8589Sdrh int nElem; 1468a76e8589Sdrh int got; 1469a76e8589Sdrh 1470a76e8589Sdrh if( objc!=5 ){ 1471a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM CONTENT"); 1472a76e8589Sdrh return TCL_ERROR; 1473a76e8589Sdrh } 1474a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1475a76e8589Sdrh if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR; 1476a76e8589Sdrh if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR; 1477a76e8589Sdrh zBuf = Tcl_GetString(objv[4]); 1478a76e8589Sdrh got = sqlite3_quota_fwrite(zBuf, sz, nElem, p); 1479a76e8589Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(got)); 1480a76e8589Sdrh return TCL_OK; 1481a76e8589Sdrh } 1482a76e8589Sdrh 1483a76e8589Sdrh /* 1484a76e8589Sdrh ** tclcmd: sqlite3_quota_fclose HANDLE 1485a76e8589Sdrh */ 1486a76e8589Sdrh static int test_quota_fclose( 1487a76e8589Sdrh void * clientData, 1488a76e8589Sdrh Tcl_Interp *interp, 1489a76e8589Sdrh int objc, 1490a76e8589Sdrh Tcl_Obj *CONST objv[] 1491a76e8589Sdrh ){ 1492a76e8589Sdrh quota_FILE *p; 1493a76e8589Sdrh int rc; 1494a76e8589Sdrh 1495a76e8589Sdrh if( objc!=2 ){ 1496a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1497a76e8589Sdrh return TCL_ERROR; 1498a76e8589Sdrh } 1499a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1500a76e8589Sdrh rc = sqlite3_quota_fclose(p); 1501a76e8589Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 1502a76e8589Sdrh return TCL_OK; 1503a76e8589Sdrh } 1504a76e8589Sdrh 1505a76e8589Sdrh /* 150669b2232dSdrh ** tclcmd: sqlite3_quota_fflush HANDLE 150769b2232dSdrh */ 150869b2232dSdrh static int test_quota_fflush( 150969b2232dSdrh void * clientData, 151069b2232dSdrh Tcl_Interp *interp, 151169b2232dSdrh int objc, 151269b2232dSdrh Tcl_Obj *CONST objv[] 151369b2232dSdrh ){ 151469b2232dSdrh quota_FILE *p; 151569b2232dSdrh int rc; 151669b2232dSdrh 151769b2232dSdrh if( objc!=2 ){ 151869b2232dSdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 151969b2232dSdrh return TCL_ERROR; 152069b2232dSdrh } 152169b2232dSdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 152269b2232dSdrh rc = sqlite3_quota_fflush(p); 152369b2232dSdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 152469b2232dSdrh return TCL_OK; 152569b2232dSdrh } 152669b2232dSdrh 152769b2232dSdrh /* 1528a76e8589Sdrh ** tclcmd: sqlite3_quota_fseek HANDLE OFFSET WHENCE 1529a76e8589Sdrh */ 1530a76e8589Sdrh static int test_quota_fseek( 1531a76e8589Sdrh void * clientData, 1532a76e8589Sdrh Tcl_Interp *interp, 1533a76e8589Sdrh int objc, 1534a76e8589Sdrh Tcl_Obj *CONST objv[] 1535a76e8589Sdrh ){ 1536a76e8589Sdrh quota_FILE *p; 1537a76e8589Sdrh int ofst; 1538a76e8589Sdrh const char *zWhence; 1539a76e8589Sdrh int whence; 1540a76e8589Sdrh int rc; 1541a76e8589Sdrh 1542a76e8589Sdrh if( objc!=4 ){ 1543a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE OFFSET WHENCE"); 1544a76e8589Sdrh return TCL_ERROR; 1545a76e8589Sdrh } 1546a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1547a76e8589Sdrh if( Tcl_GetIntFromObj(interp, objv[2], &ofst) ) return TCL_ERROR; 1548a76e8589Sdrh zWhence = Tcl_GetString(objv[3]); 1549a76e8589Sdrh if( strcmp(zWhence, "SEEK_SET")==0 ){ 1550a76e8589Sdrh whence = SEEK_SET; 1551a76e8589Sdrh }else if( strcmp(zWhence, "SEEK_CUR")==0 ){ 1552a76e8589Sdrh whence = SEEK_CUR; 1553a76e8589Sdrh }else if( strcmp(zWhence, "SEEK_END")==0 ){ 1554a76e8589Sdrh whence = SEEK_END; 1555a76e8589Sdrh }else{ 1556a76e8589Sdrh Tcl_AppendResult(interp, 1557a76e8589Sdrh "WHENCE should be SEEK_SET, SEEK_CUR, or SEEK_END", (char*)0); 1558a76e8589Sdrh return TCL_ERROR; 1559a76e8589Sdrh } 1560a76e8589Sdrh rc = sqlite3_quota_fseek(p, ofst, whence); 1561a76e8589Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 1562a76e8589Sdrh return TCL_OK; 1563a76e8589Sdrh } 1564a76e8589Sdrh 1565a76e8589Sdrh /* 1566a76e8589Sdrh ** tclcmd: sqlite3_quota_rewind HANDLE 1567a76e8589Sdrh */ 1568a76e8589Sdrh static int test_quota_rewind( 1569a76e8589Sdrh void * clientData, 1570a76e8589Sdrh Tcl_Interp *interp, 1571a76e8589Sdrh int objc, 1572a76e8589Sdrh Tcl_Obj *CONST objv[] 1573a76e8589Sdrh ){ 1574a76e8589Sdrh quota_FILE *p; 1575a76e8589Sdrh if( objc!=2 ){ 1576a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1577a76e8589Sdrh return TCL_ERROR; 1578a76e8589Sdrh } 1579a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1580a76e8589Sdrh sqlite3_quota_rewind(p); 1581a76e8589Sdrh return TCL_OK; 1582a76e8589Sdrh } 1583a76e8589Sdrh 1584a76e8589Sdrh /* 1585a76e8589Sdrh ** tclcmd: sqlite3_quota_ftell HANDLE 1586a76e8589Sdrh */ 1587a76e8589Sdrh static int test_quota_ftell( 1588a76e8589Sdrh void * clientData, 1589a76e8589Sdrh Tcl_Interp *interp, 1590a76e8589Sdrh int objc, 1591a76e8589Sdrh Tcl_Obj *CONST objv[] 1592a76e8589Sdrh ){ 1593a76e8589Sdrh quota_FILE *p; 1594a76e8589Sdrh sqlite3_int64 x; 1595a76e8589Sdrh if( objc!=2 ){ 1596a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1597a76e8589Sdrh return TCL_ERROR; 1598a76e8589Sdrh } 1599a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1600a76e8589Sdrh x = sqlite3_quota_ftell(p); 1601a76e8589Sdrh Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); 1602a76e8589Sdrh return TCL_OK; 1603a76e8589Sdrh } 1604a76e8589Sdrh 1605a76e8589Sdrh /* 1606a76e8589Sdrh ** tclcmd: sqlite3_quota_remove FILENAME 1607a76e8589Sdrh */ 1608a76e8589Sdrh static int test_quota_remove( 1609a76e8589Sdrh void * clientData, 1610a76e8589Sdrh Tcl_Interp *interp, 1611a76e8589Sdrh int objc, 1612a76e8589Sdrh Tcl_Obj *CONST objv[] 1613a76e8589Sdrh ){ 1614a76e8589Sdrh const char *zFilename; /* File pattern to configure */ 1615a76e8589Sdrh int rc; 1616a76e8589Sdrh if( objc!=2 ){ 1617a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "FILENAME"); 1618a76e8589Sdrh return TCL_ERROR; 1619a76e8589Sdrh } 1620a76e8589Sdrh zFilename = Tcl_GetString(objv[1]); 1621a76e8589Sdrh rc = sqlite3_quota_remove(zFilename); 1622a76e8589Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 1623a76e8589Sdrh return TCL_OK; 1624a76e8589Sdrh } 1625a76e8589Sdrh 1626a76e8589Sdrh /* 1627c8ccda60Sdrh ** tclcmd: sqlite3_quota_glob PATTERN TEXT 1628c8ccda60Sdrh ** 1629c8ccda60Sdrh ** Test the glob pattern matching. Return 1 if TEXT matches PATTERN 1630c8ccda60Sdrh ** and return 0 if it does not. 1631c8ccda60Sdrh */ 1632c8ccda60Sdrh static int test_quota_glob( 1633c8ccda60Sdrh void * clientData, 1634c8ccda60Sdrh Tcl_Interp *interp, 1635c8ccda60Sdrh int objc, 1636c8ccda60Sdrh Tcl_Obj *CONST objv[] 1637c8ccda60Sdrh ){ 1638c8ccda60Sdrh const char *zPattern; /* The glob pattern */ 1639c8ccda60Sdrh const char *zText; /* Text to compare agains the pattern */ 1640c8ccda60Sdrh int rc; 1641c8ccda60Sdrh if( objc!=3 ){ 1642c8ccda60Sdrh Tcl_WrongNumArgs(interp, 1, objv, "PATTERN TEXT"); 1643c8ccda60Sdrh return TCL_ERROR; 1644c8ccda60Sdrh } 1645c8ccda60Sdrh zPattern = Tcl_GetString(objv[1]); 1646c8ccda60Sdrh zText = Tcl_GetString(objv[2]); 1647c8ccda60Sdrh rc = quotaStrglob(zPattern, zText); 1648c8ccda60Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 1649c8ccda60Sdrh return TCL_OK; 1650c8ccda60Sdrh } 1651c8ccda60Sdrh 1652c8ccda60Sdrh /* 1653748a52cdSdrh ** This routine registers the custom TCL commands defined in this 1654748a52cdSdrh ** module. This should be the only procedure visible from outside 1655748a52cdSdrh ** of this module. 1656748a52cdSdrh */ 1657748a52cdSdrh int Sqlitequota_Init(Tcl_Interp *interp){ 16588cf35eb4Sdan static struct { 16598cf35eb4Sdan char *zName; 16608cf35eb4Sdan Tcl_ObjCmdProc *xProc; 16618cf35eb4Sdan } aCmd[] = { 16628cf35eb4Sdan { "sqlite3_quota_initialize", test_quota_initialize }, 16638cf35eb4Sdan { "sqlite3_quota_shutdown", test_quota_shutdown }, 16648cf35eb4Sdan { "sqlite3_quota_set", test_quota_set }, 1665b6020c4eSdrh { "sqlite3_quota_file", test_quota_file }, 1666ef9659baSdrh { "sqlite3_quota_dump", test_quota_dump }, 1667a76e8589Sdrh { "sqlite3_quota_fopen", test_quota_fopen }, 1668a76e8589Sdrh { "sqlite3_quota_fread", test_quota_fread }, 1669a76e8589Sdrh { "sqlite3_quota_fwrite", test_quota_fwrite }, 1670a76e8589Sdrh { "sqlite3_quota_fclose", test_quota_fclose }, 167169b2232dSdrh { "sqlite3_quota_fflush", test_quota_fflush }, 1672a76e8589Sdrh { "sqlite3_quota_fseek", test_quota_fseek }, 1673a76e8589Sdrh { "sqlite3_quota_rewind", test_quota_rewind }, 1674a76e8589Sdrh { "sqlite3_quota_ftell", test_quota_ftell }, 1675a76e8589Sdrh { "sqlite3_quota_remove", test_quota_remove }, 1676c8ccda60Sdrh { "sqlite3_quota_glob", test_quota_glob }, 16778cf35eb4Sdan }; 16788cf35eb4Sdan int i; 16798cf35eb4Sdan 16808cf35eb4Sdan for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){ 16818cf35eb4Sdan Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0); 16828cf35eb4Sdan } 16838cf35eb4Sdan 1684748a52cdSdrh return TCL_OK; 1685748a52cdSdrh } 1686748a52cdSdrh #endif 1687