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 4894e858b1Sdrh /* 4994e858b1Sdrh ** Figure out if we are dealing with Unix, Windows, or some other 5094e858b1Sdrh ** operating system. After the following block of preprocess macros, 5194e858b1Sdrh ** all of SQLITE_OS_UNIX, SQLITE_OS_WIN, SQLITE_OS_OS2, and SQLITE_OS_OTHER 5294e858b1Sdrh ** will defined to either 1 or 0. One of the four will be 1. The other 5394e858b1Sdrh ** three will be 0. 5494e858b1Sdrh */ 5594e858b1Sdrh #if defined(SQLITE_OS_OTHER) 5694e858b1Sdrh # if SQLITE_OS_OTHER==1 5794e858b1Sdrh # undef SQLITE_OS_UNIX 5894e858b1Sdrh # define SQLITE_OS_UNIX 0 5994e858b1Sdrh # undef SQLITE_OS_WIN 6094e858b1Sdrh # define SQLITE_OS_WIN 0 6194e858b1Sdrh # undef SQLITE_OS_OS2 6294e858b1Sdrh # define SQLITE_OS_OS2 0 6394e858b1Sdrh # else 6494e858b1Sdrh # undef SQLITE_OS_OTHER 6594e858b1Sdrh # endif 6694e858b1Sdrh #endif 6794e858b1Sdrh #if !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_OTHER) 6894e858b1Sdrh # define SQLITE_OS_OTHER 0 6994e858b1Sdrh # ifndef SQLITE_OS_WIN 7094e858b1Sdrh # if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) \ 7194e858b1Sdrh || defined(__MINGW32__) || defined(__BORLANDC__) 7294e858b1Sdrh # define SQLITE_OS_WIN 1 7394e858b1Sdrh # define SQLITE_OS_UNIX 0 7494e858b1Sdrh # define SQLITE_OS_OS2 0 7594e858b1Sdrh # elif defined(__EMX__) || defined(_OS2) || defined(OS2) \ 7694e858b1Sdrh || defined(_OS2_) || defined(__OS2__) 7794e858b1Sdrh # define SQLITE_OS_WIN 0 7894e858b1Sdrh # define SQLITE_OS_UNIX 0 7994e858b1Sdrh # define SQLITE_OS_OS2 1 8094e858b1Sdrh # else 8194e858b1Sdrh # define SQLITE_OS_WIN 0 8294e858b1Sdrh # define SQLITE_OS_UNIX 1 8394e858b1Sdrh # define SQLITE_OS_OS2 0 8494e858b1Sdrh # endif 8594e858b1Sdrh # else 8694e858b1Sdrh # define SQLITE_OS_UNIX 0 8794e858b1Sdrh # define SQLITE_OS_OS2 0 8894e858b1Sdrh # endif 8994e858b1Sdrh #else 9094e858b1Sdrh # ifndef SQLITE_OS_WIN 9194e858b1Sdrh # define SQLITE_OS_WIN 0 9294e858b1Sdrh # endif 9394e858b1Sdrh #endif 9494e858b1Sdrh 9594e858b1Sdrh #if SQLITE_OS_UNIX 9694e858b1Sdrh # include <unistd.h> 9794e858b1Sdrh #endif 9894e858b1Sdrh #if SQLITE_OS_WIN 9994e858b1Sdrh # include <windows.h> 10094e858b1Sdrh # include <io.h> 10194e858b1Sdrh #endif 10294e858b1Sdrh 10394e858b1Sdrh 104748a52cdSdrh /************************ Object Definitions ******************************/ 105748a52cdSdrh 106745de2e4Sdrh /* Forward declaration of all object types */ 107748a52cdSdrh typedef struct quotaGroup quotaGroup; 108ef9659baSdrh typedef struct quotaConn quotaConn; 109745de2e4Sdrh typedef struct quotaFile quotaFile; 110745de2e4Sdrh 1118cf35eb4Sdan /* 112549dc1baSdrh ** A "quota group" is a collection of files whose collective size we want 113549dc1baSdrh ** to limit. Each quota group is defined by a GLOB pattern. 1148cf35eb4Sdan ** 115549dc1baSdrh ** There is an instance of the following object for each defined quota 116549dc1baSdrh ** group. This object records the GLOB pattern that defines which files 117549dc1baSdrh ** belong to the quota group. The object also remembers the size limit 118549dc1baSdrh ** for the group (the quota) and the callback to be invoked when the 119549dc1baSdrh ** sum of the sizes of the files within the group goes over the limit. 1208cf35eb4Sdan ** 121549dc1baSdrh ** A quota group must be established (using sqlite3_quota_set(...)) 122549dc1baSdrh ** prior to opening any of the database connections that access files 123549dc1baSdrh ** within the quota group. 1248cf35eb4Sdan */ 125748a52cdSdrh struct quotaGroup { 126748a52cdSdrh const char *zPattern; /* Filename pattern to be quotaed */ 127748a52cdSdrh sqlite3_int64 iLimit; /* Upper bound on total file size */ 128748a52cdSdrh sqlite3_int64 iSize; /* Current size of all files */ 129748a52cdSdrh void (*xCallback)( /* Callback invoked when going over quota */ 130748a52cdSdrh const char *zFilename, /* Name of file whose size increases */ 131748a52cdSdrh sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ 132748a52cdSdrh sqlite3_int64 iSize, /* Total size of all files in the group */ 133748a52cdSdrh void *pArg /* Client data */ 134748a52cdSdrh ); 135748a52cdSdrh void *pArg; /* Third argument to the xCallback() */ 136ef9659baSdrh void (*xDestroy)(void*); /* Optional destructor for pArg */ 137748a52cdSdrh quotaGroup *pNext, **ppPrev; /* Doubly linked list of all quota objects */ 13875c19174Sdrh quotaFile *pFiles; /* Files within this group */ 139748a52cdSdrh }; 140748a52cdSdrh 141748a52cdSdrh /* 142745de2e4Sdrh ** An instance of this structure represents a single file that is part 143745de2e4Sdrh ** of a quota group. A single file can be opened multiple times. In 144745de2e4Sdrh ** order keep multiple openings of the same file from causing the size 145549dc1baSdrh ** of the file to count against the quota multiple times, each file 146745de2e4Sdrh ** has a unique instance of this object and multiple open connections 147745de2e4Sdrh ** to the same file each point to a single instance of this object. 148748a52cdSdrh */ 149748a52cdSdrh struct quotaFile { 150745de2e4Sdrh char *zFilename; /* Name of this file */ 15175c19174Sdrh quotaGroup *pGroup; /* Quota group to which this file belongs */ 1528cf35eb4Sdan sqlite3_int64 iSize; /* Current size of this file */ 153745de2e4Sdrh int nRef; /* Number of times this file is open */ 154796af156Sdrh int deleteOnClose; /* True to delete this file when it closes */ 155745de2e4Sdrh quotaFile *pNext, **ppPrev; /* Linked list of files in the same group */ 156745de2e4Sdrh }; 157745de2e4Sdrh 158745de2e4Sdrh /* 159745de2e4Sdrh ** An instance of the following object represents each open connection 160549dc1baSdrh ** to a file that participates in quota tracking. This object is a 161549dc1baSdrh ** subclass of sqlite3_file. The sqlite3_file object for the underlying 162549dc1baSdrh ** VFS is appended to this structure. 163745de2e4Sdrh */ 164ef9659baSdrh struct quotaConn { 165748a52cdSdrh sqlite3_file base; /* Base class - must be first */ 166745de2e4Sdrh quotaFile *pFile; /* The underlying file */ 167748a52cdSdrh /* The underlying VFS sqlite3_file is appended to this object */ 168748a52cdSdrh }; 169748a52cdSdrh 17060f21e4bSdrh /* 17160f21e4bSdrh ** An instance of the following object records the state of an 17260f21e4bSdrh ** open file. This object is opaque to all users - the internal 17360f21e4bSdrh ** structure is only visible to the functions below. 17460f21e4bSdrh */ 17560f21e4bSdrh struct quota_FILE { 17660f21e4bSdrh FILE *f; /* Open stdio file pointer */ 17760f21e4bSdrh sqlite3_int64 iOfst; /* Current offset into the file */ 17860f21e4bSdrh quotaFile *pFile; /* The file record in the quota system */ 179c00ce490Sdrh #if SQLITE_OS_WIN 180c00ce490Sdrh char *zMbcsName; /* Full MBCS pathname of the file */ 181c00ce490Sdrh #endif 18260f21e4bSdrh }; 18360f21e4bSdrh 18460f21e4bSdrh 185748a52cdSdrh /************************* Global Variables **********************************/ 186748a52cdSdrh /* 187748a52cdSdrh ** All global variables used by this file are containing within the following 188748a52cdSdrh ** gQuota structure. 189748a52cdSdrh */ 190748a52cdSdrh static struct { 191549dc1baSdrh /* The pOrigVfs is the real, original underlying VFS implementation. 192748a52cdSdrh ** Most operations pass-through to the real VFS. This value is read-only 193748a52cdSdrh ** during operation. It is only modified at start-time and thus does not 194748a52cdSdrh ** require a mutex. 195748a52cdSdrh */ 196748a52cdSdrh sqlite3_vfs *pOrigVfs; 197748a52cdSdrh 198748a52cdSdrh /* The sThisVfs is the VFS structure used by this shim. It is initialized 199748a52cdSdrh ** at start-time and thus does not require a mutex 200748a52cdSdrh */ 201748a52cdSdrh sqlite3_vfs sThisVfs; 202748a52cdSdrh 203748a52cdSdrh /* The sIoMethods defines the methods used by sqlite3_file objects 204748a52cdSdrh ** associated with this shim. It is initialized at start-time and does 205748a52cdSdrh ** not require a mutex. 206748a52cdSdrh ** 207748a52cdSdrh ** When the underlying VFS is called to open a file, it might return 208748a52cdSdrh ** either a version 1 or a version 2 sqlite3_file object. This shim 209748a52cdSdrh ** has to create a wrapper sqlite3_file of the same version. Hence 210748a52cdSdrh ** there are two I/O method structures, one for version 1 and the other 211748a52cdSdrh ** for version 2. 212748a52cdSdrh */ 213748a52cdSdrh sqlite3_io_methods sIoMethodsV1; 214748a52cdSdrh sqlite3_io_methods sIoMethodsV2; 215748a52cdSdrh 216748a52cdSdrh /* True when this shim as been initialized. 217748a52cdSdrh */ 218748a52cdSdrh int isInitialized; 219748a52cdSdrh 220748a52cdSdrh /* For run-time access any of the other global data structures in this 221748a52cdSdrh ** shim, the following mutex must be held. 222748a52cdSdrh */ 223748a52cdSdrh sqlite3_mutex *pMutex; 224748a52cdSdrh 225748a52cdSdrh /* List of quotaGroup objects. 226748a52cdSdrh */ 227748a52cdSdrh quotaGroup *pGroup; 228748a52cdSdrh 229748a52cdSdrh } gQuota; 230748a52cdSdrh 231748a52cdSdrh /************************* Utility Routines *********************************/ 232748a52cdSdrh /* 233748a52cdSdrh ** Acquire and release the mutex used to serialize access to the 234748a52cdSdrh ** list of quotaGroups. 235748a52cdSdrh */ 236748a52cdSdrh static void quotaEnter(void){ sqlite3_mutex_enter(gQuota.pMutex); } 237748a52cdSdrh static void quotaLeave(void){ sqlite3_mutex_leave(gQuota.pMutex); } 238748a52cdSdrh 239796af156Sdrh /* Count the number of open files in a quotaGroup 240796af156Sdrh */ 241796af156Sdrh static int quotaGroupOpenFileCount(quotaGroup *pGroup){ 242796af156Sdrh int N = 0; 243796af156Sdrh quotaFile *pFile = pGroup->pFiles; 244796af156Sdrh while( pFile ){ 245796af156Sdrh if( pFile->nRef ) N++; 246796af156Sdrh pFile = pFile->pNext; 247796af156Sdrh } 248796af156Sdrh return N; 249796af156Sdrh } 250796af156Sdrh 251796af156Sdrh /* Remove a file from a quota group. 252796af156Sdrh */ 253796af156Sdrh static void quotaRemoveFile(quotaFile *pFile){ 254796af156Sdrh quotaGroup *pGroup = pFile->pGroup; 255796af156Sdrh pGroup->iSize -= pFile->iSize; 256796af156Sdrh *pFile->ppPrev = pFile->pNext; 257796af156Sdrh if( pFile->pNext ) pFile->pNext->ppPrev = pFile->ppPrev; 258796af156Sdrh sqlite3_free(pFile); 259796af156Sdrh } 260796af156Sdrh 261796af156Sdrh /* Remove all files from a quota group. It is always the case that 262796af156Sdrh ** all files will be closed when this routine is called. 263796af156Sdrh */ 264796af156Sdrh static void quotaRemoveAllFiles(quotaGroup *pGroup){ 265796af156Sdrh while( pGroup->pFiles ){ 266796af156Sdrh assert( pGroup->pFiles->nRef==0 ); 267796af156Sdrh quotaRemoveFile(pGroup->pFiles); 268796af156Sdrh } 269796af156Sdrh } 270796af156Sdrh 271748a52cdSdrh 272748a52cdSdrh /* If the reference count and threshold for a quotaGroup are both 273748a52cdSdrh ** zero, then destroy the quotaGroup. 274748a52cdSdrh */ 27575c19174Sdrh static void quotaGroupDeref(quotaGroup *pGroup){ 276796af156Sdrh if( pGroup->iLimit==0 && quotaGroupOpenFileCount(pGroup)==0 ){ 277796af156Sdrh quotaRemoveAllFiles(pGroup); 2789fc7702cSdan *pGroup->ppPrev = pGroup->pNext; 27975c19174Sdrh if( pGroup->pNext ) pGroup->pNext->ppPrev = pGroup->ppPrev; 28075c19174Sdrh if( pGroup->xDestroy ) pGroup->xDestroy(pGroup->pArg); 28175c19174Sdrh sqlite3_free(pGroup); 282748a52cdSdrh } 283748a52cdSdrh } 284748a52cdSdrh 285748a52cdSdrh /* 286748a52cdSdrh ** Return TRUE if string z matches glob pattern zGlob. 287748a52cdSdrh ** 288748a52cdSdrh ** Globbing rules: 289748a52cdSdrh ** 290748a52cdSdrh ** '*' Matches any sequence of zero or more characters. 291748a52cdSdrh ** 292748a52cdSdrh ** '?' Matches exactly one character. 293748a52cdSdrh ** 294748a52cdSdrh ** [...] Matches one character from the enclosed list of 295748a52cdSdrh ** characters. 296748a52cdSdrh ** 297748a52cdSdrh ** [^...] Matches one character not in the enclosed list. 298748a52cdSdrh ** 299a76e8589Sdrh ** / Matches "/" or "\\" 300a76e8589Sdrh ** 301748a52cdSdrh */ 30261cc423aSdrh static int quotaStrglob(const char *zGlob, const char *z){ 303c8ccda60Sdrh int c, c2, cx; 304748a52cdSdrh int invert; 305748a52cdSdrh int seen; 306748a52cdSdrh 307748a52cdSdrh while( (c = (*(zGlob++)))!=0 ){ 308748a52cdSdrh if( c=='*' ){ 309748a52cdSdrh while( (c=(*(zGlob++))) == '*' || c=='?' ){ 310748a52cdSdrh if( c=='?' && (*(z++))==0 ) return 0; 311748a52cdSdrh } 312748a52cdSdrh if( c==0 ){ 313748a52cdSdrh return 1; 314748a52cdSdrh }else if( c=='[' ){ 31561cc423aSdrh while( *z && quotaStrglob(zGlob-1,z)==0 ){ 316748a52cdSdrh z++; 317748a52cdSdrh } 318748a52cdSdrh return (*z)!=0; 319748a52cdSdrh } 320c8ccda60Sdrh cx = (c=='/') ? '\\' : c; 321748a52cdSdrh while( (c2 = (*(z++)))!=0 ){ 322c8ccda60Sdrh while( c2!=c && c2!=cx ){ 323748a52cdSdrh c2 = *(z++); 324748a52cdSdrh if( c2==0 ) return 0; 325748a52cdSdrh } 32661cc423aSdrh if( quotaStrglob(zGlob,z) ) return 1; 327748a52cdSdrh } 328748a52cdSdrh return 0; 329748a52cdSdrh }else if( c=='?' ){ 330748a52cdSdrh if( (*(z++))==0 ) return 0; 331748a52cdSdrh }else if( c=='[' ){ 332748a52cdSdrh int prior_c = 0; 333748a52cdSdrh seen = 0; 334748a52cdSdrh invert = 0; 335748a52cdSdrh c = *(z++); 336748a52cdSdrh if( c==0 ) return 0; 337748a52cdSdrh c2 = *(zGlob++); 338748a52cdSdrh if( c2=='^' ){ 339748a52cdSdrh invert = 1; 340748a52cdSdrh c2 = *(zGlob++); 341748a52cdSdrh } 342748a52cdSdrh if( c2==']' ){ 343748a52cdSdrh if( c==']' ) seen = 1; 344748a52cdSdrh c2 = *(zGlob++); 345748a52cdSdrh } 346748a52cdSdrh while( c2 && c2!=']' ){ 347748a52cdSdrh if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){ 348748a52cdSdrh c2 = *(zGlob++); 349748a52cdSdrh if( c>=prior_c && c<=c2 ) seen = 1; 350748a52cdSdrh prior_c = 0; 351748a52cdSdrh }else{ 352748a52cdSdrh if( c==c2 ){ 353748a52cdSdrh seen = 1; 354748a52cdSdrh } 355748a52cdSdrh prior_c = c2; 356748a52cdSdrh } 357748a52cdSdrh c2 = *(zGlob++); 358748a52cdSdrh } 359748a52cdSdrh if( c2==0 || (seen ^ invert)==0 ) return 0; 360a76e8589Sdrh }else if( c=='/' ){ 361a76e8589Sdrh if( z[0]!='/' && z[0]!='\\' ) return 0; 362a76e8589Sdrh z++; 363748a52cdSdrh }else{ 364748a52cdSdrh if( c!=(*(z++)) ) return 0; 365748a52cdSdrh } 366748a52cdSdrh } 367748a52cdSdrh return *z==0; 368748a52cdSdrh } 369748a52cdSdrh 370748a52cdSdrh 371748a52cdSdrh /* Find a quotaGroup given the filename. 3728cf35eb4Sdan ** 373ef9659baSdrh ** Return a pointer to the quotaGroup object. Return NULL if not found. 374748a52cdSdrh */ 375748a52cdSdrh static quotaGroup *quotaGroupFind(const char *zFilename){ 376748a52cdSdrh quotaGroup *p; 37761cc423aSdrh for(p=gQuota.pGroup; p && quotaStrglob(p->zPattern, zFilename)==0; 37861cc423aSdrh p=p->pNext){} 379748a52cdSdrh return p; 380748a52cdSdrh } 381748a52cdSdrh 382ef9659baSdrh /* Translate an sqlite3_file* that is really a quotaConn* into 383549dc1baSdrh ** the sqlite3_file* for the underlying original VFS. 384748a52cdSdrh */ 385ef9659baSdrh static sqlite3_file *quotaSubOpen(sqlite3_file *pConn){ 386ef9659baSdrh quotaConn *p = (quotaConn*)pConn; 387748a52cdSdrh return (sqlite3_file*)&p[1]; 388748a52cdSdrh } 389748a52cdSdrh 390796af156Sdrh /* Find a file in a quota group and return a pointer to that file. 391796af156Sdrh ** Return NULL if the file is not in the group. 392796af156Sdrh */ 39360f21e4bSdrh static quotaFile *quotaFindFile( 39460f21e4bSdrh quotaGroup *pGroup, /* Group in which to look for the file */ 39560f21e4bSdrh const char *zName, /* Full pathname of the file */ 39660f21e4bSdrh int createFlag /* Try to create the file if not found */ 39760f21e4bSdrh ){ 398796af156Sdrh quotaFile *pFile = pGroup->pFiles; 399796af156Sdrh while( pFile && strcmp(pFile->zFilename, zName)!=0 ){ 400796af156Sdrh pFile = pFile->pNext; 401796af156Sdrh } 40260f21e4bSdrh if( pFile==0 && createFlag ){ 40383cc1392Sdrh int nName = (int)(strlen(zName) & 0x3fffffff); 40460f21e4bSdrh pFile = (quotaFile *)sqlite3_malloc( sizeof(*pFile) + nName + 1 ); 40560f21e4bSdrh if( pFile ){ 40660f21e4bSdrh memset(pFile, 0, sizeof(*pFile)); 40760f21e4bSdrh pFile->zFilename = (char*)&pFile[1]; 40860f21e4bSdrh memcpy(pFile->zFilename, zName, nName+1); 40960f21e4bSdrh pFile->pNext = pGroup->pFiles; 41060f21e4bSdrh if( pGroup->pFiles ) pGroup->pFiles->ppPrev = &pFile->pNext; 41160f21e4bSdrh pFile->ppPrev = &pGroup->pFiles; 41260f21e4bSdrh pGroup->pFiles = pFile; 41360f21e4bSdrh pFile->pGroup = pGroup; 41460f21e4bSdrh } 41560f21e4bSdrh } 416796af156Sdrh return pFile; 417796af156Sdrh } 418a76e8589Sdrh /* 419a76e8589Sdrh ** Translate UTF8 to MBCS for use in fopen() calls. Return a pointer to the 420a76e8589Sdrh ** translated text.. Call quota_mbcs_free() to deallocate any memory 421a76e8589Sdrh ** used to store the returned pointer when done. 422a76e8589Sdrh */ 423a76e8589Sdrh static char *quota_utf8_to_mbcs(const char *zUtf8){ 424a76e8589Sdrh #if SQLITE_OS_WIN 42583cc1392Sdrh size_t n; /* Bytes in zUtf8 */ 42627cec37dSdrh int nWide; /* number of UTF-16 characters */ 42727cec37dSdrh int nMbcs; /* Bytes of MBCS */ 42827cec37dSdrh LPWSTR zTmpWide; /* The UTF16 text */ 42927cec37dSdrh char *zMbcs; /* The MBCS text */ 43027cec37dSdrh int codepage; /* Code page used by fopen() */ 43127cec37dSdrh 43227cec37dSdrh n = strlen(zUtf8); 43327cec37dSdrh nWide = MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, NULL, 0); 43449ed5c85Sdrh if( nWide==0 ) return 0; 435c39f3e00Sdrh zTmpWide = (LPWSTR)sqlite3_malloc( (nWide+1)*sizeof(zTmpWide[0]) ); 43627cec37dSdrh if( zTmpWide==0 ) return 0; 43727cec37dSdrh MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zTmpWide, nWide); 43827cec37dSdrh codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP; 43927cec37dSdrh nMbcs = WideCharToMultiByte(codepage, 0, zTmpWide, nWide, 0, 0, 0, 0); 440c39f3e00Sdrh zMbcs = nMbcs ? (char*)sqlite3_malloc( nMbcs+1 ) : 0; 44127cec37dSdrh if( zMbcs ){ 44227cec37dSdrh WideCharToMultiByte(codepage, 0, zTmpWide, nWide, zMbcs, nMbcs, 0, 0); 44327cec37dSdrh } 44427cec37dSdrh sqlite3_free(zTmpWide); 44527cec37dSdrh return zMbcs; 446a76e8589Sdrh #else 447a76e8589Sdrh return (char*)zUtf8; /* No-op on unix */ 448a76e8589Sdrh #endif 449a76e8589Sdrh } 450a76e8589Sdrh 451a76e8589Sdrh /* 452a76e8589Sdrh ** Deallocate any memory allocated by quota_utf8_to_mbcs(). 453a76e8589Sdrh */ 454a76e8589Sdrh static void quota_mbcs_free(char *zOld){ 455a76e8589Sdrh #if SQLITE_OS_WIN 456c8ccda60Sdrh sqlite3_free(zOld); 457a76e8589Sdrh #else 458a76e8589Sdrh /* No-op on unix */ 459a76e8589Sdrh #endif 460a76e8589Sdrh } 461a76e8589Sdrh 462748a52cdSdrh /************************* VFS Method Wrappers *****************************/ 463748a52cdSdrh /* 464748a52cdSdrh ** This is the xOpen method used for the "quota" VFS. 465748a52cdSdrh ** 466748a52cdSdrh ** Most of the work is done by the underlying original VFS. This method 467549dc1baSdrh ** simply links the new file into the appropriate quota group if it is a 468549dc1baSdrh ** file that needs to be tracked. 469748a52cdSdrh */ 470748a52cdSdrh static int quotaOpen( 471549dc1baSdrh sqlite3_vfs *pVfs, /* The quota VFS */ 472549dc1baSdrh const char *zName, /* Name of file to be opened */ 473ef9659baSdrh sqlite3_file *pConn, /* Fill in this file descriptor */ 474549dc1baSdrh int flags, /* Flags to control the opening */ 475549dc1baSdrh int *pOutFlags /* Flags showing results of opening */ 476748a52cdSdrh ){ 477549dc1baSdrh int rc; /* Result code */ 478ef9659baSdrh quotaConn *pQuotaOpen; /* The new quota file descriptor */ 479549dc1baSdrh quotaFile *pFile; /* Corresponding quotaFile obj */ 480549dc1baSdrh quotaGroup *pGroup; /* The group file belongs to */ 481549dc1baSdrh sqlite3_file *pSubOpen; /* Real file descriptor */ 482549dc1baSdrh sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs; /* Real VFS */ 483748a52cdSdrh 484748a52cdSdrh /* If the file is not a main database file or a WAL, then use the 485748a52cdSdrh ** normal xOpen method. 486748a52cdSdrh */ 4878cf35eb4Sdan if( (flags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_WAL))==0 ){ 488ef9659baSdrh return pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags); 489748a52cdSdrh } 490748a52cdSdrh 491748a52cdSdrh /* If the name of the file does not match any quota group, then 492748a52cdSdrh ** use the normal xOpen method. 493748a52cdSdrh */ 494748a52cdSdrh quotaEnter(); 4958cf35eb4Sdan pGroup = quotaGroupFind(zName); 496748a52cdSdrh if( pGroup==0 ){ 497ef9659baSdrh rc = pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags); 498748a52cdSdrh }else{ 4998cf35eb4Sdan /* If we get to this point, it means the file needs to be quota tracked. 5008cf35eb4Sdan */ 501ef9659baSdrh pQuotaOpen = (quotaConn*)pConn; 502ef9659baSdrh pSubOpen = quotaSubOpen(pConn); 503745de2e4Sdrh rc = pOrigVfs->xOpen(pOrigVfs, zName, pSubOpen, flags, pOutFlags); 5048cf35eb4Sdan if( rc==SQLITE_OK ){ 50560f21e4bSdrh pFile = quotaFindFile(pGroup, zName, 1); 506745de2e4Sdrh if( pFile==0 ){ 507745de2e4Sdrh quotaLeave(); 508745de2e4Sdrh pSubOpen->pMethods->xClose(pSubOpen); 509745de2e4Sdrh return SQLITE_NOMEM; 510745de2e4Sdrh } 511796af156Sdrh pFile->deleteOnClose = (flags & SQLITE_OPEN_DELETEONCLOSE)!=0; 512745de2e4Sdrh pFile->nRef++; 513745de2e4Sdrh pQuotaOpen->pFile = pFile; 514745de2e4Sdrh if( pSubOpen->pMethods->iVersion==1 ){ 515745de2e4Sdrh pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV1; 5168cf35eb4Sdan }else{ 517745de2e4Sdrh pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV2; 518748a52cdSdrh } 519748a52cdSdrh } 5208cf35eb4Sdan } 5218cf35eb4Sdan quotaLeave(); 522748a52cdSdrh return rc; 523748a52cdSdrh } 524748a52cdSdrh 525796af156Sdrh /* 526796af156Sdrh ** This is the xDelete method used for the "quota" VFS. 527796af156Sdrh ** 528796af156Sdrh ** If the file being deleted is part of the quota group, then reduce 529796af156Sdrh ** the size of the quota group accordingly. And remove the file from 530796af156Sdrh ** the set of files in the quota group. 531796af156Sdrh */ 532796af156Sdrh static int quotaDelete( 533796af156Sdrh sqlite3_vfs *pVfs, /* The quota VFS */ 534796af156Sdrh const char *zName, /* Name of file to be deleted */ 535796af156Sdrh int syncDir /* Do a directory sync after deleting */ 536796af156Sdrh ){ 537796af156Sdrh int rc; /* Result code */ 538796af156Sdrh quotaFile *pFile; /* Files in the quota */ 539796af156Sdrh quotaGroup *pGroup; /* The group file belongs to */ 540796af156Sdrh sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs; /* Real VFS */ 541796af156Sdrh 542796af156Sdrh /* Do the actual file delete */ 543796af156Sdrh rc = pOrigVfs->xDelete(pOrigVfs, zName, syncDir); 544796af156Sdrh 545796af156Sdrh /* If the file just deleted is a member of a quota group, then remove 546796af156Sdrh ** it from that quota group. 547796af156Sdrh */ 548796af156Sdrh if( rc==SQLITE_OK ){ 549796af156Sdrh quotaEnter(); 550796af156Sdrh pGroup = quotaGroupFind(zName); 551796af156Sdrh if( pGroup ){ 55260f21e4bSdrh pFile = quotaFindFile(pGroup, zName, 0); 553796af156Sdrh if( pFile ){ 554796af156Sdrh if( pFile->nRef ){ 555796af156Sdrh pFile->deleteOnClose = 1; 556796af156Sdrh }else{ 557796af156Sdrh quotaRemoveFile(pFile); 558796af156Sdrh quotaGroupDeref(pGroup); 559796af156Sdrh } 560796af156Sdrh } 561796af156Sdrh } 562796af156Sdrh quotaLeave(); 563796af156Sdrh } 564796af156Sdrh return rc; 565796af156Sdrh } 566796af156Sdrh 567796af156Sdrh 568748a52cdSdrh /************************ I/O Method Wrappers *******************************/ 569748a52cdSdrh 570748a52cdSdrh /* xClose requests get passed through to the original VFS. But we 571ef9659baSdrh ** also have to unlink the quotaConn from the quotaFile and quotaGroup. 572ef9659baSdrh ** The quotaFile and/or quotaGroup are freed if they are no longer in use. 573748a52cdSdrh */ 574ef9659baSdrh static int quotaClose(sqlite3_file *pConn){ 575ef9659baSdrh quotaConn *p = (quotaConn*)pConn; 576745de2e4Sdrh quotaFile *pFile = p->pFile; 577ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 578748a52cdSdrh int rc; 579745de2e4Sdrh rc = pSubOpen->pMethods->xClose(pSubOpen); 580748a52cdSdrh quotaEnter(); 581745de2e4Sdrh pFile->nRef--; 582745de2e4Sdrh if( pFile->nRef==0 ){ 583745de2e4Sdrh quotaGroup *pGroup = pFile->pGroup; 58469b2232dSdrh if( pFile->deleteOnClose ){ 58569b2232dSdrh gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0); 58669b2232dSdrh quotaRemoveFile(pFile); 58769b2232dSdrh } 588748a52cdSdrh quotaGroupDeref(pGroup); 589745de2e4Sdrh } 590748a52cdSdrh quotaLeave(); 591748a52cdSdrh return rc; 592748a52cdSdrh } 593748a52cdSdrh 594748a52cdSdrh /* Pass xRead requests directory thru to the original VFS without 595748a52cdSdrh ** further processing. 596748a52cdSdrh */ 597748a52cdSdrh static int quotaRead( 598ef9659baSdrh sqlite3_file *pConn, 599748a52cdSdrh void *pBuf, 600748a52cdSdrh int iAmt, 601748a52cdSdrh sqlite3_int64 iOfst 602748a52cdSdrh ){ 603ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 604745de2e4Sdrh return pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst); 605748a52cdSdrh } 606748a52cdSdrh 607748a52cdSdrh /* Check xWrite requests to see if they expand the file. If they do, 608748a52cdSdrh ** the perform a quota check before passing them through to the 609748a52cdSdrh ** original VFS. 610748a52cdSdrh */ 611748a52cdSdrh static int quotaWrite( 612ef9659baSdrh sqlite3_file *pConn, 6138cf35eb4Sdan const void *pBuf, 614748a52cdSdrh int iAmt, 615748a52cdSdrh sqlite3_int64 iOfst 616748a52cdSdrh ){ 617ef9659baSdrh quotaConn *p = (quotaConn*)pConn; 618ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 619748a52cdSdrh sqlite3_int64 iEnd = iOfst+iAmt; 620748a52cdSdrh quotaGroup *pGroup; 621745de2e4Sdrh quotaFile *pFile = p->pFile; 622748a52cdSdrh sqlite3_int64 szNew; 623748a52cdSdrh 624745de2e4Sdrh if( pFile->iSize<iEnd ){ 625745de2e4Sdrh pGroup = pFile->pGroup; 626748a52cdSdrh quotaEnter(); 627745de2e4Sdrh szNew = pGroup->iSize - pFile->iSize + iEnd; 6288cf35eb4Sdan if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ 629748a52cdSdrh if( pGroup->xCallback ){ 630745de2e4Sdrh pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew, 631748a52cdSdrh pGroup->pArg); 632748a52cdSdrh } 6338cf35eb4Sdan if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ 634748a52cdSdrh quotaLeave(); 635748a52cdSdrh return SQLITE_FULL; 636748a52cdSdrh } 637748a52cdSdrh } 638748a52cdSdrh pGroup->iSize = szNew; 639745de2e4Sdrh pFile->iSize = iEnd; 640748a52cdSdrh quotaLeave(); 641748a52cdSdrh } 642745de2e4Sdrh return pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst); 643748a52cdSdrh } 644748a52cdSdrh 645748a52cdSdrh /* Pass xTruncate requests thru to the original VFS. If the 646748a52cdSdrh ** success, update the file size. 647748a52cdSdrh */ 648ef9659baSdrh static int quotaTruncate(sqlite3_file *pConn, sqlite3_int64 size){ 649ef9659baSdrh quotaConn *p = (quotaConn*)pConn; 650ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 651745de2e4Sdrh int rc = pSubOpen->pMethods->xTruncate(pSubOpen, size); 652745de2e4Sdrh quotaFile *pFile = p->pFile; 653745de2e4Sdrh quotaGroup *pGroup; 654748a52cdSdrh if( rc==SQLITE_OK ){ 655748a52cdSdrh quotaEnter(); 656745de2e4Sdrh pGroup = pFile->pGroup; 657745de2e4Sdrh pGroup->iSize -= pFile->iSize; 658745de2e4Sdrh pFile->iSize = size; 659748a52cdSdrh pGroup->iSize += size; 660748a52cdSdrh quotaLeave(); 661748a52cdSdrh } 662748a52cdSdrh return rc; 663748a52cdSdrh } 664748a52cdSdrh 665748a52cdSdrh /* Pass xSync requests through to the original VFS without change 666748a52cdSdrh */ 667ef9659baSdrh static int quotaSync(sqlite3_file *pConn, int flags){ 668ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 669745de2e4Sdrh return pSubOpen->pMethods->xSync(pSubOpen, flags); 670748a52cdSdrh } 671748a52cdSdrh 672748a52cdSdrh /* Pass xFileSize requests through to the original VFS but then 673748a52cdSdrh ** update the quotaGroup with the new size before returning. 674748a52cdSdrh */ 675ef9659baSdrh static int quotaFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){ 676ef9659baSdrh quotaConn *p = (quotaConn*)pConn; 677ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 678745de2e4Sdrh quotaFile *pFile = p->pFile; 679748a52cdSdrh quotaGroup *pGroup; 680748a52cdSdrh sqlite3_int64 sz; 681748a52cdSdrh int rc; 682748a52cdSdrh 683745de2e4Sdrh rc = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); 684748a52cdSdrh if( rc==SQLITE_OK ){ 685748a52cdSdrh quotaEnter(); 686745de2e4Sdrh pGroup = pFile->pGroup; 687745de2e4Sdrh pGroup->iSize -= pFile->iSize; 688745de2e4Sdrh pFile->iSize = sz; 689748a52cdSdrh pGroup->iSize += sz; 690748a52cdSdrh quotaLeave(); 691748a52cdSdrh *pSize = sz; 692748a52cdSdrh } 693748a52cdSdrh return rc; 694748a52cdSdrh } 695748a52cdSdrh 696748a52cdSdrh /* Pass xLock requests through to the original VFS unchanged. 697748a52cdSdrh */ 698ef9659baSdrh static int quotaLock(sqlite3_file *pConn, int lock){ 699ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 700745de2e4Sdrh return pSubOpen->pMethods->xLock(pSubOpen, lock); 701748a52cdSdrh } 702748a52cdSdrh 703748a52cdSdrh /* Pass xUnlock requests through to the original VFS unchanged. 704748a52cdSdrh */ 705ef9659baSdrh static int quotaUnlock(sqlite3_file *pConn, int lock){ 706ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 707745de2e4Sdrh return pSubOpen->pMethods->xUnlock(pSubOpen, lock); 708748a52cdSdrh } 709748a52cdSdrh 710748a52cdSdrh /* Pass xCheckReservedLock requests through to the original VFS unchanged. 711748a52cdSdrh */ 712ef9659baSdrh static int quotaCheckReservedLock(sqlite3_file *pConn, int *pResOut){ 713ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 714745de2e4Sdrh return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut); 715748a52cdSdrh } 716748a52cdSdrh 717748a52cdSdrh /* Pass xFileControl requests through to the original VFS unchanged. 718748a52cdSdrh */ 719ef9659baSdrh static int quotaFileControl(sqlite3_file *pConn, int op, void *pArg){ 720ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 721c39f3e00Sdrh int rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); 722c39f3e00Sdrh #if defined(SQLITE_FCNTL_VFSNAME) 723c39f3e00Sdrh if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){ 724c39f3e00Sdrh *(char**)pArg = sqlite3_mprintf("quota/%z", *(char**)pArg); 725c39f3e00Sdrh } 726c39f3e00Sdrh #endif 727c39f3e00Sdrh return rc; 728748a52cdSdrh } 729748a52cdSdrh 730748a52cdSdrh /* Pass xSectorSize requests through to the original VFS unchanged. 731748a52cdSdrh */ 732ef9659baSdrh static int quotaSectorSize(sqlite3_file *pConn){ 733ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 734745de2e4Sdrh return pSubOpen->pMethods->xSectorSize(pSubOpen); 735748a52cdSdrh } 736748a52cdSdrh 737748a52cdSdrh /* Pass xDeviceCharacteristics requests through to the original VFS unchanged. 738748a52cdSdrh */ 739ef9659baSdrh static int quotaDeviceCharacteristics(sqlite3_file *pConn){ 740ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 741745de2e4Sdrh return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen); 742748a52cdSdrh } 743748a52cdSdrh 744748a52cdSdrh /* Pass xShmMap requests through to the original VFS unchanged. 745748a52cdSdrh */ 746748a52cdSdrh static int quotaShmMap( 747ef9659baSdrh sqlite3_file *pConn, /* Handle open on database file */ 748748a52cdSdrh int iRegion, /* Region to retrieve */ 749748a52cdSdrh int szRegion, /* Size of regions */ 750748a52cdSdrh int bExtend, /* True to extend file if necessary */ 751748a52cdSdrh void volatile **pp /* OUT: Mapped memory */ 752748a52cdSdrh ){ 753ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 754745de2e4Sdrh return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend, pp); 755748a52cdSdrh } 756748a52cdSdrh 757748a52cdSdrh /* Pass xShmLock requests through to the original VFS unchanged. 758748a52cdSdrh */ 759748a52cdSdrh static int quotaShmLock( 760ef9659baSdrh sqlite3_file *pConn, /* Database file holding the shared memory */ 761748a52cdSdrh int ofst, /* First lock to acquire or release */ 762748a52cdSdrh int n, /* Number of locks to acquire or release */ 763748a52cdSdrh int flags /* What to do with the lock */ 764748a52cdSdrh ){ 765ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 766745de2e4Sdrh return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags); 767748a52cdSdrh } 768748a52cdSdrh 769748a52cdSdrh /* Pass xShmBarrier requests through to the original VFS unchanged. 770748a52cdSdrh */ 771ef9659baSdrh static void quotaShmBarrier(sqlite3_file *pConn){ 772ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 773745de2e4Sdrh pSubOpen->pMethods->xShmBarrier(pSubOpen); 774748a52cdSdrh } 775748a52cdSdrh 776748a52cdSdrh /* Pass xShmUnmap requests through to the original VFS unchanged. 777748a52cdSdrh */ 778ef9659baSdrh static int quotaShmUnmap(sqlite3_file *pConn, int deleteFlag){ 779ef9659baSdrh sqlite3_file *pSubOpen = quotaSubOpen(pConn); 780745de2e4Sdrh return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag); 781748a52cdSdrh } 782748a52cdSdrh 783748a52cdSdrh /************************** Public Interfaces *****************************/ 784748a52cdSdrh /* 785748a52cdSdrh ** Initialize the quota VFS shim. Use the VFS named zOrigVfsName 786748a52cdSdrh ** as the VFS that does the actual work. Use the default if 787748a52cdSdrh ** zOrigVfsName==NULL. 788748a52cdSdrh ** 789748a52cdSdrh ** The quota VFS shim is named "quota". It will become the default 790748a52cdSdrh ** VFS if makeDefault is non-zero. 791748a52cdSdrh ** 792748a52cdSdrh ** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once 793748a52cdSdrh ** during start-up. 794748a52cdSdrh */ 795748a52cdSdrh int sqlite3_quota_initialize(const char *zOrigVfsName, int makeDefault){ 796748a52cdSdrh sqlite3_vfs *pOrigVfs; 7978cf35eb4Sdan if( gQuota.isInitialized ) return SQLITE_MISUSE; 798748a52cdSdrh pOrigVfs = sqlite3_vfs_find(zOrigVfsName); 799748a52cdSdrh if( pOrigVfs==0 ) return SQLITE_ERROR; 8008cf35eb4Sdan assert( pOrigVfs!=&gQuota.sThisVfs ); 8018cf35eb4Sdan gQuota.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); 8028cf35eb4Sdan if( !gQuota.pMutex ){ 8038cf35eb4Sdan return SQLITE_NOMEM; 8048cf35eb4Sdan } 8058cf35eb4Sdan gQuota.isInitialized = 1; 806748a52cdSdrh gQuota.pOrigVfs = pOrigVfs; 807748a52cdSdrh gQuota.sThisVfs = *pOrigVfs; 808748a52cdSdrh gQuota.sThisVfs.xOpen = quotaOpen; 809796af156Sdrh gQuota.sThisVfs.xDelete = quotaDelete; 810ef9659baSdrh gQuota.sThisVfs.szOsFile += sizeof(quotaConn); 811748a52cdSdrh gQuota.sThisVfs.zName = "quota"; 812748a52cdSdrh gQuota.sIoMethodsV1.iVersion = 1; 813748a52cdSdrh gQuota.sIoMethodsV1.xClose = quotaClose; 814748a52cdSdrh gQuota.sIoMethodsV1.xRead = quotaRead; 815748a52cdSdrh gQuota.sIoMethodsV1.xWrite = quotaWrite; 816748a52cdSdrh gQuota.sIoMethodsV1.xTruncate = quotaTruncate; 817748a52cdSdrh gQuota.sIoMethodsV1.xSync = quotaSync; 818748a52cdSdrh gQuota.sIoMethodsV1.xFileSize = quotaFileSize; 819748a52cdSdrh gQuota.sIoMethodsV1.xLock = quotaLock; 820748a52cdSdrh gQuota.sIoMethodsV1.xUnlock = quotaUnlock; 821748a52cdSdrh gQuota.sIoMethodsV1.xCheckReservedLock = quotaCheckReservedLock; 822748a52cdSdrh gQuota.sIoMethodsV1.xFileControl = quotaFileControl; 8238cf35eb4Sdan gQuota.sIoMethodsV1.xSectorSize = quotaSectorSize; 824748a52cdSdrh gQuota.sIoMethodsV1.xDeviceCharacteristics = quotaDeviceCharacteristics; 825748a52cdSdrh gQuota.sIoMethodsV2 = gQuota.sIoMethodsV1; 826748a52cdSdrh gQuota.sIoMethodsV2.iVersion = 2; 827748a52cdSdrh gQuota.sIoMethodsV2.xShmMap = quotaShmMap; 828748a52cdSdrh gQuota.sIoMethodsV2.xShmLock = quotaShmLock; 829748a52cdSdrh gQuota.sIoMethodsV2.xShmBarrier = quotaShmBarrier; 830748a52cdSdrh gQuota.sIoMethodsV2.xShmUnmap = quotaShmUnmap; 8318cf35eb4Sdan sqlite3_vfs_register(&gQuota.sThisVfs, makeDefault); 8328cf35eb4Sdan return SQLITE_OK; 833748a52cdSdrh } 834748a52cdSdrh 835748a52cdSdrh /* 836748a52cdSdrh ** Shutdown the quota system. 837748a52cdSdrh ** 838748a52cdSdrh ** All SQLite database connections must be closed before calling this 839748a52cdSdrh ** routine. 840748a52cdSdrh ** 841796af156Sdrh ** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once while 842748a52cdSdrh ** shutting down in order to free all remaining quota groups. 843748a52cdSdrh */ 844748a52cdSdrh int sqlite3_quota_shutdown(void){ 84575c19174Sdrh quotaGroup *pGroup; 846748a52cdSdrh if( gQuota.isInitialized==0 ) return SQLITE_MISUSE; 84775c19174Sdrh for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){ 848796af156Sdrh if( quotaGroupOpenFileCount(pGroup)>0 ) return SQLITE_MISUSE; 8498cf35eb4Sdan } 8508cf35eb4Sdan while( gQuota.pGroup ){ 85175c19174Sdrh pGroup = gQuota.pGroup; 85275c19174Sdrh gQuota.pGroup = pGroup->pNext; 8539fc7702cSdan pGroup->iLimit = 0; 854796af156Sdrh assert( quotaGroupOpenFileCount(pGroup)==0 ); 8559fc7702cSdan quotaGroupDeref(pGroup); 8568cf35eb4Sdan } 857748a52cdSdrh gQuota.isInitialized = 0; 858748a52cdSdrh sqlite3_mutex_free(gQuota.pMutex); 8598cf35eb4Sdan sqlite3_vfs_unregister(&gQuota.sThisVfs); 860748a52cdSdrh memset(&gQuota, 0, sizeof(gQuota)); 8618cf35eb4Sdan return SQLITE_OK; 862748a52cdSdrh } 863748a52cdSdrh 864748a52cdSdrh /* 865748a52cdSdrh ** Create or destroy a quota group. 866748a52cdSdrh ** 867748a52cdSdrh ** The quota group is defined by the zPattern. When calling this routine 868748a52cdSdrh ** with a zPattern for a quota group that already exists, this routine 869748a52cdSdrh ** merely updates the iLimit, xCallback, and pArg values for that quota 870748a52cdSdrh ** group. If zPattern is new, then a new quota group is created. 871748a52cdSdrh ** 872748a52cdSdrh ** If the iLimit for a quota group is set to zero, then the quota group 873748a52cdSdrh ** is disabled and will be deleted when the last database connection using 874748a52cdSdrh ** the quota group is closed. 875748a52cdSdrh ** 876748a52cdSdrh ** Calling this routine on a zPattern that does not exist and with a 877748a52cdSdrh ** zero iLimit is a no-op. 878748a52cdSdrh ** 879748a52cdSdrh ** A quota group must exist with a non-zero iLimit prior to opening 880748a52cdSdrh ** database connections if those connections are to participate in the 8818cf35eb4Sdan ** quota group. Creating a quota group does not affect database connections 882748a52cdSdrh ** that are already open. 883748a52cdSdrh */ 884748a52cdSdrh int sqlite3_quota_set( 885748a52cdSdrh const char *zPattern, /* The filename pattern */ 886748a52cdSdrh sqlite3_int64 iLimit, /* New quota to set for this quota group */ 887748a52cdSdrh void (*xCallback)( /* Callback invoked when going over quota */ 888748a52cdSdrh const char *zFilename, /* Name of file whose size increases */ 889748a52cdSdrh sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ 890748a52cdSdrh sqlite3_int64 iSize, /* Total size of all files in the group */ 891748a52cdSdrh void *pArg /* Client data */ 8928cf35eb4Sdan ), 893ef9659baSdrh void *pArg, /* client data passed thru to callback */ 894ef9659baSdrh void (*xDestroy)(void*) /* Optional destructor for pArg */ 895748a52cdSdrh ){ 896748a52cdSdrh quotaGroup *pGroup; 897748a52cdSdrh quotaEnter(); 898748a52cdSdrh pGroup = gQuota.pGroup; 899748a52cdSdrh while( pGroup && strcmp(pGroup->zPattern, zPattern)!=0 ){ 900748a52cdSdrh pGroup = pGroup->pNext; 901748a52cdSdrh } 90261cc423aSdrh if( pGroup==0 ){ 90383cc1392Sdrh int nPattern = (int)(strlen(zPattern) & 0x3fffffff); 90461cc423aSdrh if( iLimit<=0 ){ 90561cc423aSdrh quotaLeave(); 90661cc423aSdrh return SQLITE_OK; 90761cc423aSdrh } 908b0a32832Sdan pGroup = (quotaGroup *)sqlite3_malloc( sizeof(*pGroup) + nPattern + 1 ); 909748a52cdSdrh if( pGroup==0 ){ 910748a52cdSdrh quotaLeave(); 911748a52cdSdrh return SQLITE_NOMEM; 912748a52cdSdrh } 913748a52cdSdrh memset(pGroup, 0, sizeof(*pGroup)); 914748a52cdSdrh pGroup->zPattern = (char*)&pGroup[1]; 9158cf35eb4Sdan memcpy((char *)pGroup->zPattern, zPattern, nPattern+1); 916748a52cdSdrh if( gQuota.pGroup ) gQuota.pGroup->ppPrev = &pGroup->pNext; 9178cf35eb4Sdan pGroup->pNext = gQuota.pGroup; 9188cf35eb4Sdan pGroup->ppPrev = &gQuota.pGroup; 9198cf35eb4Sdan gQuota.pGroup = pGroup; 920748a52cdSdrh } 921748a52cdSdrh pGroup->iLimit = iLimit; 922748a52cdSdrh pGroup->xCallback = xCallback; 923ef9659baSdrh if( pGroup->xDestroy && pGroup->pArg!=pArg ){ 924ef9659baSdrh pGroup->xDestroy(pGroup->pArg); 925ef9659baSdrh } 926748a52cdSdrh pGroup->pArg = pArg; 927ef9659baSdrh pGroup->xDestroy = xDestroy; 928748a52cdSdrh quotaGroupDeref(pGroup); 929748a52cdSdrh quotaLeave(); 930748a52cdSdrh return SQLITE_OK; 931748a52cdSdrh } 932748a52cdSdrh 933b6020c4eSdrh /* 934b6020c4eSdrh ** Bring the named file under quota management. Or if it is already under 935b6020c4eSdrh ** management, update its size. 936b6020c4eSdrh */ 937b6020c4eSdrh int sqlite3_quota_file(const char *zFilename){ 938b6020c4eSdrh char *zFull; 939b6020c4eSdrh sqlite3_file *fd; 940b6020c4eSdrh int rc; 941b6020c4eSdrh int outFlags = 0; 942b6020c4eSdrh sqlite3_int64 iSize; 94372cd90acSdan int nAlloc = gQuota.sThisVfs.szOsFile + gQuota.sThisVfs.mxPathname+2; 94472cd90acSdan 94572cd90acSdan /* Allocate space for a file-handle and the full path for file zFilename */ 94672cd90acSdan fd = (sqlite3_file *)sqlite3_malloc(nAlloc); 94772cd90acSdan if( fd==0 ){ 94872cd90acSdan rc = SQLITE_NOMEM; 94972cd90acSdan }else{ 95072cd90acSdan zFull = &((char *)fd)[gQuota.sThisVfs.szOsFile]; 951b6020c4eSdrh rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename, 952b6020c4eSdrh gQuota.sThisVfs.mxPathname+1, zFull); 95372cd90acSdan } 95472cd90acSdan 955b6020c4eSdrh if( rc==SQLITE_OK ){ 95672cd90acSdan zFull[strlen(zFull)+1] = '\0'; 957b6020c4eSdrh rc = quotaOpen(&gQuota.sThisVfs, zFull, fd, 958b6020c4eSdrh SQLITE_OPEN_READONLY | SQLITE_OPEN_MAIN_DB, &outFlags); 959b6020c4eSdrh if( rc==SQLITE_OK ){ 96026541c9aSdrh fd->pMethods->xFileSize(fd, &iSize); 96126541c9aSdrh fd->pMethods->xClose(fd); 962b6020c4eSdrh }else if( rc==SQLITE_CANTOPEN ){ 963b6020c4eSdrh quotaGroup *pGroup; 964b6020c4eSdrh quotaFile *pFile; 965b6020c4eSdrh quotaEnter(); 966b6020c4eSdrh pGroup = quotaGroupFind(zFull); 967b6020c4eSdrh if( pGroup ){ 96860f21e4bSdrh pFile = quotaFindFile(pGroup, zFull, 0); 969b6020c4eSdrh if( pFile ) quotaRemoveFile(pFile); 970b6020c4eSdrh } 971b6020c4eSdrh quotaLeave(); 972b6020c4eSdrh } 97372cd90acSdan } 97472cd90acSdan 975b6020c4eSdrh sqlite3_free(fd); 976b6020c4eSdrh return rc; 977b6020c4eSdrh } 978b6020c4eSdrh 97960f21e4bSdrh /* 98060f21e4bSdrh ** Open a potentially quotaed file for I/O. 98160f21e4bSdrh */ 98260f21e4bSdrh quota_FILE *sqlite3_quota_fopen(const char *zFilename, const char *zMode){ 98360f21e4bSdrh quota_FILE *p = 0; 98460f21e4bSdrh char *zFull = 0; 985c00ce490Sdrh char *zFullTranslated = 0; 98660f21e4bSdrh int rc; 98760f21e4bSdrh quotaGroup *pGroup; 98860f21e4bSdrh quotaFile *pFile; 98960f21e4bSdrh 990663cebfeSdrh zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1); 991a76e8589Sdrh if( zFull==0 ) return 0; 99260f21e4bSdrh rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename, 99360f21e4bSdrh gQuota.sThisVfs.mxPathname+1, zFull); 99460f21e4bSdrh if( rc ) goto quota_fopen_error; 995663cebfeSdrh p = (quota_FILE*)sqlite3_malloc(sizeof(*p)); 99660f21e4bSdrh if( p==0 ) goto quota_fopen_error; 99760f21e4bSdrh memset(p, 0, sizeof(*p)); 998a76e8589Sdrh zFullTranslated = quota_utf8_to_mbcs(zFull); 999a76e8589Sdrh if( zFullTranslated==0 ) goto quota_fopen_error; 1000a76e8589Sdrh p->f = fopen(zFullTranslated, zMode); 100160f21e4bSdrh if( p->f==0 ) goto quota_fopen_error; 100260f21e4bSdrh quotaEnter(); 100360f21e4bSdrh pGroup = quotaGroupFind(zFull); 100460f21e4bSdrh if( pGroup ){ 100560f21e4bSdrh pFile = quotaFindFile(pGroup, zFull, 1); 100660f21e4bSdrh if( pFile==0 ){ 100760f21e4bSdrh quotaLeave(); 100860f21e4bSdrh goto quota_fopen_error; 100960f21e4bSdrh } 101060f21e4bSdrh pFile->nRef++; 101160f21e4bSdrh p->pFile = pFile; 101260f21e4bSdrh } 101360f21e4bSdrh quotaLeave(); 101460f21e4bSdrh sqlite3_free(zFull); 1015c00ce490Sdrh #if SQLITE_OS_WIN 1016c00ce490Sdrh p->zMbcsName = zFullTranslated; 1017c00ce490Sdrh #endif 101860f21e4bSdrh return p; 101960f21e4bSdrh 102060f21e4bSdrh quota_fopen_error: 1021c00ce490Sdrh quota_mbcs_free(zFullTranslated); 102260f21e4bSdrh sqlite3_free(zFull); 102360f21e4bSdrh if( p && p->f ) fclose(p->f); 102460f21e4bSdrh sqlite3_free(p); 102560f21e4bSdrh return 0; 102660f21e4bSdrh } 102760f21e4bSdrh 102860f21e4bSdrh /* 102960f21e4bSdrh ** Read content from a quota_FILE 103060f21e4bSdrh */ 103160f21e4bSdrh size_t sqlite3_quota_fread( 103260f21e4bSdrh void *pBuf, /* Store the content here */ 103360f21e4bSdrh size_t size, /* Size of each element */ 103460f21e4bSdrh size_t nmemb, /* Number of elements to read */ 103560f21e4bSdrh quota_FILE *p /* Read from this quota_FILE object */ 103660f21e4bSdrh ){ 103760f21e4bSdrh return fread(pBuf, size, nmemb, p->f); 103860f21e4bSdrh } 103960f21e4bSdrh 104060f21e4bSdrh /* 104160f21e4bSdrh ** Write content into a quota_FILE. Invoke the quota callback and block 104260f21e4bSdrh ** the write if we exceed quota. 104360f21e4bSdrh */ 104460f21e4bSdrh size_t sqlite3_quota_fwrite( 1045*98c78ea6Sdrh const void *pBuf, /* Take content to write from here */ 104660f21e4bSdrh size_t size, /* Size of each element */ 104760f21e4bSdrh size_t nmemb, /* Number of elements */ 104860f21e4bSdrh quota_FILE *p /* Write to this quota_FILE objecct */ 104960f21e4bSdrh ){ 105060f21e4bSdrh sqlite3_int64 iOfst; 105160f21e4bSdrh sqlite3_int64 iEnd; 105260f21e4bSdrh sqlite3_int64 szNew; 105360f21e4bSdrh quotaFile *pFile; 1054c00ce490Sdrh size_t rc; 105560f21e4bSdrh 105660f21e4bSdrh iOfst = ftell(p->f); 105760f21e4bSdrh iEnd = iOfst + size*nmemb; 105860f21e4bSdrh pFile = p->pFile; 1059c8ccda60Sdrh if( pFile && pFile->iSize<iEnd ){ 106060f21e4bSdrh quotaGroup *pGroup = pFile->pGroup; 106160f21e4bSdrh quotaEnter(); 106260f21e4bSdrh szNew = pGroup->iSize - pFile->iSize + iEnd; 106360f21e4bSdrh if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ 106460f21e4bSdrh if( pGroup->xCallback ){ 106560f21e4bSdrh pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew, 106660f21e4bSdrh pGroup->pArg); 106760f21e4bSdrh } 106860f21e4bSdrh if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ 106960f21e4bSdrh iEnd = pGroup->iLimit - pGroup->iSize + pFile->iSize; 10707da5fcb0Sdrh nmemb = (size_t)((iEnd - iOfst)/size); 107160f21e4bSdrh iEnd = iOfst + size*nmemb; 107260f21e4bSdrh szNew = pGroup->iSize - pFile->iSize + iEnd; 107360f21e4bSdrh } 107460f21e4bSdrh } 107560f21e4bSdrh pGroup->iSize = szNew; 107660f21e4bSdrh pFile->iSize = iEnd; 107760f21e4bSdrh quotaLeave(); 1078c00ce490Sdrh }else{ 1079c00ce490Sdrh pFile = 0; 108060f21e4bSdrh } 1081c00ce490Sdrh rc = fwrite(pBuf, size, nmemb, p->f); 1082c00ce490Sdrh 1083c00ce490Sdrh /* If the write was incomplete, adjust the file size and group size 1084c00ce490Sdrh ** downward */ 1085c00ce490Sdrh if( rc<nmemb && pFile ){ 1086c00ce490Sdrh size_t nWritten = rc>=0 ? rc : 0; 1087c00ce490Sdrh sqlite3_int64 iNewEnd = iOfst + size*nWritten; 1088c00ce490Sdrh if( iNewEnd<iEnd ) iNewEnd = iEnd; 1089c00ce490Sdrh quotaEnter(); 1090c00ce490Sdrh pFile->pGroup->iSize += iNewEnd - pFile->iSize; 1091c00ce490Sdrh pFile->iSize = iNewEnd; 1092c00ce490Sdrh quotaLeave(); 1093c00ce490Sdrh } 1094c00ce490Sdrh return rc; 109560f21e4bSdrh } 109660f21e4bSdrh 109760f21e4bSdrh /* 109860f21e4bSdrh ** Close an open quota_FILE stream. 109960f21e4bSdrh */ 110060f21e4bSdrh int sqlite3_quota_fclose(quota_FILE *p){ 110160f21e4bSdrh int rc; 110260f21e4bSdrh quotaFile *pFile; 110360f21e4bSdrh rc = fclose(p->f); 110460f21e4bSdrh pFile = p->pFile; 1105c8ccda60Sdrh if( pFile ){ 110660f21e4bSdrh quotaEnter(); 110760f21e4bSdrh pFile->nRef--; 110860f21e4bSdrh if( pFile->nRef==0 ){ 110960f21e4bSdrh quotaGroup *pGroup = pFile->pGroup; 111069b2232dSdrh if( pFile->deleteOnClose ){ 111169b2232dSdrh gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0); 111269b2232dSdrh quotaRemoveFile(pFile); 111369b2232dSdrh } 111460f21e4bSdrh quotaGroupDeref(pGroup); 111560f21e4bSdrh } 111660f21e4bSdrh quotaLeave(); 1117c8ccda60Sdrh } 1118c00ce490Sdrh #if SQLITE_OS_WIN 1119c00ce490Sdrh quota_mbcs_free(p->zMbcsName); 1120c00ce490Sdrh #endif 112160f21e4bSdrh sqlite3_free(p); 112260f21e4bSdrh return rc; 112360f21e4bSdrh } 112460f21e4bSdrh 112560f21e4bSdrh /* 112669b2232dSdrh ** Flush memory buffers for a quota_FILE to disk. 112769b2232dSdrh */ 112827cec37dSdrh int sqlite3_quota_fflush(quota_FILE *p, int doFsync){ 112927cec37dSdrh int rc; 113027cec37dSdrh rc = fflush(p->f); 113127cec37dSdrh if( rc==0 && doFsync ){ 113227cec37dSdrh #if SQLITE_OS_UNIX 113327cec37dSdrh rc = fsync(fileno(p->f)); 113427cec37dSdrh #endif 113527cec37dSdrh #if SQLITE_OS_WIN 1136c39f3e00Sdrh rc = _commit(_fileno(p->f)); 113727cec37dSdrh #endif 113827cec37dSdrh } 1139c39f3e00Sdrh return rc!=0; 114069b2232dSdrh } 114169b2232dSdrh 114269b2232dSdrh /* 114360f21e4bSdrh ** Seek on a quota_FILE stream. 114460f21e4bSdrh */ 114560f21e4bSdrh int sqlite3_quota_fseek(quota_FILE *p, long offset, int whence){ 114660f21e4bSdrh return fseek(p->f, offset, whence); 114760f21e4bSdrh } 114860f21e4bSdrh 114960f21e4bSdrh /* 115060f21e4bSdrh ** rewind a quota_FILE stream. 115160f21e4bSdrh */ 115260f21e4bSdrh void sqlite3_quota_rewind(quota_FILE *p){ 115360f21e4bSdrh rewind(p->f); 115460f21e4bSdrh } 115560f21e4bSdrh 115660f21e4bSdrh /* 115760f21e4bSdrh ** Tell the current location of a quota_FILE stream. 115860f21e4bSdrh */ 115960f21e4bSdrh long sqlite3_quota_ftell(quota_FILE *p){ 116060f21e4bSdrh return ftell(p->f); 116160f21e4bSdrh } 116260f21e4bSdrh 116360f21e4bSdrh /* 1164*98c78ea6Sdrh ** Test the error indicator for the given file. 1165*98c78ea6Sdrh */ 1166*98c78ea6Sdrh int sqlite3_quota_ferror(quota_FILE *p){ 1167*98c78ea6Sdrh return ferror(p->f); 1168*98c78ea6Sdrh } 1169*98c78ea6Sdrh 1170*98c78ea6Sdrh /* 1171c00ce490Sdrh ** Truncate a file to szNew bytes. 1172c00ce490Sdrh */ 1173c00ce490Sdrh int sqlite3_quota_ftruncate(quota_FILE *p, sqlite3_int64 szNew){ 1174c00ce490Sdrh quotaFile *pFile = p->pFile; 1175c00ce490Sdrh int rc; 1176c00ce490Sdrh if( (pFile = p->pFile)!=0 && pFile->iSize<szNew ){ 1177c00ce490Sdrh quotaGroup *pGroup; 1178c00ce490Sdrh if( pFile->iSize<szNew ){ 1179c00ce490Sdrh /* This routine cannot be used to extend a file that is under 1180c00ce490Sdrh ** quota management. Only true truncation is allowed. */ 1181c00ce490Sdrh return -1; 1182c00ce490Sdrh } 1183c00ce490Sdrh pGroup = pFile->pGroup; 1184c00ce490Sdrh quotaEnter(); 1185c00ce490Sdrh pGroup->iSize += szNew - pFile->iSize; 1186c00ce490Sdrh quotaLeave(); 1187c00ce490Sdrh } 1188c00ce490Sdrh #if SQLITE_OS_UNIX 1189c00ce490Sdrh rc = ftruncate(fileno(p->f), szNew); 1190c00ce490Sdrh #endif 1191c00ce490Sdrh #if SQLITE_OS_WIN 1192c00ce490Sdrh rc = _chsize_s(_fileno(p->f), szNew); 1193c00ce490Sdrh #endif 1194c00ce490Sdrh if( pFile && rc==0 ){ 1195c00ce490Sdrh quotaGroup *pGroup = pFile->pGroup; 1196c00ce490Sdrh quotaEnter(); 1197c00ce490Sdrh pGroup->iSize += szNew - pFile->iSize; 1198c00ce490Sdrh pFile->iSize = szNew; 1199c00ce490Sdrh quotaLeave(); 1200c00ce490Sdrh } 1201c00ce490Sdrh return rc; 1202c00ce490Sdrh } 1203c00ce490Sdrh 1204c00ce490Sdrh /* 1205c00ce490Sdrh ** Determine the time that the given file was last modified, in 1206c00ce490Sdrh ** seconds size 1970. Write the result into *pTime. Return 0 on 1207c00ce490Sdrh ** success and non-zero on any kind of error. 1208c00ce490Sdrh */ 1209c00ce490Sdrh int sqlite3_quota_file_mtime(quota_FILE *p, time_t *pTime){ 1210c00ce490Sdrh int rc; 1211c00ce490Sdrh #if SQLITE_OS_UNIX 1212c00ce490Sdrh struct stat buf; 1213c00ce490Sdrh rc = fstat(fileno(p->f), &buf); 1214c00ce490Sdrh #endif 1215c00ce490Sdrh #if SQLITE_OS_WIN 1216c00ce490Sdrh struct _stati64 buf; 1217c00ce490Sdrh rc = _stati64(p->zMbcsName, &buf); 1218c00ce490Sdrh #endif 1219c00ce490Sdrh if( rc==0 ) *pTime = buf.st_mtime; 1220c00ce490Sdrh return rc; 1221c00ce490Sdrh } 1222c00ce490Sdrh 1223c00ce490Sdrh /* 1224c00ce490Sdrh ** Return the true size of the file, as reported by the operating 1225c00ce490Sdrh ** system. 1226c00ce490Sdrh */ 1227c00ce490Sdrh sqlite3_int64 sqlite3_quota_file_truesize(quota_FILE *p){ 1228c00ce490Sdrh int rc; 1229c00ce490Sdrh #if SQLITE_OS_UNIX 1230c00ce490Sdrh struct stat buf; 1231c00ce490Sdrh rc = fstat(fileno(p->f), &buf); 1232c00ce490Sdrh #endif 1233c00ce490Sdrh #if SQLITE_OS_WIN 1234c00ce490Sdrh struct _stati64 buf; 1235c00ce490Sdrh rc = _stati64(p->zMbcsName, &buf); 1236c00ce490Sdrh #endif 1237c00ce490Sdrh return rc==0 ? buf.st_size : -1; 1238c00ce490Sdrh } 1239c00ce490Sdrh 1240c00ce490Sdrh /* 1241c00ce490Sdrh ** Return the size of the file, as it is known to the quota subsystem. 1242c00ce490Sdrh */ 1243c00ce490Sdrh sqlite3_int64 sqlite3_quota_file_size(quota_FILE *p){ 1244c00ce490Sdrh return p->pFile ? p->pFile->iSize : -1; 1245c00ce490Sdrh } 1246c00ce490Sdrh 1247c00ce490Sdrh /* 1248*98c78ea6Sdrh ** Determine the amount of data in bytes available for reading 1249*98c78ea6Sdrh ** in the given file. 1250*98c78ea6Sdrh */ 1251*98c78ea6Sdrh long sqlite3_quota_file_available(quota_FILE *p){ 1252*98c78ea6Sdrh FILE* f = p->f; 1253*98c78ea6Sdrh long pos1, pos2; 1254*98c78ea6Sdrh int rc; 1255*98c78ea6Sdrh pos1 = ftell(f); 1256*98c78ea6Sdrh if ( pos1 < 0 ) return -1; 1257*98c78ea6Sdrh rc = fseek(f, 0, SEEK_END); 1258*98c78ea6Sdrh if ( rc != 0 ) return -1; 1259*98c78ea6Sdrh pos2 = ftell(f); 1260*98c78ea6Sdrh if ( pos2 < 0 ) return -1; 1261*98c78ea6Sdrh rc = fseek(f, pos1, SEEK_SET); 1262*98c78ea6Sdrh if ( rc != 0 ) return -1; 1263*98c78ea6Sdrh return pos2 - pos1; 1264*98c78ea6Sdrh } 1265*98c78ea6Sdrh 1266*98c78ea6Sdrh /* 126769b2232dSdrh ** Remove a managed file. Update quotas accordingly. 126860f21e4bSdrh */ 126960f21e4bSdrh int sqlite3_quota_remove(const char *zFilename){ 127069b2232dSdrh char *zFull; /* Full pathname for zFilename */ 127183cc1392Sdrh size_t nFull; /* Number of bytes in zFilename */ 127269b2232dSdrh int rc; /* Result code */ 127369b2232dSdrh quotaGroup *pGroup; /* Group containing zFilename */ 127469b2232dSdrh quotaFile *pFile; /* A file in the group */ 127569b2232dSdrh quotaFile *pNextFile; /* next file in the group */ 127669b2232dSdrh int diff; /* Difference between filenames */ 127769b2232dSdrh char c; /* First character past end of pattern */ 127869b2232dSdrh 1279663cebfeSdrh zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1); 128069b2232dSdrh if( zFull==0 ) return SQLITE_NOMEM; 128169b2232dSdrh rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename, 128269b2232dSdrh gQuota.sThisVfs.mxPathname+1, zFull); 128369b2232dSdrh if( rc ){ 128469b2232dSdrh sqlite3_free(zFull); 128560f21e4bSdrh return rc; 128660f21e4bSdrh } 128760f21e4bSdrh 128869b2232dSdrh /* Figure out the length of the full pathname. If the name ends with 128969b2232dSdrh ** / (or \ on windows) then remove the trailing /. 129069b2232dSdrh */ 129169b2232dSdrh nFull = strlen(zFull); 129269b2232dSdrh if( nFull>0 && (zFull[nFull-1]=='/' || zFull[nFull-1]=='\\') ){ 129369b2232dSdrh nFull--; 129469b2232dSdrh zFull[nFull] = 0; 129569b2232dSdrh } 129669b2232dSdrh 129769b2232dSdrh quotaEnter(); 129869b2232dSdrh pGroup = quotaGroupFind(zFull); 129969b2232dSdrh if( pGroup ){ 130069b2232dSdrh for(pFile=pGroup->pFiles; pFile && rc==SQLITE_OK; pFile=pNextFile){ 130169b2232dSdrh pNextFile = pFile->pNext; 130269b2232dSdrh diff = memcmp(zFull, pFile->zFilename, nFull); 130369b2232dSdrh if( diff==0 && ((c = pFile->zFilename[nFull])==0 || c=='/' || c=='\\') ){ 130469b2232dSdrh if( pFile->nRef ){ 130569b2232dSdrh pFile->deleteOnClose = 1; 130669b2232dSdrh }else{ 130769b2232dSdrh rc = gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0); 130869b2232dSdrh quotaRemoveFile(pFile); 130969b2232dSdrh quotaGroupDeref(pGroup); 131069b2232dSdrh } 131169b2232dSdrh } 131269b2232dSdrh } 131369b2232dSdrh } 131469b2232dSdrh quotaLeave(); 131569b2232dSdrh sqlite3_free(zFull); 131669b2232dSdrh return rc; 131769b2232dSdrh } 1318748a52cdSdrh 1319748a52cdSdrh /***************************** Test Code ***********************************/ 1320748a52cdSdrh #ifdef SQLITE_TEST 1321748a52cdSdrh #include <tcl.h> 13228cf35eb4Sdan 1323ef9659baSdrh /* 1324ef9659baSdrh ** Argument passed to a TCL quota-over-limit callback. 1325ef9659baSdrh */ 13268cf35eb4Sdan typedef struct TclQuotaCallback TclQuotaCallback; 13278cf35eb4Sdan struct TclQuotaCallback { 1328ef9659baSdrh Tcl_Interp *interp; /* Interpreter in which to run the script */ 1329ef9659baSdrh Tcl_Obj *pScript; /* Script to be run */ 13308cf35eb4Sdan }; 13318cf35eb4Sdan 13328cf35eb4Sdan extern const char *sqlite3TestErrorName(int); 13338cf35eb4Sdan 1334ef9659baSdrh 1335ef9659baSdrh /* 1336ef9659baSdrh ** This is the callback from a quota-over-limit. 1337ef9659baSdrh */ 13388cf35eb4Sdan static void tclQuotaCallback( 13398cf35eb4Sdan const char *zFilename, /* Name of file whose size increases */ 13408cf35eb4Sdan sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ 13418cf35eb4Sdan sqlite3_int64 iSize, /* Total size of all files in the group */ 13428cf35eb4Sdan void *pArg /* Client data */ 13438cf35eb4Sdan ){ 13448cf35eb4Sdan TclQuotaCallback *p; /* Callback script object */ 13458cf35eb4Sdan Tcl_Obj *pEval; /* Script to evaluate */ 13468cf35eb4Sdan Tcl_Obj *pVarname; /* Name of variable to pass as 2nd arg */ 13478cf35eb4Sdan unsigned int rnd; /* Random part of pVarname */ 13488cf35eb4Sdan int rc; /* Tcl error code */ 13498cf35eb4Sdan 13508cf35eb4Sdan p = (TclQuotaCallback *)pArg; 13513d3e60e4Sdrh if( p==0 ) return; 13528cf35eb4Sdan 13538cf35eb4Sdan pVarname = Tcl_NewStringObj("::piLimit_", -1); 13548cf35eb4Sdan Tcl_IncrRefCount(pVarname); 13558cf35eb4Sdan sqlite3_randomness(sizeof(rnd), (void *)&rnd); 13568cf35eb4Sdan Tcl_AppendObjToObj(pVarname, Tcl_NewIntObj((int)(rnd&0x7FFFFFFF))); 13578cf35eb4Sdan Tcl_ObjSetVar2(p->interp, pVarname, 0, Tcl_NewWideIntObj(*piLimit), 0); 13588cf35eb4Sdan 13598cf35eb4Sdan pEval = Tcl_DuplicateObj(p->pScript); 13608cf35eb4Sdan Tcl_IncrRefCount(pEval); 13618cf35eb4Sdan Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zFilename, -1)); 13628cf35eb4Sdan Tcl_ListObjAppendElement(0, pEval, pVarname); 13638cf35eb4Sdan Tcl_ListObjAppendElement(0, pEval, Tcl_NewWideIntObj(iSize)); 13648cf35eb4Sdan rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL); 13658cf35eb4Sdan 13668cf35eb4Sdan if( rc==TCL_OK ){ 13678cf35eb4Sdan Tcl_Obj *pLimit = Tcl_ObjGetVar2(p->interp, pVarname, 0, 0); 13688cf35eb4Sdan rc = Tcl_GetWideIntFromObj(p->interp, pLimit, piLimit); 13698cf35eb4Sdan Tcl_UnsetVar(p->interp, Tcl_GetString(pVarname), 0); 13708cf35eb4Sdan } 13718cf35eb4Sdan 13728cf35eb4Sdan Tcl_DecrRefCount(pEval); 13738cf35eb4Sdan Tcl_DecrRefCount(pVarname); 13748cf35eb4Sdan if( rc!=TCL_OK ) Tcl_BackgroundError(p->interp); 13758cf35eb4Sdan } 13768cf35eb4Sdan 13778cf35eb4Sdan /* 1378ef9659baSdrh ** Destructor for a TCL quota-over-limit callback. 1379ef9659baSdrh */ 1380ef9659baSdrh static void tclCallbackDestructor(void *pObj){ 1381ef9659baSdrh TclQuotaCallback *p = (TclQuotaCallback*)pObj; 1382ef9659baSdrh if( p ){ 1383ef9659baSdrh Tcl_DecrRefCount(p->pScript); 13849fc7702cSdan sqlite3_free((char *)p); 1385ef9659baSdrh } 1386ef9659baSdrh } 1387ef9659baSdrh 1388ef9659baSdrh /* 13898cf35eb4Sdan ** tclcmd: sqlite3_quota_initialize NAME MAKEDEFAULT 13908cf35eb4Sdan */ 13918cf35eb4Sdan static int test_quota_initialize( 13928cf35eb4Sdan void * clientData, 13938cf35eb4Sdan Tcl_Interp *interp, 13948cf35eb4Sdan int objc, 13958cf35eb4Sdan Tcl_Obj *CONST objv[] 13968cf35eb4Sdan ){ 13978cf35eb4Sdan const char *zName; /* Name of new quota VFS */ 13988cf35eb4Sdan int makeDefault; /* True to make the new VFS the default */ 13998cf35eb4Sdan int rc; /* Value returned by quota_initialize() */ 14008cf35eb4Sdan 14018cf35eb4Sdan /* Process arguments */ 14028cf35eb4Sdan if( objc!=3 ){ 14038cf35eb4Sdan Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT"); 14048cf35eb4Sdan return TCL_ERROR; 14058cf35eb4Sdan } 14068cf35eb4Sdan zName = Tcl_GetString(objv[1]); 14078cf35eb4Sdan if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR; 14088cf35eb4Sdan if( zName[0]=='\0' ) zName = 0; 14098cf35eb4Sdan 14108cf35eb4Sdan /* Call sqlite3_quota_initialize() */ 14118cf35eb4Sdan rc = sqlite3_quota_initialize(zName, makeDefault); 14128cf35eb4Sdan Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); 14138cf35eb4Sdan 14148cf35eb4Sdan return TCL_OK; 14158cf35eb4Sdan } 14168cf35eb4Sdan 14178cf35eb4Sdan /* 14188cf35eb4Sdan ** tclcmd: sqlite3_quota_shutdown 14198cf35eb4Sdan */ 14208cf35eb4Sdan static int test_quota_shutdown( 14218cf35eb4Sdan void * clientData, 14228cf35eb4Sdan Tcl_Interp *interp, 14238cf35eb4Sdan int objc, 14248cf35eb4Sdan Tcl_Obj *CONST objv[] 14258cf35eb4Sdan ){ 14268cf35eb4Sdan int rc; /* Value returned by quota_shutdown() */ 14278cf35eb4Sdan 14288cf35eb4Sdan if( objc!=1 ){ 14298cf35eb4Sdan Tcl_WrongNumArgs(interp, 1, objv, ""); 14308cf35eb4Sdan return TCL_ERROR; 14318cf35eb4Sdan } 14328cf35eb4Sdan 14338cf35eb4Sdan /* Call sqlite3_quota_shutdown() */ 14348cf35eb4Sdan rc = sqlite3_quota_shutdown(); 14358cf35eb4Sdan Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); 14368cf35eb4Sdan 14378cf35eb4Sdan return TCL_OK; 14388cf35eb4Sdan } 14398cf35eb4Sdan 14408cf35eb4Sdan /* 14418cf35eb4Sdan ** tclcmd: sqlite3_quota_set PATTERN LIMIT SCRIPT 14428cf35eb4Sdan */ 14438cf35eb4Sdan static int test_quota_set( 14448cf35eb4Sdan void * clientData, 14458cf35eb4Sdan Tcl_Interp *interp, 14468cf35eb4Sdan int objc, 14478cf35eb4Sdan Tcl_Obj *CONST objv[] 14488cf35eb4Sdan ){ 14498cf35eb4Sdan const char *zPattern; /* File pattern to configure */ 14508cf35eb4Sdan sqlite3_int64 iLimit; /* Initial quota in bytes */ 14518cf35eb4Sdan Tcl_Obj *pScript; /* Tcl script to invoke to increase quota */ 14528cf35eb4Sdan int rc; /* Value returned by quota_set() */ 14538cf35eb4Sdan TclQuotaCallback *p; /* Callback object */ 1454ef9659baSdrh int nScript; /* Length of callback script */ 14559fc7702cSdan void (*xDestroy)(void*); /* Optional destructor for pArg */ 145606bfd36aSdan void (*xCallback)(const char *, sqlite3_int64 *, sqlite3_int64, void *); 14578cf35eb4Sdan 14588cf35eb4Sdan /* Process arguments */ 14598cf35eb4Sdan if( objc!=4 ){ 14608cf35eb4Sdan Tcl_WrongNumArgs(interp, 1, objv, "PATTERN LIMIT SCRIPT"); 14618cf35eb4Sdan return TCL_ERROR; 14628cf35eb4Sdan } 14638cf35eb4Sdan zPattern = Tcl_GetString(objv[1]); 14648cf35eb4Sdan if( Tcl_GetWideIntFromObj(interp, objv[2], &iLimit) ) return TCL_ERROR; 14658cf35eb4Sdan pScript = objv[3]; 1466ef9659baSdrh Tcl_GetStringFromObj(pScript, &nScript); 14678cf35eb4Sdan 1468ef9659baSdrh if( nScript>0 ){ 14698cf35eb4Sdan /* Allocate a TclQuotaCallback object */ 14709fc7702cSdan p = (TclQuotaCallback *)sqlite3_malloc(sizeof(TclQuotaCallback)); 14719fc7702cSdan if( !p ){ 14729fc7702cSdan Tcl_SetResult(interp, (char *)"SQLITE_NOMEM", TCL_STATIC); 14739fc7702cSdan return TCL_OK; 14749fc7702cSdan } 14758cf35eb4Sdan memset(p, 0, sizeof(TclQuotaCallback)); 14768cf35eb4Sdan p->interp = interp; 1477ef9659baSdrh Tcl_IncrRefCount(pScript); 14788cf35eb4Sdan p->pScript = pScript; 14799fc7702cSdan xDestroy = tclCallbackDestructor; 148006bfd36aSdan xCallback = tclQuotaCallback; 1481ef9659baSdrh }else{ 1482ef9659baSdrh p = 0; 14839fc7702cSdan xDestroy = 0; 148406bfd36aSdan xCallback = 0; 14858cf35eb4Sdan } 14869fc7702cSdan 1487ef9659baSdrh /* Invoke sqlite3_quota_set() */ 148806bfd36aSdan rc = sqlite3_quota_set(zPattern, iLimit, xCallback, (void*)p, xDestroy); 14898cf35eb4Sdan 14908cf35eb4Sdan Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); 14918cf35eb4Sdan return TCL_OK; 14928cf35eb4Sdan } 14938cf35eb4Sdan 14948cf35eb4Sdan /* 1495b6020c4eSdrh ** tclcmd: sqlite3_quota_file FILENAME 1496b6020c4eSdrh */ 1497b6020c4eSdrh static int test_quota_file( 1498b6020c4eSdrh void * clientData, 1499b6020c4eSdrh Tcl_Interp *interp, 1500b6020c4eSdrh int objc, 1501b6020c4eSdrh Tcl_Obj *CONST objv[] 1502b6020c4eSdrh ){ 1503b6020c4eSdrh const char *zFilename; /* File pattern to configure */ 1504b6020c4eSdrh int rc; /* Value returned by quota_file() */ 1505b6020c4eSdrh 1506b6020c4eSdrh /* Process arguments */ 1507b6020c4eSdrh if( objc!=2 ){ 1508b6020c4eSdrh Tcl_WrongNumArgs(interp, 1, objv, "FILENAME"); 1509b6020c4eSdrh return TCL_ERROR; 1510b6020c4eSdrh } 1511b6020c4eSdrh zFilename = Tcl_GetString(objv[1]); 1512b6020c4eSdrh 1513b6020c4eSdrh /* Invoke sqlite3_quota_file() */ 1514b6020c4eSdrh rc = sqlite3_quota_file(zFilename); 1515b6020c4eSdrh 1516b6020c4eSdrh Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); 1517b6020c4eSdrh return TCL_OK; 1518b6020c4eSdrh } 1519b6020c4eSdrh 1520b6020c4eSdrh /* 1521ef9659baSdrh ** tclcmd: sqlite3_quota_dump 1522ef9659baSdrh */ 1523ef9659baSdrh static int test_quota_dump( 1524ef9659baSdrh void * clientData, 1525ef9659baSdrh Tcl_Interp *interp, 1526ef9659baSdrh int objc, 1527ef9659baSdrh Tcl_Obj *CONST objv[] 1528ef9659baSdrh ){ 1529ef9659baSdrh Tcl_Obj *pResult; 1530ef9659baSdrh Tcl_Obj *pGroupTerm; 1531ef9659baSdrh Tcl_Obj *pFileTerm; 1532ef9659baSdrh quotaGroup *pGroup; 1533ef9659baSdrh quotaFile *pFile; 1534ef9659baSdrh 1535ef9659baSdrh pResult = Tcl_NewObj(); 1536ef9659baSdrh quotaEnter(); 1537ef9659baSdrh for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){ 1538ef9659baSdrh pGroupTerm = Tcl_NewObj(); 1539ef9659baSdrh Tcl_ListObjAppendElement(interp, pGroupTerm, 1540ef9659baSdrh Tcl_NewStringObj(pGroup->zPattern, -1)); 1541ef9659baSdrh Tcl_ListObjAppendElement(interp, pGroupTerm, 1542ef9659baSdrh Tcl_NewWideIntObj(pGroup->iLimit)); 1543ef9659baSdrh Tcl_ListObjAppendElement(interp, pGroupTerm, 1544ef9659baSdrh Tcl_NewWideIntObj(pGroup->iSize)); 154575c19174Sdrh for(pFile=pGroup->pFiles; pFile; pFile=pFile->pNext){ 1546c8ccda60Sdrh int i; 1547c8ccda60Sdrh char zTemp[1000]; 1548ef9659baSdrh pFileTerm = Tcl_NewObj(); 1549c8ccda60Sdrh sqlite3_snprintf(sizeof(zTemp), zTemp, "%s", pFile->zFilename); 1550c8ccda60Sdrh for(i=0; zTemp[i]; i++){ if( zTemp[i]=='\\' ) zTemp[i] = '/'; } 1551ef9659baSdrh Tcl_ListObjAppendElement(interp, pFileTerm, 1552c8ccda60Sdrh Tcl_NewStringObj(zTemp, -1)); 1553ef9659baSdrh Tcl_ListObjAppendElement(interp, pFileTerm, 1554ef9659baSdrh Tcl_NewWideIntObj(pFile->iSize)); 1555ef9659baSdrh Tcl_ListObjAppendElement(interp, pFileTerm, 1556ef9659baSdrh Tcl_NewWideIntObj(pFile->nRef)); 1557796af156Sdrh Tcl_ListObjAppendElement(interp, pFileTerm, 1558796af156Sdrh Tcl_NewWideIntObj(pFile->deleteOnClose)); 1559ef9659baSdrh Tcl_ListObjAppendElement(interp, pGroupTerm, pFileTerm); 1560ef9659baSdrh } 1561ef9659baSdrh Tcl_ListObjAppendElement(interp, pResult, pGroupTerm); 1562ef9659baSdrh } 1563ef9659baSdrh quotaLeave(); 1564ef9659baSdrh Tcl_SetObjResult(interp, pResult); 1565ef9659baSdrh return TCL_OK; 1566ef9659baSdrh } 1567ef9659baSdrh 1568748a52cdSdrh /* 1569a76e8589Sdrh ** tclcmd: sqlite3_quota_fopen FILENAME MODE 1570a76e8589Sdrh */ 1571a76e8589Sdrh static int test_quota_fopen( 1572a76e8589Sdrh void * clientData, 1573a76e8589Sdrh Tcl_Interp *interp, 1574a76e8589Sdrh int objc, 1575a76e8589Sdrh Tcl_Obj *CONST objv[] 1576a76e8589Sdrh ){ 1577a76e8589Sdrh const char *zFilename; /* File pattern to configure */ 1578a76e8589Sdrh const char *zMode; /* Mode string */ 1579a76e8589Sdrh quota_FILE *p; /* Open string object */ 1580a76e8589Sdrh char zReturn[50]; /* Name of pointer to return */ 1581a76e8589Sdrh 1582a76e8589Sdrh /* Process arguments */ 1583a76e8589Sdrh if( objc!=3 ){ 1584a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "FILENAME MODE"); 1585a76e8589Sdrh return TCL_ERROR; 1586a76e8589Sdrh } 1587a76e8589Sdrh zFilename = Tcl_GetString(objv[1]); 1588a76e8589Sdrh zMode = Tcl_GetString(objv[2]); 1589a76e8589Sdrh p = sqlite3_quota_fopen(zFilename, zMode); 1590a76e8589Sdrh sqlite3_snprintf(sizeof(zReturn), zReturn, "%p", p); 1591a76e8589Sdrh Tcl_SetResult(interp, zReturn, TCL_VOLATILE); 1592a76e8589Sdrh return TCL_OK; 1593a76e8589Sdrh } 1594a76e8589Sdrh 1595a76e8589Sdrh /* Defined in test1.c */ 1596a76e8589Sdrh extern void *sqlite3TestTextToPtr(const char*); 1597a76e8589Sdrh 1598a76e8589Sdrh /* 1599a76e8589Sdrh ** tclcmd: sqlite3_quota_fread HANDLE SIZE NELEM 1600a76e8589Sdrh */ 1601a76e8589Sdrh static int test_quota_fread( 1602a76e8589Sdrh void * clientData, 1603a76e8589Sdrh Tcl_Interp *interp, 1604a76e8589Sdrh int objc, 1605a76e8589Sdrh Tcl_Obj *CONST objv[] 1606a76e8589Sdrh ){ 1607a76e8589Sdrh quota_FILE *p; 1608a76e8589Sdrh char *zBuf; 1609a76e8589Sdrh int sz; 1610a76e8589Sdrh int nElem; 161183cc1392Sdrh size_t got; 1612a76e8589Sdrh 1613a76e8589Sdrh if( objc!=4 ){ 1614a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM"); 1615a76e8589Sdrh return TCL_ERROR; 1616a76e8589Sdrh } 1617a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1618a76e8589Sdrh if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR; 1619a76e8589Sdrh if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR; 1620663cebfeSdrh zBuf = (char*)sqlite3_malloc( sz*nElem + 1 ); 1621a76e8589Sdrh if( zBuf==0 ){ 1622a76e8589Sdrh Tcl_SetResult(interp, "out of memory", TCL_STATIC); 1623a76e8589Sdrh return TCL_ERROR; 1624a76e8589Sdrh } 1625a76e8589Sdrh got = sqlite3_quota_fread(zBuf, sz, nElem, p); 1626a76e8589Sdrh if( got<0 ) got = 0; 1627a76e8589Sdrh zBuf[got*sz] = 0; 1628a76e8589Sdrh Tcl_SetResult(interp, zBuf, TCL_VOLATILE); 1629a76e8589Sdrh sqlite3_free(zBuf); 1630a76e8589Sdrh return TCL_OK; 1631a76e8589Sdrh } 1632a76e8589Sdrh 1633a76e8589Sdrh /* 1634a76e8589Sdrh ** tclcmd: sqlite3_quota_fwrite HANDLE SIZE NELEM CONTENT 1635a76e8589Sdrh */ 1636a76e8589Sdrh static int test_quota_fwrite( 1637a76e8589Sdrh void * clientData, 1638a76e8589Sdrh Tcl_Interp *interp, 1639a76e8589Sdrh int objc, 1640a76e8589Sdrh Tcl_Obj *CONST objv[] 1641a76e8589Sdrh ){ 1642a76e8589Sdrh quota_FILE *p; 1643a76e8589Sdrh char *zBuf; 1644a76e8589Sdrh int sz; 1645a76e8589Sdrh int nElem; 164683cc1392Sdrh size_t got; 1647a76e8589Sdrh 1648a76e8589Sdrh if( objc!=5 ){ 1649a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM CONTENT"); 1650a76e8589Sdrh return TCL_ERROR; 1651a76e8589Sdrh } 1652a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1653a76e8589Sdrh if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR; 1654a76e8589Sdrh if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR; 1655a76e8589Sdrh zBuf = Tcl_GetString(objv[4]); 1656a76e8589Sdrh got = sqlite3_quota_fwrite(zBuf, sz, nElem, p); 165783cc1392Sdrh Tcl_SetObjResult(interp, Tcl_NewWideIntObj(got)); 1658a76e8589Sdrh return TCL_OK; 1659a76e8589Sdrh } 1660a76e8589Sdrh 1661a76e8589Sdrh /* 1662a76e8589Sdrh ** tclcmd: sqlite3_quota_fclose HANDLE 1663a76e8589Sdrh */ 1664a76e8589Sdrh static int test_quota_fclose( 1665a76e8589Sdrh void * clientData, 1666a76e8589Sdrh Tcl_Interp *interp, 1667a76e8589Sdrh int objc, 1668a76e8589Sdrh Tcl_Obj *CONST objv[] 1669a76e8589Sdrh ){ 1670a76e8589Sdrh quota_FILE *p; 1671a76e8589Sdrh int rc; 1672a76e8589Sdrh 1673a76e8589Sdrh if( objc!=2 ){ 1674a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1675a76e8589Sdrh return TCL_ERROR; 1676a76e8589Sdrh } 1677a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1678a76e8589Sdrh rc = sqlite3_quota_fclose(p); 1679a76e8589Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 1680a76e8589Sdrh return TCL_OK; 1681a76e8589Sdrh } 1682a76e8589Sdrh 1683a76e8589Sdrh /* 168427cec37dSdrh ** tclcmd: sqlite3_quota_fflush HANDLE ?HARDSYNC? 168569b2232dSdrh */ 168669b2232dSdrh static int test_quota_fflush( 168769b2232dSdrh void * clientData, 168869b2232dSdrh Tcl_Interp *interp, 168969b2232dSdrh int objc, 169069b2232dSdrh Tcl_Obj *CONST objv[] 169169b2232dSdrh ){ 169269b2232dSdrh quota_FILE *p; 169369b2232dSdrh int rc; 169427cec37dSdrh int doSync = 0; 169569b2232dSdrh 169627cec37dSdrh if( objc!=2 && objc!=3 ){ 169727cec37dSdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE ?HARDSYNC?"); 169869b2232dSdrh return TCL_ERROR; 169969b2232dSdrh } 170069b2232dSdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 170127cec37dSdrh if( objc==3 ){ 170227cec37dSdrh if( Tcl_GetBooleanFromObj(interp, objv[2], &doSync) ) return TCL_ERROR; 170327cec37dSdrh } 170427cec37dSdrh rc = sqlite3_quota_fflush(p, doSync); 170569b2232dSdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 170669b2232dSdrh return TCL_OK; 170769b2232dSdrh } 170869b2232dSdrh 170969b2232dSdrh /* 1710a76e8589Sdrh ** tclcmd: sqlite3_quota_fseek HANDLE OFFSET WHENCE 1711a76e8589Sdrh */ 1712a76e8589Sdrh static int test_quota_fseek( 1713a76e8589Sdrh void * clientData, 1714a76e8589Sdrh Tcl_Interp *interp, 1715a76e8589Sdrh int objc, 1716a76e8589Sdrh Tcl_Obj *CONST objv[] 1717a76e8589Sdrh ){ 1718a76e8589Sdrh quota_FILE *p; 1719a76e8589Sdrh int ofst; 1720a76e8589Sdrh const char *zWhence; 1721a76e8589Sdrh int whence; 1722a76e8589Sdrh int rc; 1723a76e8589Sdrh 1724a76e8589Sdrh if( objc!=4 ){ 1725a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE OFFSET WHENCE"); 1726a76e8589Sdrh return TCL_ERROR; 1727a76e8589Sdrh } 1728a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1729a76e8589Sdrh if( Tcl_GetIntFromObj(interp, objv[2], &ofst) ) return TCL_ERROR; 1730a76e8589Sdrh zWhence = Tcl_GetString(objv[3]); 1731a76e8589Sdrh if( strcmp(zWhence, "SEEK_SET")==0 ){ 1732a76e8589Sdrh whence = SEEK_SET; 1733a76e8589Sdrh }else if( strcmp(zWhence, "SEEK_CUR")==0 ){ 1734a76e8589Sdrh whence = SEEK_CUR; 1735a76e8589Sdrh }else if( strcmp(zWhence, "SEEK_END")==0 ){ 1736a76e8589Sdrh whence = SEEK_END; 1737a76e8589Sdrh }else{ 1738a76e8589Sdrh Tcl_AppendResult(interp, 1739a76e8589Sdrh "WHENCE should be SEEK_SET, SEEK_CUR, or SEEK_END", (char*)0); 1740a76e8589Sdrh return TCL_ERROR; 1741a76e8589Sdrh } 1742a76e8589Sdrh rc = sqlite3_quota_fseek(p, ofst, whence); 1743a76e8589Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 1744a76e8589Sdrh return TCL_OK; 1745a76e8589Sdrh } 1746a76e8589Sdrh 1747a76e8589Sdrh /* 1748a76e8589Sdrh ** tclcmd: sqlite3_quota_rewind HANDLE 1749a76e8589Sdrh */ 1750a76e8589Sdrh static int test_quota_rewind( 1751a76e8589Sdrh void * clientData, 1752a76e8589Sdrh Tcl_Interp *interp, 1753a76e8589Sdrh int objc, 1754a76e8589Sdrh Tcl_Obj *CONST objv[] 1755a76e8589Sdrh ){ 1756a76e8589Sdrh quota_FILE *p; 1757a76e8589Sdrh if( objc!=2 ){ 1758a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1759a76e8589Sdrh return TCL_ERROR; 1760a76e8589Sdrh } 1761a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1762a76e8589Sdrh sqlite3_quota_rewind(p); 1763a76e8589Sdrh return TCL_OK; 1764a76e8589Sdrh } 1765a76e8589Sdrh 1766a76e8589Sdrh /* 1767a76e8589Sdrh ** tclcmd: sqlite3_quota_ftell HANDLE 1768a76e8589Sdrh */ 1769a76e8589Sdrh static int test_quota_ftell( 1770a76e8589Sdrh void * clientData, 1771a76e8589Sdrh Tcl_Interp *interp, 1772a76e8589Sdrh int objc, 1773a76e8589Sdrh Tcl_Obj *CONST objv[] 1774a76e8589Sdrh ){ 1775a76e8589Sdrh quota_FILE *p; 1776a76e8589Sdrh sqlite3_int64 x; 1777a76e8589Sdrh if( objc!=2 ){ 1778a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1779a76e8589Sdrh return TCL_ERROR; 1780a76e8589Sdrh } 1781a76e8589Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1782a76e8589Sdrh x = sqlite3_quota_ftell(p); 1783a76e8589Sdrh Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); 1784a76e8589Sdrh return TCL_OK; 1785a76e8589Sdrh } 1786a76e8589Sdrh 1787a76e8589Sdrh /* 1788c00ce490Sdrh ** tclcmd: sqlite3_quota_ftruncate HANDLE SIZE 1789c00ce490Sdrh */ 1790c00ce490Sdrh static int test_quota_ftruncate( 1791c00ce490Sdrh void * clientData, 1792c00ce490Sdrh Tcl_Interp *interp, 1793c00ce490Sdrh int objc, 1794c00ce490Sdrh Tcl_Obj *CONST objv[] 1795c00ce490Sdrh ){ 1796c00ce490Sdrh quota_FILE *p; 1797c00ce490Sdrh sqlite3_int64 x; 1798c00ce490Sdrh Tcl_WideInt w; 1799c00ce490Sdrh int rc; 1800c00ce490Sdrh if( objc!=3 ){ 1801c00ce490Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE"); 1802c00ce490Sdrh return TCL_ERROR; 1803c00ce490Sdrh } 1804c00ce490Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1805c00ce490Sdrh if( Tcl_GetWideIntFromObj(interp, objv[2], &w) ) return TCL_ERROR; 1806c00ce490Sdrh x = (sqlite3_int64)w; 1807c00ce490Sdrh rc = sqlite3_quota_ftruncate(p, x); 1808c00ce490Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 1809c00ce490Sdrh return TCL_OK; 1810c00ce490Sdrh } 1811c00ce490Sdrh 1812c00ce490Sdrh /* 1813c00ce490Sdrh ** tclcmd: sqlite3_quota_file_size HANDLE 1814c00ce490Sdrh */ 1815c00ce490Sdrh static int test_quota_file_size( 1816c00ce490Sdrh void * clientData, 1817c00ce490Sdrh Tcl_Interp *interp, 1818c00ce490Sdrh int objc, 1819c00ce490Sdrh Tcl_Obj *CONST objv[] 1820c00ce490Sdrh ){ 1821c00ce490Sdrh quota_FILE *p; 1822c00ce490Sdrh sqlite3_int64 x; 1823c00ce490Sdrh if( objc!=2 ){ 1824c00ce490Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1825c00ce490Sdrh return TCL_ERROR; 1826c00ce490Sdrh } 1827c00ce490Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1828c00ce490Sdrh x = sqlite3_quota_file_size(p); 1829c00ce490Sdrh Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); 1830c00ce490Sdrh return TCL_OK; 1831c00ce490Sdrh } 1832c00ce490Sdrh 1833c00ce490Sdrh /* 1834c00ce490Sdrh ** tclcmd: sqlite3_quota_file_truesize HANDLE 1835c00ce490Sdrh */ 1836c00ce490Sdrh static int test_quota_file_truesize( 1837c00ce490Sdrh void * clientData, 1838c00ce490Sdrh Tcl_Interp *interp, 1839c00ce490Sdrh int objc, 1840c00ce490Sdrh Tcl_Obj *CONST objv[] 1841c00ce490Sdrh ){ 1842c00ce490Sdrh quota_FILE *p; 1843c00ce490Sdrh sqlite3_int64 x; 1844c00ce490Sdrh if( objc!=2 ){ 1845c00ce490Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1846c00ce490Sdrh return TCL_ERROR; 1847c00ce490Sdrh } 1848c00ce490Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1849c00ce490Sdrh x = sqlite3_quota_file_truesize(p); 1850c00ce490Sdrh Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); 1851c00ce490Sdrh return TCL_OK; 1852c00ce490Sdrh } 1853c00ce490Sdrh 1854c00ce490Sdrh /* 1855c00ce490Sdrh ** tclcmd: sqlite3_quota_file_mtime HANDLE 1856c00ce490Sdrh */ 1857c00ce490Sdrh static int test_quota_file_mtime( 1858c00ce490Sdrh void * clientData, 1859c00ce490Sdrh Tcl_Interp *interp, 1860c00ce490Sdrh int objc, 1861c00ce490Sdrh Tcl_Obj *CONST objv[] 1862c00ce490Sdrh ){ 1863c00ce490Sdrh quota_FILE *p; 1864c00ce490Sdrh time_t t; 1865c00ce490Sdrh if( objc!=2 ){ 1866c00ce490Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1867c00ce490Sdrh return TCL_ERROR; 1868c00ce490Sdrh } 1869c00ce490Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1870c00ce490Sdrh t = 0; 1871c00ce490Sdrh sqlite3_quota_file_mtime(p, &t); 1872c00ce490Sdrh Tcl_SetObjResult(interp, Tcl_NewWideIntObj(t)); 1873c00ce490Sdrh return TCL_OK; 1874c00ce490Sdrh } 1875c00ce490Sdrh 1876c00ce490Sdrh 1877c00ce490Sdrh /* 1878a76e8589Sdrh ** tclcmd: sqlite3_quota_remove FILENAME 1879a76e8589Sdrh */ 1880a76e8589Sdrh static int test_quota_remove( 1881a76e8589Sdrh void * clientData, 1882a76e8589Sdrh Tcl_Interp *interp, 1883a76e8589Sdrh int objc, 1884a76e8589Sdrh Tcl_Obj *CONST objv[] 1885a76e8589Sdrh ){ 1886a76e8589Sdrh const char *zFilename; /* File pattern to configure */ 1887a76e8589Sdrh int rc; 1888a76e8589Sdrh if( objc!=2 ){ 1889a76e8589Sdrh Tcl_WrongNumArgs(interp, 1, objv, "FILENAME"); 1890a76e8589Sdrh return TCL_ERROR; 1891a76e8589Sdrh } 1892a76e8589Sdrh zFilename = Tcl_GetString(objv[1]); 1893a76e8589Sdrh rc = sqlite3_quota_remove(zFilename); 1894a76e8589Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 1895a76e8589Sdrh return TCL_OK; 1896a76e8589Sdrh } 1897a76e8589Sdrh 1898a76e8589Sdrh /* 1899c8ccda60Sdrh ** tclcmd: sqlite3_quota_glob PATTERN TEXT 1900c8ccda60Sdrh ** 1901c8ccda60Sdrh ** Test the glob pattern matching. Return 1 if TEXT matches PATTERN 1902c8ccda60Sdrh ** and return 0 if it does not. 1903c8ccda60Sdrh */ 1904c8ccda60Sdrh static int test_quota_glob( 1905c8ccda60Sdrh void * clientData, 1906c8ccda60Sdrh Tcl_Interp *interp, 1907c8ccda60Sdrh int objc, 1908c8ccda60Sdrh Tcl_Obj *CONST objv[] 1909c8ccda60Sdrh ){ 1910c8ccda60Sdrh const char *zPattern; /* The glob pattern */ 1911c8ccda60Sdrh const char *zText; /* Text to compare agains the pattern */ 1912c8ccda60Sdrh int rc; 1913c8ccda60Sdrh if( objc!=3 ){ 1914c8ccda60Sdrh Tcl_WrongNumArgs(interp, 1, objv, "PATTERN TEXT"); 1915c8ccda60Sdrh return TCL_ERROR; 1916c8ccda60Sdrh } 1917c8ccda60Sdrh zPattern = Tcl_GetString(objv[1]); 1918c8ccda60Sdrh zText = Tcl_GetString(objv[2]); 1919c8ccda60Sdrh rc = quotaStrglob(zPattern, zText); 1920c8ccda60Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 1921c8ccda60Sdrh return TCL_OK; 1922c8ccda60Sdrh } 1923c8ccda60Sdrh 1924c8ccda60Sdrh /* 1925*98c78ea6Sdrh ** tclcmd: sqlite3_quota_file_available HANDLE 1926*98c78ea6Sdrh ** 1927*98c78ea6Sdrh ** Return the number of bytes from the current file point to the end of 1928*98c78ea6Sdrh ** the file. 1929*98c78ea6Sdrh */ 1930*98c78ea6Sdrh static int test_quota_file_available( 1931*98c78ea6Sdrh void * clientData, 1932*98c78ea6Sdrh Tcl_Interp *interp, 1933*98c78ea6Sdrh int objc, 1934*98c78ea6Sdrh Tcl_Obj *CONST objv[] 1935*98c78ea6Sdrh ){ 1936*98c78ea6Sdrh quota_FILE *p; 1937*98c78ea6Sdrh sqlite3_int64 x; 1938*98c78ea6Sdrh if( objc!=2 ){ 1939*98c78ea6Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1940*98c78ea6Sdrh return TCL_ERROR; 1941*98c78ea6Sdrh } 1942*98c78ea6Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1943*98c78ea6Sdrh x = sqlite3_quota_file_available(p); 1944*98c78ea6Sdrh Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); 1945*98c78ea6Sdrh return TCL_OK; 1946*98c78ea6Sdrh } 1947*98c78ea6Sdrh 1948*98c78ea6Sdrh /* 1949*98c78ea6Sdrh ** tclcmd: sqlite3_quota_ferror HANDLE 1950*98c78ea6Sdrh ** 1951*98c78ea6Sdrh ** Return true if the file handle is in the error state. 1952*98c78ea6Sdrh */ 1953*98c78ea6Sdrh static int test_quota_ferror( 1954*98c78ea6Sdrh void * clientData, 1955*98c78ea6Sdrh Tcl_Interp *interp, 1956*98c78ea6Sdrh int objc, 1957*98c78ea6Sdrh Tcl_Obj *CONST objv[] 1958*98c78ea6Sdrh ){ 1959*98c78ea6Sdrh quota_FILE *p; 1960*98c78ea6Sdrh int x; 1961*98c78ea6Sdrh if( objc!=2 ){ 1962*98c78ea6Sdrh Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); 1963*98c78ea6Sdrh return TCL_ERROR; 1964*98c78ea6Sdrh } 1965*98c78ea6Sdrh p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 1966*98c78ea6Sdrh x = sqlite3_quota_ferror(p); 1967*98c78ea6Sdrh Tcl_SetObjResult(interp, Tcl_NewIntObj(x)); 1968*98c78ea6Sdrh return TCL_OK; 1969*98c78ea6Sdrh } 1970*98c78ea6Sdrh 1971*98c78ea6Sdrh /* 1972748a52cdSdrh ** This routine registers the custom TCL commands defined in this 1973748a52cdSdrh ** module. This should be the only procedure visible from outside 1974748a52cdSdrh ** of this module. 1975748a52cdSdrh */ 1976748a52cdSdrh int Sqlitequota_Init(Tcl_Interp *interp){ 19778cf35eb4Sdan static struct { 19788cf35eb4Sdan char *zName; 19798cf35eb4Sdan Tcl_ObjCmdProc *xProc; 19808cf35eb4Sdan } aCmd[] = { 19818cf35eb4Sdan { "sqlite3_quota_initialize", test_quota_initialize }, 19828cf35eb4Sdan { "sqlite3_quota_shutdown", test_quota_shutdown }, 19838cf35eb4Sdan { "sqlite3_quota_set", test_quota_set }, 1984b6020c4eSdrh { "sqlite3_quota_file", test_quota_file }, 1985ef9659baSdrh { "sqlite3_quota_dump", test_quota_dump }, 1986a76e8589Sdrh { "sqlite3_quota_fopen", test_quota_fopen }, 1987a76e8589Sdrh { "sqlite3_quota_fread", test_quota_fread }, 1988a76e8589Sdrh { "sqlite3_quota_fwrite", test_quota_fwrite }, 1989a76e8589Sdrh { "sqlite3_quota_fclose", test_quota_fclose }, 199069b2232dSdrh { "sqlite3_quota_fflush", test_quota_fflush }, 1991a76e8589Sdrh { "sqlite3_quota_fseek", test_quota_fseek }, 1992a76e8589Sdrh { "sqlite3_quota_rewind", test_quota_rewind }, 1993a76e8589Sdrh { "sqlite3_quota_ftell", test_quota_ftell }, 1994c00ce490Sdrh { "sqlite3_quota_ftruncate", test_quota_ftruncate }, 1995c00ce490Sdrh { "sqlite3_quota_file_size", test_quota_file_size }, 1996c00ce490Sdrh { "sqlite3_quota_file_truesize", test_quota_file_truesize }, 1997c00ce490Sdrh { "sqlite3_quota_file_mtime", test_quota_file_mtime }, 1998a76e8589Sdrh { "sqlite3_quota_remove", test_quota_remove }, 1999c8ccda60Sdrh { "sqlite3_quota_glob", test_quota_glob }, 2000*98c78ea6Sdrh { "sqlite3_quota_file_available",test_quota_file_available }, 2001*98c78ea6Sdrh { "sqlite3_quota_ferror", test_quota_ferror }, 20028cf35eb4Sdan }; 20038cf35eb4Sdan int i; 20048cf35eb4Sdan 20058cf35eb4Sdan for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){ 20068cf35eb4Sdan Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0); 20078cf35eb4Sdan } 20088cf35eb4Sdan 2009748a52cdSdrh return TCL_OK; 2010748a52cdSdrh } 2011748a52cdSdrh #endif 2012