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 47*f74b9e09Smistachkin #include "os_setup.h" 4894e858b1Sdrh 4994e858b1Sdrh #if SQLITE_OS_UNIX 5094e858b1Sdrh # include <unistd.h> 5194e858b1Sdrh #endif 5294e858b1Sdrh #if SQLITE_OS_WIN 53*f74b9e09Smistachkin # include "os_win.h" 5494e858b1Sdrh # include <io.h> 5594e858b1Sdrh #endif 5694e858b1Sdrh 5794e858b1Sdrh 58748a52cdSdrh /************************ Object Definitions ******************************/ 59748a52cdSdrh 60745de2e4Sdrh /* Forward declaration of all object types */ 61748a52cdSdrh typedef struct quotaGroup quotaGroup; 62ef9659baSdrh typedef struct quotaConn quotaConn; 63745de2e4Sdrh typedef struct quotaFile quotaFile; 64745de2e4Sdrh 658cf35eb4Sdan /* 66549dc1baSdrh ** A "quota group" is a collection of files whose collective size we want 67549dc1baSdrh ** to limit. Each quota group is defined by a GLOB pattern. 688cf35eb4Sdan ** 69549dc1baSdrh ** There is an instance of the following object for each defined quota 70549dc1baSdrh ** group. This object records the GLOB pattern that defines which files 71549dc1baSdrh ** belong to the quota group. The object also remembers the size limit 72549dc1baSdrh ** for the group (the quota) and the callback to be invoked when the 73549dc1baSdrh ** sum of the sizes of the files within the group goes over the limit. 748cf35eb4Sdan ** 75549dc1baSdrh ** A quota group must be established (using sqlite3_quota_set(...)) 76549dc1baSdrh ** prior to opening any of the database connections that access files 77549dc1baSdrh ** within the quota group. 788cf35eb4Sdan */ 79748a52cdSdrh struct quotaGroup { 80748a52cdSdrh const char *zPattern; /* Filename pattern to be quotaed */ 81748a52cdSdrh sqlite3_int64 iLimit; /* Upper bound on total file size */ 82748a52cdSdrh sqlite3_int64 iSize; /* Current size of all files */ 83748a52cdSdrh void (*xCallback)( /* Callback invoked when going over quota */ 84748a52cdSdrh const char *zFilename, /* Name of file whose size increases */ 85748a52cdSdrh sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ 86748a52cdSdrh sqlite3_int64 iSize, /* Total size of all files in the group */ 87748a52cdSdrh void *pArg /* Client data */ 88748a52cdSdrh ); 89748a52cdSdrh void *pArg; /* Third argument to the xCallback() */ 90ef9659baSdrh void (*xDestroy)(void*); /* Optional destructor for pArg */ 91748a52cdSdrh quotaGroup *pNext, **ppPrev; /* Doubly linked list of all quota objects */ 9275c19174Sdrh quotaFile *pFiles; /* Files within this group */ 93748a52cdSdrh }; 94748a52cdSdrh 95748a52cdSdrh /* 96745de2e4Sdrh ** An instance of this structure represents a single file that is part 97745de2e4Sdrh ** of a quota group. A single file can be opened multiple times. In 98745de2e4Sdrh ** order keep multiple openings of the same file from causing the size 99549dc1baSdrh ** of the file to count against the quota multiple times, each file 100745de2e4Sdrh ** has a unique instance of this object and multiple open connections 101745de2e4Sdrh ** to the same file each point to a single instance of this object. 102748a52cdSdrh */ 103748a52cdSdrh struct quotaFile { 104745de2e4Sdrh char *zFilename; /* Name of this file */ 10575c19174Sdrh quotaGroup *pGroup; /* Quota group to which this file belongs */ 1068cf35eb4Sdan sqlite3_int64 iSize; /* Current size of this file */ 107745de2e4Sdrh int nRef; /* Number of times this file is open */ 108796af156Sdrh int deleteOnClose; /* True to delete this file when it closes */ 109745de2e4Sdrh quotaFile *pNext, **ppPrev; /* Linked list of files in the same group */ 110745de2e4Sdrh }; 111745de2e4Sdrh 112745de2e4Sdrh /* 113745de2e4Sdrh ** An instance of the following object represents each open connection 114549dc1baSdrh ** to a file that participates in quota tracking. This object is a 115549dc1baSdrh ** subclass of sqlite3_file. The sqlite3_file object for the underlying 116549dc1baSdrh ** VFS is appended to this structure. 117745de2e4Sdrh */ 118ef9659baSdrh struct quotaConn { 119748a52cdSdrh sqlite3_file base; /* Base class - must be first */ 120745de2e4Sdrh quotaFile *pFile; /* The underlying file */ 121748a52cdSdrh /* The underlying VFS sqlite3_file is appended to this object */ 122748a52cdSdrh }; 123748a52cdSdrh 12460f21e4bSdrh /* 12560f21e4bSdrh ** An instance of the following object records the state of an 12660f21e4bSdrh ** open file. This object is opaque to all users - the internal 12760f21e4bSdrh ** structure is only visible to the functions below. 12860f21e4bSdrh */ 12960f21e4bSdrh struct quota_FILE { 13060f21e4bSdrh FILE *f; /* Open stdio file pointer */ 13160f21e4bSdrh sqlite3_int64 iOfst; /* Current offset into the file */ 13260f21e4bSdrh quotaFile *pFile; /* The file record in the quota system */ 133c00ce490Sdrh #if SQLITE_OS_WIN 134c00ce490Sdrh char *zMbcsName; /* Full MBCS pathname of the file */ 135c00ce490Sdrh #endif 13660f21e4bSdrh }; 13760f21e4bSdrh 13860f21e4bSdrh 139748a52cdSdrh /************************* Global Variables **********************************/ 140748a52cdSdrh /* 141748a52cdSdrh ** All global variables used by this file are containing within the following 142748a52cdSdrh ** gQuota structure. 143748a52cdSdrh */ 144748a52cdSdrh static struct { 145549dc1baSdrh /* The pOrigVfs is the real, original underlying VFS implementation. 146748a52cdSdrh ** Most operations pass-through to the real VFS. This value is read-only 147748a52cdSdrh ** during operation. It is only modified at start-time and thus does not 148748a52cdSdrh ** require a mutex. 149748a52cdSdrh */ 150748a52cdSdrh sqlite3_vfs *pOrigVfs; 151748a52cdSdrh 152748a52cdSdrh /* The sThisVfs is the VFS structure used by this shim. It is initialized 153748a52cdSdrh ** at start-time and thus does not require a mutex 154748a52cdSdrh */ 155748a52cdSdrh sqlite3_vfs sThisVfs; 156748a52cdSdrh 157748a52cdSdrh /* The sIoMethods defines the methods used by sqlite3_file objects 158748a52cdSdrh ** associated with this shim. It is initialized at start-time and does 159748a52cdSdrh ** not require a mutex. 160748a52cdSdrh ** 161748a52cdSdrh ** When the underlying VFS is called to open a file, it might return 162748a52cdSdrh ** either a version 1 or a version 2 sqlite3_file object. This shim 163748a52cdSdrh ** has to create a wrapper sqlite3_file of the same version. Hence 164748a52cdSdrh ** there are two I/O method structures, one for version 1 and the other 165748a52cdSdrh ** for version 2. 166748a52cdSdrh */ 167748a52cdSdrh sqlite3_io_methods sIoMethodsV1; 168748a52cdSdrh sqlite3_io_methods sIoMethodsV2; 169748a52cdSdrh 170748a52cdSdrh /* True when this shim as been initialized. 171748a52cdSdrh */ 172748a52cdSdrh int isInitialized; 173748a52cdSdrh 174748a52cdSdrh /* For run-time access any of the other global data structures in this 175748a52cdSdrh ** shim, the following mutex must be held. 176748a52cdSdrh */ 177748a52cdSdrh sqlite3_mutex *pMutex; 178748a52cdSdrh 179748a52cdSdrh /* List of quotaGroup objects. 180748a52cdSdrh */ 181748a52cdSdrh quotaGroup *pGroup; 182748a52cdSdrh 183748a52cdSdrh } gQuota; 184748a52cdSdrh 185748a52cdSdrh /************************* Utility Routines *********************************/ 186748a52cdSdrh /* 187748a52cdSdrh ** Acquire and release the mutex used to serialize access to the 188748a52cdSdrh ** list of quotaGroups. 189748a52cdSdrh */ 190748a52cdSdrh static void quotaEnter(void){ sqlite3_mutex_enter(gQuota.pMutex); } 191748a52cdSdrh static void quotaLeave(void){ sqlite3_mutex_leave(gQuota.pMutex); } 192748a52cdSdrh 193796af156Sdrh /* Count the number of open files in a quotaGroup 194796af156Sdrh */ 195796af156Sdrh static int quotaGroupOpenFileCount(quotaGroup *pGroup){ 196796af156Sdrh int N = 0; 197796af156Sdrh quotaFile *pFile = pGroup->pFiles; 198796af156Sdrh while( pFile ){ 199796af156Sdrh if( pFile->nRef ) N++; 200796af156Sdrh pFile = pFile->pNext; 201796af156Sdrh } 202796af156Sdrh return N; 203796af156Sdrh } 204796af156Sdrh 205796af156Sdrh /* Remove a file from a quota group. 206796af156Sdrh */ 207796af156Sdrh static void quotaRemoveFile(quotaFile *pFile){ 208796af156Sdrh quotaGroup *pGroup = pFile->pGroup; 209796af156Sdrh pGroup->iSize -= pFile->iSize; 210796af156Sdrh *pFile->ppPrev = pFile->pNext; 211796af156Sdrh if( pFile->pNext ) pFile->pNext->ppPrev = pFile->ppPrev; 212796af156Sdrh sqlite3_free(pFile); 213796af156Sdrh } 214796af156Sdrh 215796af156Sdrh /* Remove all files from a quota group. It is always the case that 216796af156Sdrh ** all files will be closed when this routine is called. 217796af156Sdrh */ 218796af156Sdrh static void quotaRemoveAllFiles(quotaGroup *pGroup){ 219796af156Sdrh while( pGroup->pFiles ){ 220796af156Sdrh assert( pGroup->pFiles->nRef==0 ); 221796af156Sdrh quotaRemoveFile(pGroup->pFiles); 222796af156Sdrh } 223796af156Sdrh } 224796af156Sdrh 225748a52cdSdrh 226748a52cdSdrh /* If the reference count and threshold for a quotaGroup are both 227748a52cdSdrh ** zero, then destroy the quotaGroup. 228748a52cdSdrh */ 22975c19174Sdrh static void quotaGroupDeref(quotaGroup *pGroup){ 230796af156Sdrh if( pGroup->iLimit==0 && quotaGroupOpenFileCount(pGroup)==0 ){ 231796af156Sdrh quotaRemoveAllFiles(pGroup); 2329fc7702cSdan *pGroup->ppPrev = pGroup->pNext; 23375c19174Sdrh if( pGroup->pNext ) pGroup->pNext->ppPrev = pGroup->ppPrev; 23475c19174Sdrh if( pGroup->xDestroy ) pGroup->xDestroy(pGroup->pArg); 23575c19174Sdrh sqlite3_free(pGroup); 236748a52cdSdrh } 237748a52cdSdrh } 238748a52cdSdrh 239748a52cdSdrh /* 240748a52cdSdrh ** Return TRUE if string z matches glob pattern zGlob. 241748a52cdSdrh ** 242748a52cdSdrh ** Globbing rules: 243748a52cdSdrh ** 244748a52cdSdrh ** '*' Matches any sequence of zero or more characters. 245748a52cdSdrh ** 246748a52cdSdrh ** '?' Matches exactly one character. 247748a52cdSdrh ** 248748a52cdSdrh ** [...] Matches one character from the enclosed list of 249748a52cdSdrh ** characters. 250748a52cdSdrh ** 251748a52cdSdrh ** [^...] Matches one character not in the enclosed list. 252748a52cdSdrh ** 253a76e8589Sdrh ** / Matches "/" or "\\" 254a76e8589Sdrh ** 255748a52cdSdrh */ 25661cc423aSdrh static int quotaStrglob(const char *zGlob, const char *z){ 257c8ccda60Sdrh int c, c2, cx; 258748a52cdSdrh int invert; 259748a52cdSdrh int seen; 260748a52cdSdrh 261748a52cdSdrh while( (c = (*(zGlob++)))!=0 ){ 262748a52cdSdrh if( c=='*' ){ 263748a52cdSdrh while( (c=(*(zGlob++))) == '*' || c=='?' ){ 264748a52cdSdrh if( c=='?' && (*(z++))==0 ) return 0; 265748a52cdSdrh } 266748a52cdSdrh if( c==0 ){ 267748a52cdSdrh return 1; 268748a52cdSdrh }else if( c=='[' ){ 26961cc423aSdrh while( *z && quotaStrglob(zGlob-1,z)==0 ){ 270748a52cdSdrh z++; 271748a52cdSdrh } 272748a52cdSdrh return (*z)!=0; 273748a52cdSdrh } 274c8ccda60Sdrh cx = (c=='/') ? '\\' : c; 275748a52cdSdrh while( (c2 = (*(z++)))!=0 ){ 276c8ccda60Sdrh while( c2!=c && c2!=cx ){ 277748a52cdSdrh c2 = *(z++); 278748a52cdSdrh if( c2==0 ) return 0; 279748a52cdSdrh } 28061cc423aSdrh if( quotaStrglob(zGlob,z) ) return 1; 281748a52cdSdrh } 282748a52cdSdrh return 0; 283748a52cdSdrh }else if( c=='?' ){ 284748a52cdSdrh if( (*(z++))==0 ) return 0; 285748a52cdSdrh }else if( c=='[' ){ 286748a52cdSdrh int prior_c = 0; 287748a52cdSdrh seen = 0; 288748a52cdSdrh invert = 0; 289748a52cdSdrh c = *(z++); 290748a52cdSdrh if( c==0 ) return 0; 291748a52cdSdrh c2 = *(zGlob++); 292748a52cdSdrh if( c2=='^' ){ 293748a52cdSdrh invert = 1; 294748a52cdSdrh c2 = *(zGlob++); 295748a52cdSdrh } 296748a52cdSdrh if( c2==']' ){ 297748a52cdSdrh if( c==']' ) seen = 1; 298748a52cdSdrh c2 = *(zGlob++); 299748a52cdSdrh } 300748a52cdSdrh while( c2 && c2!=']' ){ 301748a52cdSdrh if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){ 302748a52cdSdrh c2 = *(zGlob++); 303748a52cdSdrh if( c>=prior_c && c<=c2 ) seen = 1; 304748a52cdSdrh prior_c = 0; 305748a52cdSdrh }else{ 306748a52cdSdrh if( c==c2 ){ 307748a52cdSdrh seen = 1; 308748a52cdSdrh } 309748a52cdSdrh prior_c = c2; 310748a52cdSdrh } 311748a52cdSdrh c2 = *(zGlob++); 312748a52cdSdrh } 313748a52cdSdrh if( c2==0 || (seen ^ invert)==0 ) return 0; 314a76e8589Sdrh }else if( c=='/' ){ 315a76e8589Sdrh if( z[0]!='/' && z[0]!='\\' ) return 0; 316a76e8589Sdrh z++; 317748a52cdSdrh }else{ 318748a52cdSdrh if( c!=(*(z++)) ) return 0; 319748a52cdSdrh } 320748a52cdSdrh } 321748a52cdSdrh return *z==0; 322748a52cdSdrh } 323748a52cdSdrh 324748a52cdSdrh 325748a52cdSdrh /* Find a quotaGroup given the filename. 3268cf35eb4Sdan ** 327ef9659baSdrh ** Return a pointer to the quotaGroup object. Return NULL if not found. 328748a52cdSdrh */ 329748a52cdSdrh static quotaGroup *quotaGroupFind(const char *zFilename){ 330748a52cdSdrh quotaGroup *p; 33161cc423aSdrh for(p=gQuota.pGroup; p && quotaStrglob(p->zPattern, zFilename)==0; 33261cc423aSdrh p=p->pNext){} 333748a52cdSdrh return p; 334748a52cdSdrh } 335748a52cdSdrh 336ef9659baSdrh /* Translate an sqlite3_file* that is really a quotaConn* into 337549dc1baSdrh ** the sqlite3_file* for the underlying original VFS. 338748a52cdSdrh */ 339ef9659baSdrh static sqlite3_file *quotaSubOpen(sqlite3_file *pConn){ 340ef9659baSdrh quotaConn *p = (quotaConn*)pConn; 341748a52cdSdrh return (sqlite3_file*)&p[1]; 342748a52cdSdrh } 343748a52cdSdrh 344796af156Sdrh /* Find a file in a quota group and return a pointer to that file. 345796af156Sdrh ** Return NULL if the file is not in the group. 346796af156Sdrh */ 34760f21e4bSdrh static quotaFile *quotaFindFile( 34860f21e4bSdrh quotaGroup *pGroup, /* Group in which to look for the file */ 34960f21e4bSdrh const char *zName, /* Full pathname of the file */ 35060f21e4bSdrh int createFlag /* Try to create the file if not found */ 35160f21e4bSdrh ){ 352796af156Sdrh quotaFile *pFile = pGroup->pFiles; 353796af156Sdrh while( pFile && strcmp(pFile->zFilename, zName)!=0 ){ 354796af156Sdrh pFile = pFile->pNext; 355796af156Sdrh } 35660f21e4bSdrh if( pFile==0 && createFlag ){ 35783cc1392Sdrh int nName = (int)(strlen(zName) & 0x3fffffff); 35860f21e4bSdrh pFile = (quotaFile *)sqlite3_malloc( sizeof(*pFile) + nName + 1 ); 35960f21e4bSdrh if( pFile ){ 36060f21e4bSdrh memset(pFile, 0, sizeof(*pFile)); 36160f21e4bSdrh pFile->zFilename = (char*)&pFile[1]; 36260f21e4bSdrh memcpy(pFile->zFilename, zName, nName+1); 36360f21e4bSdrh pFile->pNext = pGroup->pFiles; 36460f21e4bSdrh if( pGroup->pFiles ) pGroup->pFiles->ppPrev = &pFile->pNext; 36560f21e4bSdrh pFile->ppPrev = &pGroup->pFiles; 36660f21e4bSdrh pGroup->pFiles = pFile; 36760f21e4bSdrh pFile->pGroup = pGroup; 36860f21e4bSdrh } 36960f21e4bSdrh } 370796af156Sdrh return pFile; 371796af156Sdrh } 372a76e8589Sdrh /* 373a76e8589Sdrh ** Translate UTF8 to MBCS for use in fopen() calls. Return a pointer to the 374a76e8589Sdrh ** translated text.. Call quota_mbcs_free() to deallocate any memory 375a76e8589Sdrh ** used to store the returned pointer when done. 376a76e8589Sdrh */ 377a76e8589Sdrh static char *quota_utf8_to_mbcs(const char *zUtf8){ 378a76e8589Sdrh #if SQLITE_OS_WIN 37983cc1392Sdrh size_t n; /* Bytes in zUtf8 */ 38027cec37dSdrh int nWide; /* number of UTF-16 characters */ 38127cec37dSdrh int nMbcs; /* Bytes of MBCS */ 38227cec37dSdrh LPWSTR zTmpWide; /* The UTF16 text */ 38327cec37dSdrh char *zMbcs; /* The MBCS text */ 38427cec37dSdrh int codepage; /* Code page used by fopen() */ 38527cec37dSdrh 38627cec37dSdrh n = strlen(zUtf8); 38727cec37dSdrh nWide = MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, NULL, 0); 38849ed5c85Sdrh if( nWide==0 ) return 0; 389c39f3e00Sdrh zTmpWide = (LPWSTR)sqlite3_malloc( (nWide+1)*sizeof(zTmpWide[0]) ); 39027cec37dSdrh if( zTmpWide==0 ) return 0; 39127cec37dSdrh MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zTmpWide, nWide); 39227cec37dSdrh codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP; 39327cec37dSdrh nMbcs = WideCharToMultiByte(codepage, 0, zTmpWide, nWide, 0, 0, 0, 0); 394c39f3e00Sdrh zMbcs = nMbcs ? (char*)sqlite3_malloc( nMbcs+1 ) : 0; 39527cec37dSdrh if( zMbcs ){ 39627cec37dSdrh WideCharToMultiByte(codepage, 0, zTmpWide, nWide, zMbcs, nMbcs, 0, 0); 39727cec37dSdrh } 39827cec37dSdrh sqlite3_free(zTmpWide); 39927cec37dSdrh return zMbcs; 400a76e8589Sdrh #else 401a76e8589Sdrh return (char*)zUtf8; /* No-op on unix */ 402a76e8589Sdrh #endif 403a76e8589Sdrh } 404a76e8589Sdrh 405a76e8589Sdrh /* 406a76e8589Sdrh ** Deallocate any memory allocated by quota_utf8_to_mbcs(). 407a76e8589Sdrh */ 408a76e8589Sdrh static void quota_mbcs_free(char *zOld){ 409a76e8589Sdrh #if SQLITE_OS_WIN 410c8ccda60Sdrh sqlite3_free(zOld); 411a76e8589Sdrh #else 412a76e8589Sdrh /* No-op on unix */ 413a76e8589Sdrh #endif 414a76e8589Sdrh } 415a76e8589Sdrh 416748a52cdSdrh /************************* VFS Method Wrappers *****************************/ 417748a52cdSdrh /* 418748a52cdSdrh ** This is the xOpen method used for the "quota" VFS. 419748a52cdSdrh ** 420748a52cdSdrh ** Most of the work is done by the underlying original VFS. This method 421549dc1baSdrh ** simply links the new file into the appropriate quota group if it is a 422549dc1baSdrh ** file that needs to be tracked. 423748a52cdSdrh */ 424748a52cdSdrh static int quotaOpen( 425549dc1baSdrh sqlite3_vfs *pVfs, /* The quota VFS */ 426549dc1baSdrh const char *zName, /* Name of file to be opened */ 427ef9659baSdrh sqlite3_file *pConn, /* Fill in this file descriptor */ 428549dc1baSdrh int flags, /* Flags to control the opening */ 429549dc1baSdrh int *pOutFlags /* Flags showing results of opening */ 430748a52cdSdrh ){ 431549dc1baSdrh int rc; /* Result code */ 432ef9659baSdrh quotaConn *pQuotaOpen; /* The new quota file descriptor */ 433549dc1baSdrh quotaFile *pFile; /* Corresponding quotaFile obj */ 434549dc1baSdrh quotaGroup *pGroup; /* The group file belongs to */ 435549dc1baSdrh sqlite3_file *pSubOpen; /* Real file descriptor */ 436549dc1baSdrh sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs; /* Real VFS */ 437748a52cdSdrh 438748a52cdSdrh /* If the file is not a main database file or a WAL, then use the 439748a52cdSdrh ** normal xOpen method. 440748a52cdSdrh */ 4418cf35eb4Sdan if( (flags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_WAL))==0 ){ 442ef9659baSdrh return pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags); 443748a52cdSdrh } 444748a52cdSdrh 445748a52cdSdrh /* If the name of the file does not match any quota group, then 446748a52cdSdrh ** use the normal xOpen method. 447748a52cdSdrh */ 448748a52cdSdrh quotaEnter(); 4498cf35eb4Sdan pGroup = quotaGroupFind(zName); 450748a52cdSdrh if( pGroup==0 ){ 451ef9659baSdrh rc = pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags); 452748a52cdSdrh }else{ 4538cf35eb4Sdan /* If we get to this point, it means the file needs to be quota tracked. 4548cf35eb4Sdan */ 455ef9659baSdrh pQuotaOpen = (quotaConn*)pConn; 456ef9659baSdrh pSubOpen = quotaSubOpen(pConn); 457745de2e4Sdrh rc = pOrigVfs->xOpen(pOrigVfs, zName, pSubOpen, flags, pOutFlags); 4588cf35eb4Sdan if( rc==SQLITE_OK ){ 45960f21e4bSdrh pFile = quotaFindFile(pGroup, zName, 1); 460745de2e4Sdrh if( pFile==0 ){ 461745de2e4Sdrh quotaLeave(); 462745de2e4Sdrh pSubOpen->pMethods->xClose(pSubOpen); 463745de2e4Sdrh return SQLITE_NOMEM; 464745de2e4Sdrh } 465796af156Sdrh pFile->deleteOnClose = (flags & SQLITE_OPEN_DELETEONCLOSE)!=0; 466745de2e4Sdrh pFile->nRef++; 467745de2e4Sdrh pQuotaOpen->pFile = pFile; 468745de2e4Sdrh if( pSubOpen->pMethods->iVersion==1 ){ 469745de2e4Sdrh pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV1; 4708cf35eb4Sdan }else{ 471745de2e4Sdrh pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV2; 472748a52cdSdrh } 473748a52cdSdrh } 4748cf35eb4Sdan } 4758cf35eb4Sdan quotaLeave(); 476748a52cdSdrh return rc; 477748a52cdSdrh } 478748a52cdSdrh 479796af156Sdrh /* 480796af156Sdrh ** This is the xDelete method used for the "quota" VFS. 481796af156Sdrh ** 482796af156Sdrh ** If the file being deleted is part of the quota group, then reduce 483796af156Sdrh ** the size of the quota group accordingly. And remove the file from 484796af156Sdrh ** the set of files in the quota group. 485796af156Sdrh */ 486796af156Sdrh static int quotaDelete( 487796af156Sdrh sqlite3_vfs *pVfs, /* The quota VFS */ 488796af156Sdrh const char *zName, /* Name of file to be deleted */ 489796af156Sdrh int syncDir /* Do a directory sync after deleting */ 490796af156Sdrh ){ 491796af156Sdrh int rc; /* Result code */ 492796af156Sdrh quotaFile *pFile; /* Files in the quota */ 493796af156Sdrh quotaGroup *pGroup; /* The group file belongs to */ 494796af156Sdrh sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs; /* Real VFS */ 495796af156Sdrh 496796af156Sdrh /* Do the actual file delete */ 497796af156Sdrh rc = pOrigVfs->xDelete(pOrigVfs, zName, syncDir); 498796af156Sdrh 499796af156Sdrh /* If the file just deleted is a member of a quota group, then remove 500796af156Sdrh ** it from that quota group. 501796af156Sdrh */ 502796af156Sdrh if( rc==SQLITE_OK ){ 503796af156Sdrh quotaEnter(); 504796af156Sdrh pGroup = quotaGroupFind(zName); 505796af156Sdrh if( pGroup ){ 50660f21e4bSdrh pFile = quotaFindFile(pGroup, zName, 0); 507796af156Sdrh if( pFile ){ 508796af156Sdrh if( pFile->nRef ){ 509796af156Sdrh pFile->deleteOnClose = 1; 510796af156Sdrh }else{ 511796af156Sdrh quotaRemoveFile(pFile); 512796af156Sdrh quotaGroupDeref(pGroup); 513796af156Sdrh } 514796af156Sdrh } 515796af156Sdrh } 516796af156Sdrh quotaLeave(); 517796af156Sdrh } 518796af156Sdrh return rc; 519796af156Sdrh } 520796af156Sdrh 521796af156Sdrh 522748a52cdSdrh /************************ I/O Method Wrappers *******************************/ 523748a52cdSdrh 524748a52cdSdrh /* xClose requests get passed through to the original VFS. But we 525ef9659baSdrh ** also have to unlink the quotaConn from the quotaFile and quotaGroup. 526ef9659baSdrh ** The quotaFile and/or quotaGroup are freed if they are no longer in use. 527748a52cdSdrh */ 528ef9659baSdrh static int quotaClose(sqlite3_file *pConn){ 529ef9659baSdrh quotaConn *p = (quotaConn*)pConn; 530745de2e4Sdrh quotaFile *pFile = p->pFile; 531ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 532748a52cdSdrh int rc; 533745de2e4Sdrh rc = pSubOpen->pMethods->xClose(pSubOpen); 534748a52cdSdrh quotaEnter(); 535745de2e4Sdrh pFile->nRef--; 536745de2e4Sdrh if( pFile->nRef==0 ){ 537745de2e4Sdrh quotaGroup *pGroup = pFile->pGroup; 53869b2232dSdrh if( pFile->deleteOnClose ){ 53969b2232dSdrh gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0); 54069b2232dSdrh quotaRemoveFile(pFile); 54169b2232dSdrh } 542748a52cdSdrh quotaGroupDeref(pGroup); 543745de2e4Sdrh } 544748a52cdSdrh quotaLeave(); 545748a52cdSdrh return rc; 546748a52cdSdrh } 547748a52cdSdrh 548748a52cdSdrh /* Pass xRead requests directory thru to the original VFS without 549748a52cdSdrh ** further processing. 550748a52cdSdrh */ 551748a52cdSdrh static int quotaRead( 552ef9659baSdrh sqlite3_file *pConn, 553748a52cdSdrh void *pBuf, 554748a52cdSdrh int iAmt, 555748a52cdSdrh sqlite3_int64 iOfst 556748a52cdSdrh ){ 557ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 558745de2e4Sdrh return pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst); 559748a52cdSdrh } 560748a52cdSdrh 561748a52cdSdrh /* Check xWrite requests to see if they expand the file. If they do, 562748a52cdSdrh ** the perform a quota check before passing them through to the 563748a52cdSdrh ** original VFS. 564748a52cdSdrh */ 565748a52cdSdrh static int quotaWrite( 566ef9659baSdrh sqlite3_file *pConn, 5678cf35eb4Sdan const void *pBuf, 568748a52cdSdrh int iAmt, 569748a52cdSdrh sqlite3_int64 iOfst 570748a52cdSdrh ){ 571ef9659baSdrh quotaConn *p = (quotaConn*)pConn; 572ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 573748a52cdSdrh sqlite3_int64 iEnd = iOfst+iAmt; 574748a52cdSdrh quotaGroup *pGroup; 575745de2e4Sdrh quotaFile *pFile = p->pFile; 576748a52cdSdrh sqlite3_int64 szNew; 577748a52cdSdrh 578745de2e4Sdrh if( pFile->iSize<iEnd ){ 579745de2e4Sdrh pGroup = pFile->pGroup; 580748a52cdSdrh quotaEnter(); 581745de2e4Sdrh szNew = pGroup->iSize - pFile->iSize + iEnd; 5828cf35eb4Sdan if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ 583748a52cdSdrh if( pGroup->xCallback ){ 584745de2e4Sdrh pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew, 585748a52cdSdrh pGroup->pArg); 586748a52cdSdrh } 5878cf35eb4Sdan if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ 588748a52cdSdrh quotaLeave(); 589748a52cdSdrh return SQLITE_FULL; 590748a52cdSdrh } 591748a52cdSdrh } 592748a52cdSdrh pGroup->iSize = szNew; 593745de2e4Sdrh pFile->iSize = iEnd; 594748a52cdSdrh quotaLeave(); 595748a52cdSdrh } 596745de2e4Sdrh return pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst); 597748a52cdSdrh } 598748a52cdSdrh 599748a52cdSdrh /* Pass xTruncate requests thru to the original VFS. If the 600748a52cdSdrh ** success, update the file size. 601748a52cdSdrh */ 602ef9659baSdrh static int quotaTruncate(sqlite3_file *pConn, sqlite3_int64 size){ 603ef9659baSdrh quotaConn *p = (quotaConn*)pConn; 604ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 605745de2e4Sdrh int rc = pSubOpen->pMethods->xTruncate(pSubOpen, size); 606745de2e4Sdrh quotaFile *pFile = p->pFile; 607745de2e4Sdrh quotaGroup *pGroup; 608748a52cdSdrh if( rc==SQLITE_OK ){ 609748a52cdSdrh quotaEnter(); 610745de2e4Sdrh pGroup = pFile->pGroup; 611745de2e4Sdrh pGroup->iSize -= pFile->iSize; 612745de2e4Sdrh pFile->iSize = size; 613748a52cdSdrh pGroup->iSize += size; 614748a52cdSdrh quotaLeave(); 615748a52cdSdrh } 616748a52cdSdrh return rc; 617748a52cdSdrh } 618748a52cdSdrh 619748a52cdSdrh /* Pass xSync requests through to the original VFS without change 620748a52cdSdrh */ 621ef9659baSdrh static int quotaSync(sqlite3_file *pConn, int flags){ 622ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 623745de2e4Sdrh return pSubOpen->pMethods->xSync(pSubOpen, flags); 624748a52cdSdrh } 625748a52cdSdrh 626748a52cdSdrh /* Pass xFileSize requests through to the original VFS but then 627748a52cdSdrh ** update the quotaGroup with the new size before returning. 628748a52cdSdrh */ 629ef9659baSdrh static int quotaFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){ 630ef9659baSdrh quotaConn *p = (quotaConn*)pConn; 631ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 632745de2e4Sdrh quotaFile *pFile = p->pFile; 633748a52cdSdrh quotaGroup *pGroup; 634748a52cdSdrh sqlite3_int64 sz; 635748a52cdSdrh int rc; 636748a52cdSdrh 637745de2e4Sdrh rc = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); 638748a52cdSdrh if( rc==SQLITE_OK ){ 639748a52cdSdrh quotaEnter(); 640745de2e4Sdrh pGroup = pFile->pGroup; 641745de2e4Sdrh pGroup->iSize -= pFile->iSize; 642745de2e4Sdrh pFile->iSize = sz; 643748a52cdSdrh pGroup->iSize += sz; 644748a52cdSdrh quotaLeave(); 645748a52cdSdrh *pSize = sz; 646748a52cdSdrh } 647748a52cdSdrh return rc; 648748a52cdSdrh } 649748a52cdSdrh 650748a52cdSdrh /* Pass xLock requests through to the original VFS unchanged. 651748a52cdSdrh */ 652ef9659baSdrh static int quotaLock(sqlite3_file *pConn, int lock){ 653ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 654745de2e4Sdrh return pSubOpen->pMethods->xLock(pSubOpen, lock); 655748a52cdSdrh } 656748a52cdSdrh 657748a52cdSdrh /* Pass xUnlock requests through to the original VFS unchanged. 658748a52cdSdrh */ 659ef9659baSdrh static int quotaUnlock(sqlite3_file *pConn, int lock){ 660ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 661745de2e4Sdrh return pSubOpen->pMethods->xUnlock(pSubOpen, lock); 662748a52cdSdrh } 663748a52cdSdrh 664748a52cdSdrh /* Pass xCheckReservedLock requests through to the original VFS unchanged. 665748a52cdSdrh */ 666ef9659baSdrh static int quotaCheckReservedLock(sqlite3_file *pConn, int *pResOut){ 667ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 668745de2e4Sdrh return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut); 669748a52cdSdrh } 670748a52cdSdrh 671748a52cdSdrh /* Pass xFileControl requests through to the original VFS unchanged. 672748a52cdSdrh */ 673ef9659baSdrh static int quotaFileControl(sqlite3_file *pConn, int op, void *pArg){ 674ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 675c39f3e00Sdrh int rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); 676c39f3e00Sdrh #if defined(SQLITE_FCNTL_VFSNAME) 677c39f3e00Sdrh if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){ 678c39f3e00Sdrh *(char**)pArg = sqlite3_mprintf("quota/%z", *(char**)pArg); 679c39f3e00Sdrh } 680c39f3e00Sdrh #endif 681c39f3e00Sdrh return rc; 682748a52cdSdrh } 683748a52cdSdrh 684748a52cdSdrh /* Pass xSectorSize requests through to the original VFS unchanged. 685748a52cdSdrh */ 686ef9659baSdrh static int quotaSectorSize(sqlite3_file *pConn){ 687ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 688745de2e4Sdrh return pSubOpen->pMethods->xSectorSize(pSubOpen); 689748a52cdSdrh } 690748a52cdSdrh 691748a52cdSdrh /* Pass xDeviceCharacteristics requests through to the original VFS unchanged. 692748a52cdSdrh */ 693ef9659baSdrh static int quotaDeviceCharacteristics(sqlite3_file *pConn){ 694ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 695745de2e4Sdrh return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen); 696748a52cdSdrh } 697748a52cdSdrh 698748a52cdSdrh /* Pass xShmMap requests through to the original VFS unchanged. 699748a52cdSdrh */ 700748a52cdSdrh static int quotaShmMap( 701ef9659baSdrh sqlite3_file *pConn, /* Handle open on database file */ 702748a52cdSdrh int iRegion, /* Region to retrieve */ 703748a52cdSdrh int szRegion, /* Size of regions */ 704748a52cdSdrh int bExtend, /* True to extend file if necessary */ 705748a52cdSdrh void volatile **pp /* OUT: Mapped memory */ 706748a52cdSdrh ){ 707ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 708745de2e4Sdrh return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend, pp); 709748a52cdSdrh } 710748a52cdSdrh 711748a52cdSdrh /* Pass xShmLock requests through to the original VFS unchanged. 712748a52cdSdrh */ 713748a52cdSdrh static int quotaShmLock( 714ef9659baSdrh sqlite3_file *pConn, /* Database file holding the shared memory */ 715748a52cdSdrh int ofst, /* First lock to acquire or release */ 716748a52cdSdrh int n, /* Number of locks to acquire or release */ 717748a52cdSdrh int flags /* What to do with the lock */ 718748a52cdSdrh ){ 719ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 720745de2e4Sdrh return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags); 721748a52cdSdrh } 722748a52cdSdrh 723748a52cdSdrh /* Pass xShmBarrier requests through to the original VFS unchanged. 724748a52cdSdrh */ 725ef9659baSdrh static void quotaShmBarrier(sqlite3_file *pConn){ 726ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 727745de2e4Sdrh pSubOpen->pMethods->xShmBarrier(pSubOpen); 728748a52cdSdrh } 729748a52cdSdrh 730748a52cdSdrh /* Pass xShmUnmap requests through to the original VFS unchanged. 731748a52cdSdrh */ 732ef9659baSdrh static int quotaShmUnmap(sqlite3_file *pConn, int deleteFlag){ 733ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 734745de2e4Sdrh return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag); 735748a52cdSdrh } 736748a52cdSdrh 737748a52cdSdrh /************************** Public Interfaces *****************************/ 738748a52cdSdrh /* 739748a52cdSdrh ** Initialize the quota VFS shim. Use the VFS named zOrigVfsName 740748a52cdSdrh ** as the VFS that does the actual work. Use the default if 741748a52cdSdrh ** zOrigVfsName==NULL. 742748a52cdSdrh ** 743748a52cdSdrh ** The quota VFS shim is named "quota". It will become the default 744748a52cdSdrh ** VFS if makeDefault is non-zero. 745748a52cdSdrh ** 746748a52cdSdrh ** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once 747748a52cdSdrh ** during start-up. 748748a52cdSdrh */ 749748a52cdSdrh int sqlite3_quota_initialize(const char *zOrigVfsName, int makeDefault){ 750748a52cdSdrh sqlite3_vfs *pOrigVfs; 7518cf35eb4Sdan if( gQuota.isInitialized ) return SQLITE_MISUSE; 752748a52cdSdrh pOrigVfs = sqlite3_vfs_find(zOrigVfsName); 753748a52cdSdrh if( pOrigVfs==0 ) return SQLITE_ERROR; 7548cf35eb4Sdan assert( pOrigVfs!=&gQuota.sThisVfs ); 7558cf35eb4Sdan gQuota.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); 7568cf35eb4Sdan if( !gQuota.pMutex ){ 7578cf35eb4Sdan return SQLITE_NOMEM; 7588cf35eb4Sdan } 7598cf35eb4Sdan gQuota.isInitialized = 1; 760748a52cdSdrh gQuota.pOrigVfs = pOrigVfs; 761748a52cdSdrh gQuota.sThisVfs = *pOrigVfs; 762748a52cdSdrh gQuota.sThisVfs.xOpen = quotaOpen; 763796af156Sdrh gQuota.sThisVfs.xDelete = quotaDelete; 764ef9659baSdrh gQuota.sThisVfs.szOsFile += sizeof(quotaConn); 765748a52cdSdrh gQuota.sThisVfs.zName = "quota"; 766748a52cdSdrh gQuota.sIoMethodsV1.iVersion = 1; 767748a52cdSdrh gQuota.sIoMethodsV1.xClose = quotaClose; 768748a52cdSdrh gQuota.sIoMethodsV1.xRead = quotaRead; 769748a52cdSdrh gQuota.sIoMethodsV1.xWrite = quotaWrite; 770748a52cdSdrh gQuota.sIoMethodsV1.xTruncate = quotaTruncate; 771748a52cdSdrh gQuota.sIoMethodsV1.xSync = quotaSync; 772748a52cdSdrh gQuota.sIoMethodsV1.xFileSize = quotaFileSize; 773748a52cdSdrh gQuota.sIoMethodsV1.xLock = quotaLock; 774748a52cdSdrh gQuota.sIoMethodsV1.xUnlock = quotaUnlock; 775748a52cdSdrh gQuota.sIoMethodsV1.xCheckReservedLock = quotaCheckReservedLock; 776748a52cdSdrh gQuota.sIoMethodsV1.xFileControl = quotaFileControl; 7778cf35eb4Sdan gQuota.sIoMethodsV1.xSectorSize = quotaSectorSize; 778748a52cdSdrh gQuota.sIoMethodsV1.xDeviceCharacteristics = quotaDeviceCharacteristics; 779748a52cdSdrh gQuota.sIoMethodsV2 = gQuota.sIoMethodsV1; 780748a52cdSdrh gQuota.sIoMethodsV2.iVersion = 2; 781748a52cdSdrh gQuota.sIoMethodsV2.xShmMap = quotaShmMap; 782748a52cdSdrh gQuota.sIoMethodsV2.xShmLock = quotaShmLock; 783748a52cdSdrh gQuota.sIoMethodsV2.xShmBarrier = quotaShmBarrier; 784748a52cdSdrh gQuota.sIoMethodsV2.xShmUnmap = quotaShmUnmap; 7858cf35eb4Sdan sqlite3_vfs_register(&gQuota.sThisVfs, makeDefault); 7868cf35eb4Sdan return SQLITE_OK; 787748a52cdSdrh } 788748a52cdSdrh 789748a52cdSdrh /* 790748a52cdSdrh ** Shutdown the quota system. 791748a52cdSdrh ** 792748a52cdSdrh ** All SQLite database connections must be closed before calling this 793748a52cdSdrh ** routine. 794748a52cdSdrh ** 795796af156Sdrh ** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once while 796748a52cdSdrh ** shutting down in order to free all remaining quota groups. 797748a52cdSdrh */ 798748a52cdSdrh int sqlite3_quota_shutdown(void){ 79975c19174Sdrh quotaGroup *pGroup; 800748a52cdSdrh if( gQuota.isInitialized==0 ) return SQLITE_MISUSE; 80175c19174Sdrh for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){ 802796af156Sdrh if( quotaGroupOpenFileCount(pGroup)>0 ) return SQLITE_MISUSE; 8038cf35eb4Sdan } 8048cf35eb4Sdan while( gQuota.pGroup ){ 80575c19174Sdrh pGroup = gQuota.pGroup; 80675c19174Sdrh gQuota.pGroup = pGroup->pNext; 8079fc7702cSdan pGroup->iLimit = 0; 808796af156Sdrh assert( quotaGroupOpenFileCount(pGroup)==0 ); 8099fc7702cSdan quotaGroupDeref(pGroup); 8108cf35eb4Sdan } 811748a52cdSdrh gQuota.isInitialized = 0; 812748a52cdSdrh sqlite3_mutex_free(gQuota.pMutex); 8138cf35eb4Sdan sqlite3_vfs_unregister(&gQuota.sThisVfs); 814748a52cdSdrh memset(&gQuota, 0, sizeof(gQuota)); 8158cf35eb4Sdan return SQLITE_OK; 816748a52cdSdrh } 817748a52cdSdrh 818748a52cdSdrh /* 819748a52cdSdrh ** Create or destroy a quota group. 820748a52cdSdrh ** 821748a52cdSdrh ** The quota group is defined by the zPattern. When calling this routine 822748a52cdSdrh ** with a zPattern for a quota group that already exists, this routine 823748a52cdSdrh ** merely updates the iLimit, xCallback, and pArg values for that quota 824748a52cdSdrh ** group. If zPattern is new, then a new quota group is created. 825748a52cdSdrh ** 826748a52cdSdrh ** If the iLimit for a quota group is set to zero, then the quota group 827748a52cdSdrh ** is disabled and will be deleted when the last database connection using 828748a52cdSdrh ** the quota group is closed. 829748a52cdSdrh ** 830748a52cdSdrh ** Calling this routine on a zPattern that does not exist and with a 831748a52cdSdrh ** zero iLimit is a no-op. 832748a52cdSdrh ** 833748a52cdSdrh ** A quota group must exist with a non-zero iLimit prior to opening 834748a52cdSdrh ** database connections if those connections are to participate in the 8358cf35eb4Sdan ** quota group. Creating a quota group does not affect database connections 836748a52cdSdrh ** that are already open. 837748a52cdSdrh */ 838748a52cdSdrh int sqlite3_quota_set( 839748a52cdSdrh const char *zPattern, /* The filename pattern */ 840748a52cdSdrh sqlite3_int64 iLimit, /* New quota to set for this quota group */ 841748a52cdSdrh void (*xCallback)( /* Callback invoked when going over quota */ 842748a52cdSdrh const char *zFilename, /* Name of file whose size increases */ 843748a52cdSdrh sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ 844748a52cdSdrh sqlite3_int64 iSize, /* Total size of all files in the group */ 845748a52cdSdrh void *pArg /* Client data */ 8468cf35eb4Sdan ), 847ef9659baSdrh void *pArg, /* client data passed thru to callback */ 848ef9659baSdrh void (*xDestroy)(void*) /* Optional destructor for pArg */ 849748a52cdSdrh ){ 850748a52cdSdrh quotaGroup *pGroup; 851748a52cdSdrh quotaEnter(); 852748a52cdSdrh pGroup = gQuota.pGroup; 853748a52cdSdrh while( pGroup && strcmp(pGroup->zPattern, zPattern)!=0 ){ 854748a52cdSdrh pGroup = pGroup->pNext; 855748a52cdSdrh } 85661cc423aSdrh if( pGroup==0 ){ 85783cc1392Sdrh int nPattern = (int)(strlen(zPattern) & 0x3fffffff); 85861cc423aSdrh if( iLimit<=0 ){ 85961cc423aSdrh quotaLeave(); 86061cc423aSdrh return SQLITE_OK; 86161cc423aSdrh } 862b0a32832Sdan pGroup = (quotaGroup *)sqlite3_malloc( sizeof(*pGroup) + nPattern + 1 ); 863748a52cdSdrh if( pGroup==0 ){ 864748a52cdSdrh quotaLeave(); 865748a52cdSdrh return SQLITE_NOMEM; 866748a52cdSdrh } 867748a52cdSdrh memset(pGroup, 0, sizeof(*pGroup)); 868748a52cdSdrh pGroup->zPattern = (char*)&pGroup[1]; 8698cf35eb4Sdan memcpy((char *)pGroup->zPattern, zPattern, nPattern+1); 870748a52cdSdrh if( gQuota.pGroup ) gQuota.pGroup->ppPrev = &pGroup->pNext; 8718cf35eb4Sdan pGroup->pNext = gQuota.pGroup; 8728cf35eb4Sdan pGroup->ppPrev = &gQuota.pGroup; 8738cf35eb4Sdan gQuota.pGroup = pGroup; 874748a52cdSdrh } 875748a52cdSdrh pGroup->iLimit = iLimit; 876748a52cdSdrh pGroup->xCallback = xCallback; 877ef9659baSdrh if( pGroup->xDestroy && pGroup->pArg!=pArg ){ 878ef9659baSdrh pGroup->xDestroy(pGroup->pArg); 879ef9659baSdrh } 880748a52cdSdrh pGroup->pArg = pArg; 881ef9659baSdrh pGroup->xDestroy = xDestroy; 882748a52cdSdrh quotaGroupDeref(pGroup); 883748a52cdSdrh quotaLeave(); 884748a52cdSdrh return SQLITE_OK; 885748a52cdSdrh } 886748a52cdSdrh 887b6020c4eSdrh /* 888b6020c4eSdrh ** Bring the named file under quota management. Or if it is already under 889b6020c4eSdrh ** management, update its size. 890b6020c4eSdrh */ 891b6020c4eSdrh int sqlite3_quota_file(const char *zFilename){ 892b6020c4eSdrh char *zFull; 893b6020c4eSdrh sqlite3_file *fd; 894b6020c4eSdrh int rc; 895b6020c4eSdrh int outFlags = 0; 896b6020c4eSdrh sqlite3_int64 iSize; 89772cd90acSdan int nAlloc = gQuota.sThisVfs.szOsFile + gQuota.sThisVfs.mxPathname+2; 89872cd90acSdan 89972cd90acSdan /* Allocate space for a file-handle and the full path for file zFilename */ 90072cd90acSdan fd = (sqlite3_file *)sqlite3_malloc(nAlloc); 90172cd90acSdan if( fd==0 ){ 90272cd90acSdan rc = SQLITE_NOMEM; 90372cd90acSdan }else{ 90472cd90acSdan zFull = &((char *)fd)[gQuota.sThisVfs.szOsFile]; 905b6020c4eSdrh rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename, 906b6020c4eSdrh gQuota.sThisVfs.mxPathname+1, zFull); 90772cd90acSdan } 90872cd90acSdan 909b6020c4eSdrh if( rc==SQLITE_OK ){ 91072cd90acSdan zFull[strlen(zFull)+1] = '\0'; 911b6020c4eSdrh rc = quotaOpen(&gQuota.sThisVfs, zFull, fd, 912b6020c4eSdrh SQLITE_OPEN_READONLY | SQLITE_OPEN_MAIN_DB, &outFlags); 913b6020c4eSdrh if( rc==SQLITE_OK ){ 91426541c9aSdrh fd->pMethods->xFileSize(fd, &iSize); 91526541c9aSdrh fd->pMethods->xClose(fd); 916b6020c4eSdrh }else if( rc==SQLITE_CANTOPEN ){ 917b6020c4eSdrh quotaGroup *pGroup; 918b6020c4eSdrh quotaFile *pFile; 919b6020c4eSdrh quotaEnter(); 920b6020c4eSdrh pGroup = quotaGroupFind(zFull); 921b6020c4eSdrh if( pGroup ){ 92260f21e4bSdrh pFile = quotaFindFile(pGroup, zFull, 0); 923b6020c4eSdrh if( pFile ) quotaRemoveFile(pFile); 924b6020c4eSdrh } 925b6020c4eSdrh quotaLeave(); 926b6020c4eSdrh } 92772cd90acSdan } 92872cd90acSdan 929b6020c4eSdrh sqlite3_free(fd); 930b6020c4eSdrh return rc; 931b6020c4eSdrh } 932b6020c4eSdrh 93360f21e4bSdrh /* 93460f21e4bSdrh ** Open a potentially quotaed file for I/O. 93560f21e4bSdrh */ 93660f21e4bSdrh quota_FILE *sqlite3_quota_fopen(const char *zFilename, const char *zMode){ 93760f21e4bSdrh quota_FILE *p = 0; 93860f21e4bSdrh char *zFull = 0; 939c00ce490Sdrh char *zFullTranslated = 0; 94060f21e4bSdrh int rc; 94160f21e4bSdrh quotaGroup *pGroup; 94260f21e4bSdrh quotaFile *pFile; 94360f21e4bSdrh 944663cebfeSdrh zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1); 945a76e8589Sdrh if( zFull==0 ) return 0; 94660f21e4bSdrh rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename, 94760f21e4bSdrh gQuota.sThisVfs.mxPathname+1, zFull); 94860f21e4bSdrh if( rc ) goto quota_fopen_error; 949663cebfeSdrh p = (quota_FILE*)sqlite3_malloc(sizeof(*p)); 95060f21e4bSdrh if( p==0 ) goto quota_fopen_error; 95160f21e4bSdrh memset(p, 0, sizeof(*p)); 952a76e8589Sdrh zFullTranslated = quota_utf8_to_mbcs(zFull); 953a76e8589Sdrh if( zFullTranslated==0 ) goto quota_fopen_error; 954a76e8589Sdrh p->f = fopen(zFullTranslated, zMode); 95560f21e4bSdrh if( p->f==0 ) goto quota_fopen_error; 95660f21e4bSdrh quotaEnter(); 95760f21e4bSdrh pGroup = quotaGroupFind(zFull); 95860f21e4bSdrh if( pGroup ){ 95960f21e4bSdrh pFile = quotaFindFile(pGroup, zFull, 1); 96060f21e4bSdrh if( pFile==0 ){ 96160f21e4bSdrh quotaLeave(); 96260f21e4bSdrh goto quota_fopen_error; 96360f21e4bSdrh } 96460f21e4bSdrh pFile->nRef++; 96560f21e4bSdrh p->pFile = pFile; 96660f21e4bSdrh } 96760f21e4bSdrh quotaLeave(); 96860f21e4bSdrh sqlite3_free(zFull); 969c00ce490Sdrh #if SQLITE_OS_WIN 970c00ce490Sdrh p->zMbcsName = zFullTranslated; 971c00ce490Sdrh #endif 97260f21e4bSdrh return p; 97360f21e4bSdrh 97460f21e4bSdrh quota_fopen_error: 975c00ce490Sdrh quota_mbcs_free(zFullTranslated); 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( 99998c78ea6Sdrh const 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; 1008c00ce490Sdrh size_t rc; 100960f21e4bSdrh 101060f21e4bSdrh iOfst = ftell(p->f); 101160f21e4bSdrh iEnd = iOfst + size*nmemb; 101260f21e4bSdrh pFile = p->pFile; 1013c8ccda60Sdrh if( pFile && pFile->iSize<iEnd ){ 101460f21e4bSdrh quotaGroup *pGroup = pFile->pGroup; 101560f21e4bSdrh quotaEnter(); 101660f21e4bSdrh szNew = pGroup->iSize - pFile->iSize + iEnd; 101760f21e4bSdrh if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ 101860f21e4bSdrh if( pGroup->xCallback ){ 101960f21e4bSdrh pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew, 102060f21e4bSdrh pGroup->pArg); 102160f21e4bSdrh } 102260f21e4bSdrh if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ 102360f21e4bSdrh iEnd = pGroup->iLimit - pGroup->iSize + pFile->iSize; 10247da5fcb0Sdrh nmemb = (size_t)((iEnd - iOfst)/size); 102560f21e4bSdrh iEnd = iOfst + size*nmemb; 102660f21e4bSdrh szNew = pGroup->iSize - pFile->iSize + iEnd; 102760f21e4bSdrh } 102860f21e4bSdrh } 102960f21e4bSdrh pGroup->iSize = szNew; 103060f21e4bSdrh pFile->iSize = iEnd; 103160f21e4bSdrh quotaLeave(); 1032c00ce490Sdrh }else{ 1033c00ce490Sdrh pFile = 0; 103460f21e4bSdrh } 1035c00ce490Sdrh rc = fwrite(pBuf, size, nmemb, p->f); 1036c00ce490Sdrh 1037c00ce490Sdrh /* If the write was incomplete, adjust the file size and group size 1038c00ce490Sdrh ** downward */ 1039c00ce490Sdrh if( rc<nmemb && pFile ){ 1040b3f787f4Sdrh size_t nWritten = rc; 1041c00ce490Sdrh sqlite3_int64 iNewEnd = iOfst + size*nWritten; 1042c00ce490Sdrh if( iNewEnd<iEnd ) iNewEnd = iEnd; 1043c00ce490Sdrh quotaEnter(); 1044c00ce490Sdrh pFile->pGroup->iSize += iNewEnd - pFile->iSize; 1045c00ce490Sdrh pFile->iSize = iNewEnd; 1046c00ce490Sdrh quotaLeave(); 1047c00ce490Sdrh } 1048c00ce490Sdrh return rc; 104960f21e4bSdrh } 105060f21e4bSdrh 105160f21e4bSdrh /* 105260f21e4bSdrh ** Close an open quota_FILE stream. 105360f21e4bSdrh */ 105460f21e4bSdrh int sqlite3_quota_fclose(quota_FILE *p){ 105560f21e4bSdrh int rc; 105660f21e4bSdrh quotaFile *pFile; 105760f21e4bSdrh rc = fclose(p->f); 105860f21e4bSdrh pFile = p->pFile; 1059c8ccda60Sdrh if( pFile ){ 106060f21e4bSdrh quotaEnter(); 106160f21e4bSdrh pFile->nRef--; 106260f21e4bSdrh if( pFile->nRef==0 ){ 106360f21e4bSdrh quotaGroup *pGroup = pFile->pGroup; 106469b2232dSdrh if( pFile->deleteOnClose ){ 106569b2232dSdrh gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0); 106669b2232dSdrh quotaRemoveFile(pFile); 106769b2232dSdrh } 106860f21e4bSdrh quotaGroupDeref(pGroup); 106960f21e4bSdrh } 107060f21e4bSdrh quotaLeave(); 1071c8ccda60Sdrh } 1072c00ce490Sdrh #if SQLITE_OS_WIN 1073c00ce490Sdrh quota_mbcs_free(p->zMbcsName); 1074c00ce490Sdrh #endif 107560f21e4bSdrh sqlite3_free(p); 107660f21e4bSdrh return rc; 107760f21e4bSdrh } 107860f21e4bSdrh 107960f21e4bSdrh /* 108069b2232dSdrh ** Flush memory buffers for a quota_FILE to disk. 108169b2232dSdrh */ 108227cec37dSdrh int sqlite3_quota_fflush(quota_FILE *p, int doFsync){ 108327cec37dSdrh int rc; 108427cec37dSdrh rc = fflush(p->f); 108527cec37dSdrh if( rc==0 && doFsync ){ 108627cec37dSdrh #if SQLITE_OS_UNIX 108727cec37dSdrh rc = fsync(fileno(p->f)); 108827cec37dSdrh #endif 108927cec37dSdrh #if SQLITE_OS_WIN 1090c39f3e00Sdrh rc = _commit(_fileno(p->f)); 109127cec37dSdrh #endif 109227cec37dSdrh } 1093c39f3e00Sdrh return rc!=0; 109469b2232dSdrh } 109569b2232dSdrh 109669b2232dSdrh /* 109760f21e4bSdrh ** Seek on a quota_FILE stream. 109860f21e4bSdrh */ 109960f21e4bSdrh int sqlite3_quota_fseek(quota_FILE *p, long offset, int whence){ 110060f21e4bSdrh return fseek(p->f, offset, whence); 110160f21e4bSdrh } 110260f21e4bSdrh 110360f21e4bSdrh /* 110460f21e4bSdrh ** rewind a quota_FILE stream. 110560f21e4bSdrh */ 110660f21e4bSdrh void sqlite3_quota_rewind(quota_FILE *p){ 110760f21e4bSdrh rewind(p->f); 110860f21e4bSdrh } 110960f21e4bSdrh 111060f21e4bSdrh /* 111160f21e4bSdrh ** Tell the current location of a quota_FILE stream. 111260f21e4bSdrh */ 111360f21e4bSdrh long sqlite3_quota_ftell(quota_FILE *p){ 111460f21e4bSdrh return ftell(p->f); 111560f21e4bSdrh } 111660f21e4bSdrh 111760f21e4bSdrh /* 111898c78ea6Sdrh ** Test the error indicator for the given file. 111998c78ea6Sdrh */ 112098c78ea6Sdrh int sqlite3_quota_ferror(quota_FILE *p){ 112198c78ea6Sdrh return ferror(p->f); 112298c78ea6Sdrh } 112398c78ea6Sdrh 112498c78ea6Sdrh /* 1125c00ce490Sdrh ** Truncate a file to szNew bytes. 1126c00ce490Sdrh */ 1127c00ce490Sdrh int sqlite3_quota_ftruncate(quota_FILE *p, sqlite3_int64 szNew){ 1128c00ce490Sdrh quotaFile *pFile = p->pFile; 1129c00ce490Sdrh int rc; 1130c00ce490Sdrh if( (pFile = p->pFile)!=0 && pFile->iSize<szNew ){ 1131c00ce490Sdrh quotaGroup *pGroup; 1132c00ce490Sdrh if( pFile->iSize<szNew ){ 1133c00ce490Sdrh /* This routine cannot be used to extend a file that is under 1134c00ce490Sdrh ** quota management. Only true truncation is allowed. */ 1135c00ce490Sdrh return -1; 1136c00ce490Sdrh } 1137c00ce490Sdrh pGroup = pFile->pGroup; 1138c00ce490Sdrh quotaEnter(); 1139c00ce490Sdrh pGroup->iSize += szNew - pFile->iSize; 1140c00ce490Sdrh quotaLeave(); 1141c00ce490Sdrh } 1142c00ce490Sdrh #if SQLITE_OS_UNIX 1143c00ce490Sdrh rc = ftruncate(fileno(p->f), szNew); 1144c00ce490Sdrh #endif 1145c00ce490Sdrh #if SQLITE_OS_WIN 114646f7d98cSdrh # if defined(__MINGW32__) && defined(SQLITE_TEST) 114746f7d98cSdrh /* _chsize_s() is missing from MingW (as of 2012-11-06). Use 114846f7d98cSdrh ** _chsize() as a work-around for testing purposes. */ 114946f7d98cSdrh rc = _chsize(_fileno(p->f), (long)szNew); 115046f7d98cSdrh # else 1151c00ce490Sdrh rc = _chsize_s(_fileno(p->f), szNew); 1152c00ce490Sdrh # endif 115346f7d98cSdrh #endif 1154c00ce490Sdrh if( pFile && rc==0 ){ 1155c00ce490Sdrh quotaGroup *pGroup = pFile->pGroup; 1156c00ce490Sdrh quotaEnter(); 1157c00ce490Sdrh pGroup->iSize += szNew - pFile->iSize; 1158c00ce490Sdrh pFile->iSize = szNew; 1159c00ce490Sdrh quotaLeave(); 1160c00ce490Sdrh } 1161c00ce490Sdrh return rc; 1162c00ce490Sdrh } 1163c00ce490Sdrh 1164c00ce490Sdrh /* 1165c00ce490Sdrh ** Determine the time that the given file was last modified, in 1166c00ce490Sdrh ** seconds size 1970. Write the result into *pTime. Return 0 on 1167c00ce490Sdrh ** success and non-zero on any kind of error. 1168c00ce490Sdrh */ 1169c00ce490Sdrh int sqlite3_quota_file_mtime(quota_FILE *p, time_t *pTime){ 1170c00ce490Sdrh int rc; 1171c00ce490Sdrh #if SQLITE_OS_UNIX 1172c00ce490Sdrh struct stat buf; 1173c00ce490Sdrh rc = fstat(fileno(p->f), &buf); 1174c00ce490Sdrh #endif 1175c00ce490Sdrh #if SQLITE_OS_WIN 1176c00ce490Sdrh struct _stati64 buf; 1177c00ce490Sdrh rc = _stati64(p->zMbcsName, &buf); 1178c00ce490Sdrh #endif 1179c00ce490Sdrh if( rc==0 ) *pTime = buf.st_mtime; 1180c00ce490Sdrh return rc; 1181c00ce490Sdrh } 1182c00ce490Sdrh 1183c00ce490Sdrh /* 1184c00ce490Sdrh ** Return the true size of the file, as reported by the operating 1185c00ce490Sdrh ** system. 1186c00ce490Sdrh */ 1187c00ce490Sdrh sqlite3_int64 sqlite3_quota_file_truesize(quota_FILE *p){ 1188c00ce490Sdrh int rc; 1189c00ce490Sdrh #if SQLITE_OS_UNIX 1190c00ce490Sdrh struct stat buf; 1191c00ce490Sdrh rc = fstat(fileno(p->f), &buf); 1192c00ce490Sdrh #endif 1193c00ce490Sdrh #if SQLITE_OS_WIN 1194c00ce490Sdrh struct _stati64 buf; 1195c00ce490Sdrh rc = _stati64(p->zMbcsName, &buf); 1196c00ce490Sdrh #endif 1197c00ce490Sdrh return rc==0 ? buf.st_size : -1; 1198c00ce490Sdrh } 1199c00ce490Sdrh 1200c00ce490Sdrh /* 1201c00ce490Sdrh ** Return the size of the file, as it is known to the quota subsystem. 1202c00ce490Sdrh */ 1203c00ce490Sdrh sqlite3_int64 sqlite3_quota_file_size(quota_FILE *p){ 1204c00ce490Sdrh return p->pFile ? p->pFile->iSize : -1; 1205c00ce490Sdrh } 1206c00ce490Sdrh 1207c00ce490Sdrh /* 120898c78ea6Sdrh ** Determine the amount of data in bytes available for reading 120998c78ea6Sdrh ** in the given file. 121098c78ea6Sdrh */ 121198c78ea6Sdrh long sqlite3_quota_file_available(quota_FILE *p){ 121298c78ea6Sdrh FILE* f = p->f; 121398c78ea6Sdrh long pos1, pos2; 121498c78ea6Sdrh int rc; 121598c78ea6Sdrh pos1 = ftell(f); 121698c78ea6Sdrh if ( pos1 < 0 ) return -1; 121798c78ea6Sdrh rc = fseek(f, 0, SEEK_END); 121898c78ea6Sdrh if ( rc != 0 ) return -1; 121998c78ea6Sdrh pos2 = ftell(f); 122098c78ea6Sdrh if ( pos2 < 0 ) return -1; 122198c78ea6Sdrh rc = fseek(f, pos1, SEEK_SET); 122298c78ea6Sdrh if ( rc != 0 ) return -1; 122398c78ea6Sdrh return pos2 - pos1; 122498c78ea6Sdrh } 122598c78ea6Sdrh 122698c78ea6Sdrh /* 122769b2232dSdrh ** Remove a managed file. Update quotas accordingly. 122860f21e4bSdrh */ 122960f21e4bSdrh int sqlite3_quota_remove(const char *zFilename){ 123069b2232dSdrh char *zFull; /* Full pathname for zFilename */ 123183cc1392Sdrh size_t nFull; /* Number of bytes in zFilename */ 123269b2232dSdrh int rc; /* Result code */ 123369b2232dSdrh quotaGroup *pGroup; /* Group containing zFilename */ 123469b2232dSdrh quotaFile *pFile; /* A file in the group */ 123569b2232dSdrh quotaFile *pNextFile; /* next file in the group */ 123669b2232dSdrh int diff; /* Difference between filenames */ 123769b2232dSdrh char c; /* First character past end of pattern */ 123869b2232dSdrh 1239663cebfeSdrh zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1); 124069b2232dSdrh if( zFull==0 ) return SQLITE_NOMEM; 124169b2232dSdrh rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename, 124269b2232dSdrh gQuota.sThisVfs.mxPathname+1, zFull); 124369b2232dSdrh if( rc ){ 124469b2232dSdrh sqlite3_free(zFull); 124560f21e4bSdrh return rc; 124660f21e4bSdrh } 124760f21e4bSdrh 124869b2232dSdrh /* Figure out the length of the full pathname. If the name ends with 124969b2232dSdrh ** / (or \ on windows) then remove the trailing /. 125069b2232dSdrh */ 125169b2232dSdrh nFull = strlen(zFull); 125269b2232dSdrh if( nFull>0 && (zFull[nFull-1]=='/' || zFull[nFull-1]=='\\') ){ 125369b2232dSdrh nFull--; 125469b2232dSdrh zFull[nFull] = 0; 125569b2232dSdrh } 125669b2232dSdrh 125769b2232dSdrh quotaEnter(); 125869b2232dSdrh pGroup = quotaGroupFind(zFull); 125969b2232dSdrh if( pGroup ){ 126069b2232dSdrh for(pFile=pGroup->pFiles; pFile && rc==SQLITE_OK; pFile=pNextFile){ 126169b2232dSdrh pNextFile = pFile->pNext; 1262503a686eSdrh diff = strncmp(zFull, pFile->zFilename, nFull); 126369b2232dSdrh if( diff==0 && ((c = pFile->zFilename[nFull])==0 || c=='/' || c=='\\') ){ 126469b2232dSdrh if( pFile->nRef ){ 126569b2232dSdrh pFile->deleteOnClose = 1; 126669b2232dSdrh }else{ 126769b2232dSdrh rc = gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0); 126869b2232dSdrh quotaRemoveFile(pFile); 126969b2232dSdrh quotaGroupDeref(pGroup); 127069b2232dSdrh } 127169b2232dSdrh } 127269b2232dSdrh } 127369b2232dSdrh } 127469b2232dSdrh quotaLeave(); 127569b2232dSdrh sqlite3_free(zFull); 127669b2232dSdrh return rc; 127769b2232dSdrh } 1278748a52cdSdrh 1279748a52cdSdrh /***************************** Test Code ***********************************/ 1280748a52cdSdrh #ifdef SQLITE_TEST 1281748a52cdSdrh #include <tcl.h> 12828cf35eb4Sdan 1283ef9659baSdrh /* 1284ef9659baSdrh ** Argument passed to a TCL quota-over-limit callback. 1285ef9659baSdrh */ 12868cf35eb4Sdan typedef struct TclQuotaCallback TclQuotaCallback; 12878cf35eb4Sdan struct TclQuotaCallback { 1288ef9659baSdrh Tcl_Interp *interp; /* Interpreter in which to run the script */ 1289ef9659baSdrh Tcl_Obj *pScript; /* Script to be run */ 12908cf35eb4Sdan }; 12918cf35eb4Sdan 1292e84d8d32Smistachkin extern const char *sqlite3ErrName(int); 12938cf35eb4Sdan 1294ef9659baSdrh 1295ef9659baSdrh /* 1296ef9659baSdrh ** This is the callback from a quota-over-limit. 1297ef9659baSdrh */ 12988cf35eb4Sdan static void tclQuotaCallback( 12998cf35eb4Sdan const char *zFilename, /* Name of file whose size increases */ 13008cf35eb4Sdan sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ 13018cf35eb4Sdan sqlite3_int64 iSize, /* Total size of all files in the group */ 13028cf35eb4Sdan void *pArg /* Client data */ 13038cf35eb4Sdan ){ 13048cf35eb4Sdan TclQuotaCallback *p; /* Callback script object */ 13058cf35eb4Sdan Tcl_Obj *pEval; /* Script to evaluate */ 13068cf35eb4Sdan Tcl_Obj *pVarname; /* Name of variable to pass as 2nd arg */ 13078cf35eb4Sdan unsigned int rnd; /* Random part of pVarname */ 13088cf35eb4Sdan int rc; /* Tcl error code */ 13098cf35eb4Sdan 13108cf35eb4Sdan p = (TclQuotaCallback *)pArg; 13113d3e60e4Sdrh if( p==0 ) return; 13128cf35eb4Sdan 13138cf35eb4Sdan pVarname = Tcl_NewStringObj("::piLimit_", -1); 13148cf35eb4Sdan Tcl_IncrRefCount(pVarname); 13158cf35eb4Sdan sqlite3_randomness(sizeof(rnd), (void *)&rnd); 13168cf35eb4Sdan Tcl_AppendObjToObj(pVarname, Tcl_NewIntObj((int)(rnd&0x7FFFFFFF))); 13178cf35eb4Sdan Tcl_ObjSetVar2(p->interp, pVarname, 0, Tcl_NewWideIntObj(*piLimit), 0); 13188cf35eb4Sdan 13198cf35eb4Sdan pEval = Tcl_DuplicateObj(p->pScript); 13208cf35eb4Sdan Tcl_IncrRefCount(pEval); 13218cf35eb4Sdan Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zFilename, -1)); 13228cf35eb4Sdan Tcl_ListObjAppendElement(0, pEval, pVarname); 13238cf35eb4Sdan Tcl_ListObjAppendElement(0, pEval, Tcl_NewWideIntObj(iSize)); 13248cf35eb4Sdan rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL); 13258cf35eb4Sdan 13268cf35eb4Sdan if( rc==TCL_OK ){ 1327b3f787f4Sdrh Tcl_WideInt x; 13288cf35eb4Sdan Tcl_Obj *pLimit = Tcl_ObjGetVar2(p->interp, pVarname, 0, 0); 1329b3f787f4Sdrh rc = Tcl_GetWideIntFromObj(p->interp, pLimit, &x); 1330b3f787f4Sdrh *piLimit = x; 13318cf35eb4Sdan Tcl_UnsetVar(p->interp, Tcl_GetString(pVarname), 0); 13328cf35eb4Sdan } 13338cf35eb4Sdan 13348cf35eb4Sdan Tcl_DecrRefCount(pEval); 13358cf35eb4Sdan Tcl_DecrRefCount(pVarname); 13368cf35eb4Sdan if( rc!=TCL_OK ) Tcl_BackgroundError(p->interp); 13378cf35eb4Sdan } 13388cf35eb4Sdan 13398cf35eb4Sdan /* 1340ef9659baSdrh ** Destructor for a TCL quota-over-limit callback. 1341ef9659baSdrh */ 1342ef9659baSdrh static void tclCallbackDestructor(void *pObj){ 1343ef9659baSdrh TclQuotaCallback *p = (TclQuotaCallback*)pObj; 1344ef9659baSdrh if( p ){ 1345ef9659baSdrh Tcl_DecrRefCount(p->pScript); 13469fc7702cSdan sqlite3_free((char *)p); 1347ef9659baSdrh } 1348ef9659baSdrh } 1349ef9659baSdrh 1350ef9659baSdrh /* 13518cf35eb4Sdan ** tclcmd: sqlite3_quota_initialize NAME MAKEDEFAULT 13528cf35eb4Sdan */ 13538cf35eb4Sdan static int test_quota_initialize( 13548cf35eb4Sdan void * clientData, 13558cf35eb4Sdan Tcl_Interp *interp, 13568cf35eb4Sdan int objc, 13578cf35eb4Sdan Tcl_Obj *CONST objv[] 13588cf35eb4Sdan ){ 13598cf35eb4Sdan const char *zName; /* Name of new quota VFS */ 13608cf35eb4Sdan int makeDefault; /* True to make the new VFS the default */ 13618cf35eb4Sdan int rc; /* Value returned by quota_initialize() */ 13628cf35eb4Sdan 13638cf35eb4Sdan /* Process arguments */ 13648cf35eb4Sdan if( objc!=3 ){ 13658cf35eb4Sdan Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT"); 13668cf35eb4Sdan return TCL_ERROR; 13678cf35eb4Sdan } 13688cf35eb4Sdan zName = Tcl_GetString(objv[1]); 13698cf35eb4Sdan if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR; 13708cf35eb4Sdan if( zName[0]=='\0' ) zName = 0; 13718cf35eb4Sdan 13728cf35eb4Sdan /* Call sqlite3_quota_initialize() */ 13738cf35eb4Sdan rc = sqlite3_quota_initialize(zName, makeDefault); 1374e84d8d32Smistachkin Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC); 13758cf35eb4Sdan 13768cf35eb4Sdan return TCL_OK; 13778cf35eb4Sdan } 13788cf35eb4Sdan 13798cf35eb4Sdan /* 13808cf35eb4Sdan ** tclcmd: sqlite3_quota_shutdown 13818cf35eb4Sdan */ 13828cf35eb4Sdan static int test_quota_shutdown( 13838cf35eb4Sdan void * clientData, 13848cf35eb4Sdan Tcl_Interp *interp, 13858cf35eb4Sdan int objc, 13868cf35eb4Sdan Tcl_Obj *CONST objv[] 13878cf35eb4Sdan ){ 13888cf35eb4Sdan int rc; /* Value returned by quota_shutdown() */ 13898cf35eb4Sdan 13908cf35eb4Sdan if( objc!=1 ){ 13918cf35eb4Sdan Tcl_WrongNumArgs(interp, 1, objv, ""); 13928cf35eb4Sdan return TCL_ERROR; 13938cf35eb4Sdan } 13948cf35eb4Sdan 13958cf35eb4Sdan /* Call sqlite3_quota_shutdown() */ 13968cf35eb4Sdan rc = sqlite3_quota_shutdown(); 1397e84d8d32Smistachkin Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC); 13988cf35eb4Sdan 13998cf35eb4Sdan return TCL_OK; 14008cf35eb4Sdan } 14018cf35eb4Sdan 14028cf35eb4Sdan /* 14038cf35eb4Sdan ** tclcmd: sqlite3_quota_set PATTERN LIMIT SCRIPT 14048cf35eb4Sdan */ 14058cf35eb4Sdan static int test_quota_set( 14068cf35eb4Sdan void * clientData, 14078cf35eb4Sdan Tcl_Interp *interp, 14088cf35eb4Sdan int objc, 14098cf35eb4Sdan Tcl_Obj *CONST objv[] 14108cf35eb4Sdan ){ 14118cf35eb4Sdan const char *zPattern; /* File pattern to configure */ 1412b3f787f4Sdrh Tcl_WideInt iLimit; /* Initial quota in bytes */ 14138cf35eb4Sdan Tcl_Obj *pScript; /* Tcl script to invoke to increase quota */ 14148cf35eb4Sdan int rc; /* Value returned by quota_set() */ 14158cf35eb4Sdan TclQuotaCallback *p; /* Callback object */ 1416ef9659baSdrh int nScript; /* Length of callback script */ 14179fc7702cSdan void (*xDestroy)(void*); /* Optional destructor for pArg */ 141806bfd36aSdan void (*xCallback)(const char *, sqlite3_int64 *, sqlite3_int64, void *); 14198cf35eb4Sdan 14208cf35eb4Sdan /* Process arguments */ 14218cf35eb4Sdan if( objc!=4 ){ 14228cf35eb4Sdan Tcl_WrongNumArgs(interp, 1, objv, "PATTERN LIMIT SCRIPT"); 14238cf35eb4Sdan return TCL_ERROR; 14248cf35eb4Sdan } 14258cf35eb4Sdan zPattern = Tcl_GetString(objv[1]); 14268cf35eb4Sdan if( Tcl_GetWideIntFromObj(interp, objv[2], &iLimit) ) return TCL_ERROR; 14278cf35eb4Sdan pScript = objv[3]; 1428ef9659baSdrh Tcl_GetStringFromObj(pScript, &nScript); 14298cf35eb4Sdan 1430ef9659baSdrh if( nScript>0 ){ 14318cf35eb4Sdan /* Allocate a TclQuotaCallback object */ 14329fc7702cSdan p = (TclQuotaCallback *)sqlite3_malloc(sizeof(TclQuotaCallback)); 14339fc7702cSdan if( !p ){ 14349fc7702cSdan Tcl_SetResult(interp, (char *)"SQLITE_NOMEM", TCL_STATIC); 14359fc7702cSdan return TCL_OK; 14369fc7702cSdan } 14378cf35eb4Sdan memset(p, 0, sizeof(TclQuotaCallback)); 14388cf35eb4Sdan p->interp = interp; 1439ef9659baSdrh Tcl_IncrRefCount(pScript); 14408cf35eb4Sdan p->pScript = pScript; 14419fc7702cSdan xDestroy = tclCallbackDestructor; 144206bfd36aSdan xCallback = tclQuotaCallback; 1443ef9659baSdrh }else{ 1444ef9659baSdrh p = 0; 14459fc7702cSdan xDestroy = 0; 144606bfd36aSdan xCallback = 0; 14478cf35eb4Sdan } 14489fc7702cSdan 1449ef9659baSdrh /* Invoke sqlite3_quota_set() */ 145006bfd36aSdan rc = sqlite3_quota_set(zPattern, iLimit, xCallback, (void*)p, xDestroy); 14518cf35eb4Sdan 1452e84d8d32Smistachkin Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC); 14538cf35eb4Sdan return TCL_OK; 14548cf35eb4Sdan } 14558cf35eb4Sdan 14568cf35eb4Sdan /* 1457b6020c4eSdrh ** tclcmd: sqlite3_quota_file FILENAME 1458b6020c4eSdrh */ 1459b6020c4eSdrh static int test_quota_file( 1460b6020c4eSdrh void * clientData, 1461b6020c4eSdrh Tcl_Interp *interp, 1462b6020c4eSdrh int objc, 1463b6020c4eSdrh Tcl_Obj *CONST objv[] 1464b6020c4eSdrh ){ 1465b6020c4eSdrh const char *zFilename; /* File pattern to configure */ 1466b6020c4eSdrh int rc; /* Value returned by quota_file() */ 1467b6020c4eSdrh 1468b6020c4eSdrh /* Process arguments */ 1469b6020c4eSdrh if( objc!=2 ){ 1470b6020c4eSdrh Tcl_WrongNumArgs(interp, 1, objv, "FILENAME"); 1471b6020c4eSdrh return TCL_ERROR; 1472b6020c4eSdrh } 1473b6020c4eSdrh zFilename = Tcl_GetString(objv[1]); 1474b6020c4eSdrh 1475b6020c4eSdrh /* Invoke sqlite3_quota_file() */ 1476b6020c4eSdrh rc = sqlite3_quota_file(zFilename); 1477b6020c4eSdrh 1478e84d8d32Smistachkin Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC); 1479b6020c4eSdrh return TCL_OK; 1480b6020c4eSdrh } 1481b6020c4eSdrh 1482b6020c4eSdrh /* 1483ef9659baSdrh ** tclcmd: sqlite3_quota_dump 1484ef9659baSdrh */ 1485ef9659baSdrh static int test_quota_dump( 1486ef9659baSdrh void * clientData, 1487ef9659baSdrh Tcl_Interp *interp, 1488ef9659baSdrh int objc, 1489ef9659baSdrh Tcl_Obj *CONST objv[] 1490ef9659baSdrh ){ 1491ef9659baSdrh Tcl_Obj *pResult; 1492ef9659baSdrh Tcl_Obj *pGroupTerm; 1493ef9659baSdrh Tcl_Obj *pFileTerm; 1494ef9659baSdrh quotaGroup *pGroup; 1495ef9659baSdrh quotaFile *pFile; 1496ef9659baSdrh 1497ef9659baSdrh pResult = Tcl_NewObj(); 1498ef9659baSdrh quotaEnter(); 1499ef9659baSdrh for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){ 1500ef9659baSdrh pGroupTerm = Tcl_NewObj(); 1501ef9659baSdrh Tcl_ListObjAppendElement(interp, pGroupTerm, 1502ef9659baSdrh Tcl_NewStringObj(pGroup->zPattern, -1)); 1503ef9659baSdrh Tcl_ListObjAppendElement(interp, pGroupTerm, 1504ef9659baSdrh Tcl_NewWideIntObj(pGroup->iLimit)); 1505ef9659baSdrh Tcl_ListObjAppendElement(interp, pGroupTerm, 1506ef9659baSdrh Tcl_NewWideIntObj(pGroup->iSize)); 150775c19174Sdrh for(pFile=pGroup->pFiles; pFile; pFile=pFile->pNext){ 1508c8ccda60Sdrh int i; 1509c8ccda60Sdrh char zTemp[1000]; 1510ef9659baSdrh pFileTerm = Tcl_NewObj(); 1511c8ccda60Sdrh sqlite3_snprintf(sizeof(zTemp), zTemp, "%s", pFile->zFilename); 1512c8ccda60Sdrh for(i=0; zTemp[i]; i++){ if( zTemp[i]=='\\' ) zTemp[i] = '/'; } 1513ef9659baSdrh Tcl_ListObjAppendElement(interp, pFileTerm, 1514c8ccda60Sdrh Tcl_NewStringObj(zTemp, -1)); 1515ef9659baSdrh Tcl_ListObjAppendElement(interp, pFileTerm, 1516ef9659baSdrh Tcl_NewWideIntObj(pFile->iSize)); 1517ef9659baSdrh Tcl_ListObjAppendElement(interp, pFileTerm, 1518ef9659baSdrh Tcl_NewWideIntObj(pFile->nRef)); 1519796af156Sdrh Tcl_ListObjAppendElement(interp, pFileTerm, 1520796af156Sdrh Tcl_NewWideIntObj(pFile->deleteOnClose)); 1521ef9659baSdrh Tcl_ListObjAppendElement(interp, pGroupTerm, pFileTerm); 1522ef9659baSdrh } 1523ef9659baSdrh Tcl_ListObjAppendElement(interp, pResult, pGroupTerm); 1524ef9659baSdrh } 1525ef9659baSdrh quotaLeave(); 1526ef9659baSdrh Tcl_SetObjResult(interp, pResult); 1527ef9659baSdrh return TCL_OK; 1528ef9659baSdrh } 1529ef9659baSdrh 1530748a52cdSdrh /* 1531a76e8589Sdrh ** tclcmd: sqlite3_quota_fopen FILENAME MODE 1532a76e8589Sdrh */ 1533a76e8589Sdrh static int test_quota_fopen( 1534a76e8589Sdrh void * clientData, 1535a76e8589Sdrh Tcl_Interp *interp, 1536a76e8589Sdrh int objc, 1537a76e8589Sdrh Tcl_Obj *CONST objv[] 1538a76e8589Sdrh ){ 1539a76e8589Sdrh const char *zFilename; /* File pattern to configure */ 1540a76e8589Sdrh const char *zMode; /* Mode string */ 1541a76e8589Sdrh quota_FILE *p; /* Open string object */ 1542a76e8589Sdrh char zReturn[50]; /* Name of pointer to return */ 1543a76e8589Sdrh 1544a76e8589Sdrh /* Process arguments */ 1545a76e8589Sdrh if( objc!=3 ){ 1546a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "FILENAME MODE"); 1547a76e8589Sdrh return TCL_ERROR; 1548a76e8589Sdrh } 1549a76e8589Sdrh zFilename = Tcl_GetString(objv[1]); 1550a76e8589Sdrh zMode = Tcl_GetString(objv[2]); 1551a76e8589Sdrh p = sqlite3_quota_fopen(zFilename, zMode); 1552a76e8589Sdrh sqlite3_snprintf(sizeof(zReturn), zReturn, "%p", p); 1553a76e8589Sdrh Tcl_SetResult(interp, zReturn, TCL_VOLATILE); 1554a76e8589Sdrh return TCL_OK; 1555a76e8589Sdrh } 1556a76e8589Sdrh 1557a76e8589Sdrh /* Defined in test1.c */ 1558a76e8589Sdrh extern void *sqlite3TestTextToPtr(const char*); 1559a76e8589Sdrh 1560a76e8589Sdrh /* 1561a76e8589Sdrh ** tclcmd: sqlite3_quota_fread HANDLE SIZE NELEM 1562a76e8589Sdrh */ 1563a76e8589Sdrh static int test_quota_fread( 1564a76e8589Sdrh void * clientData, 1565a76e8589Sdrh Tcl_Interp *interp, 1566a76e8589Sdrh int objc, 1567a76e8589Sdrh Tcl_Obj *CONST objv[] 1568a76e8589Sdrh ){ 1569a76e8589Sdrh quota_FILE *p; 1570a76e8589Sdrh char *zBuf; 1571a76e8589Sdrh int sz; 1572a76e8589Sdrh int nElem; 157383cc1392Sdrh size_t got; 1574a76e8589Sdrh 1575a76e8589Sdrh if( objc!=4 ){ 1576a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM"); 1577a76e8589Sdrh return TCL_ERROR; 1578a76e8589Sdrh } 1579a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1580a76e8589Sdrh if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR; 1581a76e8589Sdrh if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR; 1582663cebfeSdrh zBuf = (char*)sqlite3_malloc( sz*nElem + 1 ); 1583a76e8589Sdrh if( zBuf==0 ){ 1584a76e8589Sdrh Tcl_SetResult(interp, "out of memory", TCL_STATIC); 1585a76e8589Sdrh return TCL_ERROR; 1586a76e8589Sdrh } 1587a76e8589Sdrh got = sqlite3_quota_fread(zBuf, sz, nElem, p); 1588a76e8589Sdrh zBuf[got*sz] = 0; 1589a76e8589Sdrh Tcl_SetResult(interp, zBuf, TCL_VOLATILE); 1590a76e8589Sdrh sqlite3_free(zBuf); 1591a76e8589Sdrh return TCL_OK; 1592a76e8589Sdrh } 1593a76e8589Sdrh 1594a76e8589Sdrh /* 1595a76e8589Sdrh ** tclcmd: sqlite3_quota_fwrite HANDLE SIZE NELEM CONTENT 1596a76e8589Sdrh */ 1597a76e8589Sdrh static int test_quota_fwrite( 1598a76e8589Sdrh void * clientData, 1599a76e8589Sdrh Tcl_Interp *interp, 1600a76e8589Sdrh int objc, 1601a76e8589Sdrh Tcl_Obj *CONST objv[] 1602a76e8589Sdrh ){ 1603a76e8589Sdrh quota_FILE *p; 1604a76e8589Sdrh char *zBuf; 1605a76e8589Sdrh int sz; 1606a76e8589Sdrh int nElem; 160783cc1392Sdrh size_t got; 1608a76e8589Sdrh 1609a76e8589Sdrh if( objc!=5 ){ 1610a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM CONTENT"); 1611a76e8589Sdrh return TCL_ERROR; 1612a76e8589Sdrh } 1613a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1614a76e8589Sdrh if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR; 1615a76e8589Sdrh if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR; 1616a76e8589Sdrh zBuf = Tcl_GetString(objv[4]); 1617a76e8589Sdrh got = sqlite3_quota_fwrite(zBuf, sz, nElem, p); 161883cc1392Sdrh Tcl_SetObjResult(interp, Tcl_NewWideIntObj(got)); 1619a76e8589Sdrh return TCL_OK; 1620a76e8589Sdrh } 1621a76e8589Sdrh 1622a76e8589Sdrh /* 1623a76e8589Sdrh ** tclcmd: sqlite3_quota_fclose HANDLE 1624a76e8589Sdrh */ 1625a76e8589Sdrh static int test_quota_fclose( 1626a76e8589Sdrh void * clientData, 1627a76e8589Sdrh Tcl_Interp *interp, 1628a76e8589Sdrh int objc, 1629a76e8589Sdrh Tcl_Obj *CONST objv[] 1630a76e8589Sdrh ){ 1631a76e8589Sdrh quota_FILE *p; 1632a76e8589Sdrh int rc; 1633a76e8589Sdrh 1634a76e8589Sdrh if( objc!=2 ){ 1635a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1636a76e8589Sdrh return TCL_ERROR; 1637a76e8589Sdrh } 1638a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1639a76e8589Sdrh rc = sqlite3_quota_fclose(p); 1640a76e8589Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 1641a76e8589Sdrh return TCL_OK; 1642a76e8589Sdrh } 1643a76e8589Sdrh 1644a76e8589Sdrh /* 164527cec37dSdrh ** tclcmd: sqlite3_quota_fflush HANDLE ?HARDSYNC? 164669b2232dSdrh */ 164769b2232dSdrh static int test_quota_fflush( 164869b2232dSdrh void * clientData, 164969b2232dSdrh Tcl_Interp *interp, 165069b2232dSdrh int objc, 165169b2232dSdrh Tcl_Obj *CONST objv[] 165269b2232dSdrh ){ 165369b2232dSdrh quota_FILE *p; 165469b2232dSdrh int rc; 165527cec37dSdrh int doSync = 0; 165669b2232dSdrh 165727cec37dSdrh if( objc!=2 && objc!=3 ){ 165827cec37dSdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE ?HARDSYNC?"); 165969b2232dSdrh return TCL_ERROR; 166069b2232dSdrh } 166169b2232dSdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 166227cec37dSdrh if( objc==3 ){ 166327cec37dSdrh if( Tcl_GetBooleanFromObj(interp, objv[2], &doSync) ) return TCL_ERROR; 166427cec37dSdrh } 166527cec37dSdrh rc = sqlite3_quota_fflush(p, doSync); 166669b2232dSdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 166769b2232dSdrh return TCL_OK; 166869b2232dSdrh } 166969b2232dSdrh 167069b2232dSdrh /* 1671a76e8589Sdrh ** tclcmd: sqlite3_quota_fseek HANDLE OFFSET WHENCE 1672a76e8589Sdrh */ 1673a76e8589Sdrh static int test_quota_fseek( 1674a76e8589Sdrh void * clientData, 1675a76e8589Sdrh Tcl_Interp *interp, 1676a76e8589Sdrh int objc, 1677a76e8589Sdrh Tcl_Obj *CONST objv[] 1678a76e8589Sdrh ){ 1679a76e8589Sdrh quota_FILE *p; 1680a76e8589Sdrh int ofst; 1681a76e8589Sdrh const char *zWhence; 1682a76e8589Sdrh int whence; 1683a76e8589Sdrh int rc; 1684a76e8589Sdrh 1685a76e8589Sdrh if( objc!=4 ){ 1686a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE OFFSET WHENCE"); 1687a76e8589Sdrh return TCL_ERROR; 1688a76e8589Sdrh } 1689a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1690a76e8589Sdrh if( Tcl_GetIntFromObj(interp, objv[2], &ofst) ) return TCL_ERROR; 1691a76e8589Sdrh zWhence = Tcl_GetString(objv[3]); 1692a76e8589Sdrh if( strcmp(zWhence, "SEEK_SET")==0 ){ 1693a76e8589Sdrh whence = SEEK_SET; 1694a76e8589Sdrh }else if( strcmp(zWhence, "SEEK_CUR")==0 ){ 1695a76e8589Sdrh whence = SEEK_CUR; 1696a76e8589Sdrh }else if( strcmp(zWhence, "SEEK_END")==0 ){ 1697a76e8589Sdrh whence = SEEK_END; 1698a76e8589Sdrh }else{ 1699a76e8589Sdrh Tcl_AppendResult(interp, 1700a76e8589Sdrh "WHENCE should be SEEK_SET, SEEK_CUR, or SEEK_END", (char*)0); 1701a76e8589Sdrh return TCL_ERROR; 1702a76e8589Sdrh } 1703a76e8589Sdrh rc = sqlite3_quota_fseek(p, ofst, whence); 1704a76e8589Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 1705a76e8589Sdrh return TCL_OK; 1706a76e8589Sdrh } 1707a76e8589Sdrh 1708a76e8589Sdrh /* 1709a76e8589Sdrh ** tclcmd: sqlite3_quota_rewind HANDLE 1710a76e8589Sdrh */ 1711a76e8589Sdrh static int test_quota_rewind( 1712a76e8589Sdrh void * clientData, 1713a76e8589Sdrh Tcl_Interp *interp, 1714a76e8589Sdrh int objc, 1715a76e8589Sdrh Tcl_Obj *CONST objv[] 1716a76e8589Sdrh ){ 1717a76e8589Sdrh quota_FILE *p; 1718a76e8589Sdrh if( objc!=2 ){ 1719a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1720a76e8589Sdrh return TCL_ERROR; 1721a76e8589Sdrh } 1722a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1723a76e8589Sdrh sqlite3_quota_rewind(p); 1724a76e8589Sdrh return TCL_OK; 1725a76e8589Sdrh } 1726a76e8589Sdrh 1727a76e8589Sdrh /* 1728a76e8589Sdrh ** tclcmd: sqlite3_quota_ftell HANDLE 1729a76e8589Sdrh */ 1730a76e8589Sdrh static int test_quota_ftell( 1731a76e8589Sdrh void * clientData, 1732a76e8589Sdrh Tcl_Interp *interp, 1733a76e8589Sdrh int objc, 1734a76e8589Sdrh Tcl_Obj *CONST objv[] 1735a76e8589Sdrh ){ 1736a76e8589Sdrh quota_FILE *p; 1737a76e8589Sdrh sqlite3_int64 x; 1738a76e8589Sdrh if( objc!=2 ){ 1739a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1740a76e8589Sdrh return TCL_ERROR; 1741a76e8589Sdrh } 1742a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1743a76e8589Sdrh x = sqlite3_quota_ftell(p); 1744a76e8589Sdrh Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); 1745a76e8589Sdrh return TCL_OK; 1746a76e8589Sdrh } 1747a76e8589Sdrh 1748a76e8589Sdrh /* 1749c00ce490Sdrh ** tclcmd: sqlite3_quota_ftruncate HANDLE SIZE 1750c00ce490Sdrh */ 1751c00ce490Sdrh static int test_quota_ftruncate( 1752c00ce490Sdrh void * clientData, 1753c00ce490Sdrh Tcl_Interp *interp, 1754c00ce490Sdrh int objc, 1755c00ce490Sdrh Tcl_Obj *CONST objv[] 1756c00ce490Sdrh ){ 1757c00ce490Sdrh quota_FILE *p; 1758c00ce490Sdrh sqlite3_int64 x; 1759c00ce490Sdrh Tcl_WideInt w; 1760c00ce490Sdrh int rc; 1761c00ce490Sdrh if( objc!=3 ){ 1762c00ce490Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE"); 1763c00ce490Sdrh return TCL_ERROR; 1764c00ce490Sdrh } 1765c00ce490Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1766c00ce490Sdrh if( Tcl_GetWideIntFromObj(interp, objv[2], &w) ) return TCL_ERROR; 1767c00ce490Sdrh x = (sqlite3_int64)w; 1768c00ce490Sdrh rc = sqlite3_quota_ftruncate(p, x); 1769c00ce490Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 1770c00ce490Sdrh return TCL_OK; 1771c00ce490Sdrh } 1772c00ce490Sdrh 1773c00ce490Sdrh /* 1774c00ce490Sdrh ** tclcmd: sqlite3_quota_file_size HANDLE 1775c00ce490Sdrh */ 1776c00ce490Sdrh static int test_quota_file_size( 1777c00ce490Sdrh void * clientData, 1778c00ce490Sdrh Tcl_Interp *interp, 1779c00ce490Sdrh int objc, 1780c00ce490Sdrh Tcl_Obj *CONST objv[] 1781c00ce490Sdrh ){ 1782c00ce490Sdrh quota_FILE *p; 1783c00ce490Sdrh sqlite3_int64 x; 1784c00ce490Sdrh if( objc!=2 ){ 1785c00ce490Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1786c00ce490Sdrh return TCL_ERROR; 1787c00ce490Sdrh } 1788c00ce490Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1789c00ce490Sdrh x = sqlite3_quota_file_size(p); 1790c00ce490Sdrh Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); 1791c00ce490Sdrh return TCL_OK; 1792c00ce490Sdrh } 1793c00ce490Sdrh 1794c00ce490Sdrh /* 1795c00ce490Sdrh ** tclcmd: sqlite3_quota_file_truesize HANDLE 1796c00ce490Sdrh */ 1797c00ce490Sdrh static int test_quota_file_truesize( 1798c00ce490Sdrh void * clientData, 1799c00ce490Sdrh Tcl_Interp *interp, 1800c00ce490Sdrh int objc, 1801c00ce490Sdrh Tcl_Obj *CONST objv[] 1802c00ce490Sdrh ){ 1803c00ce490Sdrh quota_FILE *p; 1804c00ce490Sdrh sqlite3_int64 x; 1805c00ce490Sdrh if( objc!=2 ){ 1806c00ce490Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1807c00ce490Sdrh return TCL_ERROR; 1808c00ce490Sdrh } 1809c00ce490Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1810c00ce490Sdrh x = sqlite3_quota_file_truesize(p); 1811c00ce490Sdrh Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); 1812c00ce490Sdrh return TCL_OK; 1813c00ce490Sdrh } 1814c00ce490Sdrh 1815c00ce490Sdrh /* 1816c00ce490Sdrh ** tclcmd: sqlite3_quota_file_mtime HANDLE 1817c00ce490Sdrh */ 1818c00ce490Sdrh static int test_quota_file_mtime( 1819c00ce490Sdrh void * clientData, 1820c00ce490Sdrh Tcl_Interp *interp, 1821c00ce490Sdrh int objc, 1822c00ce490Sdrh Tcl_Obj *CONST objv[] 1823c00ce490Sdrh ){ 1824c00ce490Sdrh quota_FILE *p; 1825c00ce490Sdrh time_t t; 1826c00ce490Sdrh if( objc!=2 ){ 1827c00ce490Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1828c00ce490Sdrh return TCL_ERROR; 1829c00ce490Sdrh } 1830c00ce490Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1831c00ce490Sdrh t = 0; 1832c00ce490Sdrh sqlite3_quota_file_mtime(p, &t); 1833c00ce490Sdrh Tcl_SetObjResult(interp, Tcl_NewWideIntObj(t)); 1834c00ce490Sdrh return TCL_OK; 1835c00ce490Sdrh } 1836c00ce490Sdrh 1837c00ce490Sdrh 1838c00ce490Sdrh /* 1839a76e8589Sdrh ** tclcmd: sqlite3_quota_remove FILENAME 1840a76e8589Sdrh */ 1841a76e8589Sdrh static int test_quota_remove( 1842a76e8589Sdrh void * clientData, 1843a76e8589Sdrh Tcl_Interp *interp, 1844a76e8589Sdrh int objc, 1845a76e8589Sdrh Tcl_Obj *CONST objv[] 1846a76e8589Sdrh ){ 1847a76e8589Sdrh const char *zFilename; /* File pattern to configure */ 1848a76e8589Sdrh int rc; 1849a76e8589Sdrh if( objc!=2 ){ 1850a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "FILENAME"); 1851a76e8589Sdrh return TCL_ERROR; 1852a76e8589Sdrh } 1853a76e8589Sdrh zFilename = Tcl_GetString(objv[1]); 1854a76e8589Sdrh rc = sqlite3_quota_remove(zFilename); 1855a76e8589Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 1856a76e8589Sdrh return TCL_OK; 1857a76e8589Sdrh } 1858a76e8589Sdrh 1859a76e8589Sdrh /* 1860c8ccda60Sdrh ** tclcmd: sqlite3_quota_glob PATTERN TEXT 1861c8ccda60Sdrh ** 1862c8ccda60Sdrh ** Test the glob pattern matching. Return 1 if TEXT matches PATTERN 1863c8ccda60Sdrh ** and return 0 if it does not. 1864c8ccda60Sdrh */ 1865c8ccda60Sdrh static int test_quota_glob( 1866c8ccda60Sdrh void * clientData, 1867c8ccda60Sdrh Tcl_Interp *interp, 1868c8ccda60Sdrh int objc, 1869c8ccda60Sdrh Tcl_Obj *CONST objv[] 1870c8ccda60Sdrh ){ 1871c8ccda60Sdrh const char *zPattern; /* The glob pattern */ 1872c8ccda60Sdrh const char *zText; /* Text to compare agains the pattern */ 1873c8ccda60Sdrh int rc; 1874c8ccda60Sdrh if( objc!=3 ){ 1875c8ccda60Sdrh Tcl_WrongNumArgs(interp, 1, objv, "PATTERN TEXT"); 1876c8ccda60Sdrh return TCL_ERROR; 1877c8ccda60Sdrh } 1878c8ccda60Sdrh zPattern = Tcl_GetString(objv[1]); 1879c8ccda60Sdrh zText = Tcl_GetString(objv[2]); 1880c8ccda60Sdrh rc = quotaStrglob(zPattern, zText); 1881c8ccda60Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 1882c8ccda60Sdrh return TCL_OK; 1883c8ccda60Sdrh } 1884c8ccda60Sdrh 1885c8ccda60Sdrh /* 188698c78ea6Sdrh ** tclcmd: sqlite3_quota_file_available HANDLE 188798c78ea6Sdrh ** 188898c78ea6Sdrh ** Return the number of bytes from the current file point to the end of 188998c78ea6Sdrh ** the file. 189098c78ea6Sdrh */ 189198c78ea6Sdrh static int test_quota_file_available( 189298c78ea6Sdrh void * clientData, 189398c78ea6Sdrh Tcl_Interp *interp, 189498c78ea6Sdrh int objc, 189598c78ea6Sdrh Tcl_Obj *CONST objv[] 189698c78ea6Sdrh ){ 189798c78ea6Sdrh quota_FILE *p; 189898c78ea6Sdrh sqlite3_int64 x; 189998c78ea6Sdrh if( objc!=2 ){ 190098c78ea6Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 190198c78ea6Sdrh return TCL_ERROR; 190298c78ea6Sdrh } 190398c78ea6Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 190498c78ea6Sdrh x = sqlite3_quota_file_available(p); 190598c78ea6Sdrh Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); 190698c78ea6Sdrh return TCL_OK; 190798c78ea6Sdrh } 190898c78ea6Sdrh 190998c78ea6Sdrh /* 191098c78ea6Sdrh ** tclcmd: sqlite3_quota_ferror HANDLE 191198c78ea6Sdrh ** 191298c78ea6Sdrh ** Return true if the file handle is in the error state. 191398c78ea6Sdrh */ 191498c78ea6Sdrh static int test_quota_ferror( 191598c78ea6Sdrh void * clientData, 191698c78ea6Sdrh Tcl_Interp *interp, 191798c78ea6Sdrh int objc, 191898c78ea6Sdrh Tcl_Obj *CONST objv[] 191998c78ea6Sdrh ){ 192098c78ea6Sdrh quota_FILE *p; 192198c78ea6Sdrh int x; 192298c78ea6Sdrh if( objc!=2 ){ 192398c78ea6Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 192498c78ea6Sdrh return TCL_ERROR; 192598c78ea6Sdrh } 192698c78ea6Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 192798c78ea6Sdrh x = sqlite3_quota_ferror(p); 192898c78ea6Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(x)); 192998c78ea6Sdrh return TCL_OK; 193098c78ea6Sdrh } 193198c78ea6Sdrh 193298c78ea6Sdrh /* 1933748a52cdSdrh ** This routine registers the custom TCL commands defined in this 1934748a52cdSdrh ** module. This should be the only procedure visible from outside 1935748a52cdSdrh ** of this module. 1936748a52cdSdrh */ 1937748a52cdSdrh int Sqlitequota_Init(Tcl_Interp *interp){ 19388cf35eb4Sdan static struct { 19398cf35eb4Sdan char *zName; 19408cf35eb4Sdan Tcl_ObjCmdProc *xProc; 19418cf35eb4Sdan } aCmd[] = { 19428cf35eb4Sdan { "sqlite3_quota_initialize", test_quota_initialize }, 19438cf35eb4Sdan { "sqlite3_quota_shutdown", test_quota_shutdown }, 19448cf35eb4Sdan { "sqlite3_quota_set", test_quota_set }, 1945b6020c4eSdrh { "sqlite3_quota_file", test_quota_file }, 1946ef9659baSdrh { "sqlite3_quota_dump", test_quota_dump }, 1947a76e8589Sdrh { "sqlite3_quota_fopen", test_quota_fopen }, 1948a76e8589Sdrh { "sqlite3_quota_fread", test_quota_fread }, 1949a76e8589Sdrh { "sqlite3_quota_fwrite", test_quota_fwrite }, 1950a76e8589Sdrh { "sqlite3_quota_fclose", test_quota_fclose }, 195169b2232dSdrh { "sqlite3_quota_fflush", test_quota_fflush }, 1952a76e8589Sdrh { "sqlite3_quota_fseek", test_quota_fseek }, 1953a76e8589Sdrh { "sqlite3_quota_rewind", test_quota_rewind }, 1954a76e8589Sdrh { "sqlite3_quota_ftell", test_quota_ftell }, 1955c00ce490Sdrh { "sqlite3_quota_ftruncate", test_quota_ftruncate }, 1956c00ce490Sdrh { "sqlite3_quota_file_size", test_quota_file_size }, 1957c00ce490Sdrh { "sqlite3_quota_file_truesize", test_quota_file_truesize }, 1958c00ce490Sdrh { "sqlite3_quota_file_mtime", test_quota_file_mtime }, 1959a76e8589Sdrh { "sqlite3_quota_remove", test_quota_remove }, 1960c8ccda60Sdrh { "sqlite3_quota_glob", test_quota_glob }, 196198c78ea6Sdrh { "sqlite3_quota_file_available",test_quota_file_available }, 196298c78ea6Sdrh { "sqlite3_quota_ferror", test_quota_ferror }, 19638cf35eb4Sdan }; 19648cf35eb4Sdan int i; 19658cf35eb4Sdan 19668cf35eb4Sdan for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){ 19678cf35eb4Sdan Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0); 19688cf35eb4Sdan } 19698cf35eb4Sdan 1970748a52cdSdrh return TCL_OK; 1971748a52cdSdrh } 1972748a52cdSdrh #endif 1973