restructure to allow non-amalgamated builds again
[sqlcipher.git] / src / test_quota.c
blob38dc36fdc23835d878c4cdc3269781ec0afc7787
1 /*
2 ** 2010 September 31
3 **
4 ** The author disclaims copyright to this source code. In place of
5 ** a legal notice, here is a blessing:
6 **
7 ** May you do good and not evil.
8 ** May you find forgiveness for yourself and forgive others.
9 ** May you share freely, never taking more than you give.
11 *************************************************************************
13 ** This file contains a VFS "shim" - a layer that sits in between the
14 ** pager and the real VFS.
16 ** This particular shim enforces a quota system on files. One or more
17 ** database files are in a "quota group" that is defined by a GLOB
18 ** pattern. A quota is set for the combined size of all files in the
19 ** the group. A quota of zero means "no limit". If the total size
20 ** of all files in the quota group is greater than the limit, then
21 ** write requests that attempt to enlarge a file fail with SQLITE_FULL.
23 ** However, before returning SQLITE_FULL, the write requests invoke
24 ** a callback function that is configurable for each quota group.
25 ** This callback has the opportunity to enlarge the quota. If the
26 ** callback does enlarge the quota such that the total size of all
27 ** files within the group is less than the new quota, then the write
28 ** continues as if nothing had happened.
30 #include "test_quota.h"
31 #include <string.h>
32 #include <assert.h>
35 ** For an build without mutexes, no-op the mutex calls.
37 #if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0
38 #define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8)
39 #define sqlite3_mutex_free(X)
40 #define sqlite3_mutex_enter(X)
41 #define sqlite3_mutex_try(X) SQLITE_OK
42 #define sqlite3_mutex_leave(X)
43 #define sqlite3_mutex_held(X) ((void)(X),1)
44 #define sqlite3_mutex_notheld(X) ((void)(X),1)
45 #endif /* SQLITE_THREADSAFE==0 */
49 ** Figure out if we are dealing with Unix, Windows, or some other
50 ** operating system. After the following block of preprocess macros,
51 ** all of SQLITE_OS_UNIX, SQLITE_OS_WIN, SQLITE_OS_OS2, and SQLITE_OS_OTHER
52 ** will defined to either 1 or 0. One of the four will be 1. The other
53 ** three will be 0.
55 #if defined(SQLITE_OS_OTHER)
56 # if SQLITE_OS_OTHER==1
57 # undef SQLITE_OS_UNIX
58 # define SQLITE_OS_UNIX 0
59 # undef SQLITE_OS_WIN
60 # define SQLITE_OS_WIN 0
61 # undef SQLITE_OS_OS2
62 # define SQLITE_OS_OS2 0
63 # else
64 # undef SQLITE_OS_OTHER
65 # endif
66 #endif
67 #if !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_OTHER)
68 # define SQLITE_OS_OTHER 0
69 # ifndef SQLITE_OS_WIN
70 # if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) \
71 || defined(__MINGW32__) || defined(__BORLANDC__)
72 # define SQLITE_OS_WIN 1
73 # define SQLITE_OS_UNIX 0
74 # define SQLITE_OS_OS2 0
75 # elif defined(__EMX__) || defined(_OS2) || defined(OS2) \
76 || defined(_OS2_) || defined(__OS2__)
77 # define SQLITE_OS_WIN 0
78 # define SQLITE_OS_UNIX 0
79 # define SQLITE_OS_OS2 1
80 # else
81 # define SQLITE_OS_WIN 0
82 # define SQLITE_OS_UNIX 1
83 # define SQLITE_OS_OS2 0
84 # endif
85 # else
86 # define SQLITE_OS_UNIX 0
87 # define SQLITE_OS_OS2 0
88 # endif
89 #else
90 # ifndef SQLITE_OS_WIN
91 # define SQLITE_OS_WIN 0
92 # endif
93 #endif
95 #if SQLITE_OS_UNIX
96 # include <unistd.h>
97 #endif
98 #if SQLITE_OS_WIN
99 # include <windows.h>
100 # include <io.h>
101 #endif
104 /************************ Object Definitions ******************************/
106 /* Forward declaration of all object types */
107 typedef struct quotaGroup quotaGroup;
108 typedef struct quotaConn quotaConn;
109 typedef struct quotaFile quotaFile;
112 ** A "quota group" is a collection of files whose collective size we want
113 ** to limit. Each quota group is defined by a GLOB pattern.
115 ** There is an instance of the following object for each defined quota
116 ** group. This object records the GLOB pattern that defines which files
117 ** belong to the quota group. The object also remembers the size limit
118 ** for the group (the quota) and the callback to be invoked when the
119 ** sum of the sizes of the files within the group goes over the limit.
121 ** A quota group must be established (using sqlite3_quota_set(...))
122 ** prior to opening any of the database connections that access files
123 ** within the quota group.
125 struct quotaGroup {
126 const char *zPattern; /* Filename pattern to be quotaed */
127 sqlite3_int64 iLimit; /* Upper bound on total file size */
128 sqlite3_int64 iSize; /* Current size of all files */
129 void (*xCallback)( /* Callback invoked when going over quota */
130 const char *zFilename, /* Name of file whose size increases */
131 sqlite3_int64 *piLimit, /* IN/OUT: The current limit */
132 sqlite3_int64 iSize, /* Total size of all files in the group */
133 void *pArg /* Client data */
135 void *pArg; /* Third argument to the xCallback() */
136 void (*xDestroy)(void*); /* Optional destructor for pArg */
137 quotaGroup *pNext, **ppPrev; /* Doubly linked list of all quota objects */
138 quotaFile *pFiles; /* Files within this group */
142 ** An instance of this structure represents a single file that is part
143 ** of a quota group. A single file can be opened multiple times. In
144 ** order keep multiple openings of the same file from causing the size
145 ** of the file to count against the quota multiple times, each file
146 ** has a unique instance of this object and multiple open connections
147 ** to the same file each point to a single instance of this object.
149 struct quotaFile {
150 char *zFilename; /* Name of this file */
151 quotaGroup *pGroup; /* Quota group to which this file belongs */
152 sqlite3_int64 iSize; /* Current size of this file */
153 int nRef; /* Number of times this file is open */
154 int deleteOnClose; /* True to delete this file when it closes */
155 quotaFile *pNext, **ppPrev; /* Linked list of files in the same group */
159 ** An instance of the following object represents each open connection
160 ** to a file that participates in quota tracking. This object is a
161 ** subclass of sqlite3_file. The sqlite3_file object for the underlying
162 ** VFS is appended to this structure.
164 struct quotaConn {
165 sqlite3_file base; /* Base class - must be first */
166 quotaFile *pFile; /* The underlying file */
167 /* The underlying VFS sqlite3_file is appended to this object */
171 ** An instance of the following object records the state of an
172 ** open file. This object is opaque to all users - the internal
173 ** structure is only visible to the functions below.
175 struct quota_FILE {
176 FILE *f; /* Open stdio file pointer */
177 sqlite3_int64 iOfst; /* Current offset into the file */
178 quotaFile *pFile; /* The file record in the quota system */
179 #if SQLITE_OS_WIN
180 char *zMbcsName; /* Full MBCS pathname of the file */
181 #endif
185 /************************* Global Variables **********************************/
187 ** All global variables used by this file are containing within the following
188 ** gQuota structure.
190 static struct {
191 /* The pOrigVfs is the real, original underlying VFS implementation.
192 ** Most operations pass-through to the real VFS. This value is read-only
193 ** during operation. It is only modified at start-time and thus does not
194 ** require a mutex.
196 sqlite3_vfs *pOrigVfs;
198 /* The sThisVfs is the VFS structure used by this shim. It is initialized
199 ** at start-time and thus does not require a mutex
201 sqlite3_vfs sThisVfs;
203 /* The sIoMethods defines the methods used by sqlite3_file objects
204 ** associated with this shim. It is initialized at start-time and does
205 ** not require a mutex.
207 ** When the underlying VFS is called to open a file, it might return
208 ** either a version 1 or a version 2 sqlite3_file object. This shim
209 ** has to create a wrapper sqlite3_file of the same version. Hence
210 ** there are two I/O method structures, one for version 1 and the other
211 ** for version 2.
213 sqlite3_io_methods sIoMethodsV1;
214 sqlite3_io_methods sIoMethodsV2;
216 /* True when this shim as been initialized.
218 int isInitialized;
220 /* For run-time access any of the other global data structures in this
221 ** shim, the following mutex must be held.
223 sqlite3_mutex *pMutex;
225 /* List of quotaGroup objects.
227 quotaGroup *pGroup;
229 } gQuota;
231 /************************* Utility Routines *********************************/
233 ** Acquire and release the mutex used to serialize access to the
234 ** list of quotaGroups.
236 static void quotaEnter(void){ sqlite3_mutex_enter(gQuota.pMutex); }
237 static void quotaLeave(void){ sqlite3_mutex_leave(gQuota.pMutex); }
239 /* Count the number of open files in a quotaGroup
241 static int quotaGroupOpenFileCount(quotaGroup *pGroup){
242 int N = 0;
243 quotaFile *pFile = pGroup->pFiles;
244 while( pFile ){
245 if( pFile->nRef ) N++;
246 pFile = pFile->pNext;
248 return N;
251 /* Remove a file from a quota group.
253 static void quotaRemoveFile(quotaFile *pFile){
254 quotaGroup *pGroup = pFile->pGroup;
255 pGroup->iSize -= pFile->iSize;
256 *pFile->ppPrev = pFile->pNext;
257 if( pFile->pNext ) pFile->pNext->ppPrev = pFile->ppPrev;
258 sqlite3_free(pFile);
261 /* Remove all files from a quota group. It is always the case that
262 ** all files will be closed when this routine is called.
264 static void quotaRemoveAllFiles(quotaGroup *pGroup){
265 while( pGroup->pFiles ){
266 assert( pGroup->pFiles->nRef==0 );
267 quotaRemoveFile(pGroup->pFiles);
272 /* If the reference count and threshold for a quotaGroup are both
273 ** zero, then destroy the quotaGroup.
275 static void quotaGroupDeref(quotaGroup *pGroup){
276 if( pGroup->iLimit==0 && quotaGroupOpenFileCount(pGroup)==0 ){
277 quotaRemoveAllFiles(pGroup);
278 *pGroup->ppPrev = pGroup->pNext;
279 if( pGroup->pNext ) pGroup->pNext->ppPrev = pGroup->ppPrev;
280 if( pGroup->xDestroy ) pGroup->xDestroy(pGroup->pArg);
281 sqlite3_free(pGroup);
286 ** Return TRUE if string z matches glob pattern zGlob.
288 ** Globbing rules:
290 ** '*' Matches any sequence of zero or more characters.
292 ** '?' Matches exactly one character.
294 ** [...] Matches one character from the enclosed list of
295 ** characters.
297 ** [^...] Matches one character not in the enclosed list.
299 ** / Matches "/" or "\\"
302 static int quotaStrglob(const char *zGlob, const char *z){
303 int c, c2, cx;
304 int invert;
305 int seen;
307 while( (c = (*(zGlob++)))!=0 ){
308 if( c=='*' ){
309 while( (c=(*(zGlob++))) == '*' || c=='?' ){
310 if( c=='?' && (*(z++))==0 ) return 0;
312 if( c==0 ){
313 return 1;
314 }else if( c=='[' ){
315 while( *z && quotaStrglob(zGlob-1,z)==0 ){
316 z++;
318 return (*z)!=0;
320 cx = (c=='/') ? '\\' : c;
321 while( (c2 = (*(z++)))!=0 ){
322 while( c2!=c && c2!=cx ){
323 c2 = *(z++);
324 if( c2==0 ) return 0;
326 if( quotaStrglob(zGlob,z) ) return 1;
328 return 0;
329 }else if( c=='?' ){
330 if( (*(z++))==0 ) return 0;
331 }else if( c=='[' ){
332 int prior_c = 0;
333 seen = 0;
334 invert = 0;
335 c = *(z++);
336 if( c==0 ) return 0;
337 c2 = *(zGlob++);
338 if( c2=='^' ){
339 invert = 1;
340 c2 = *(zGlob++);
342 if( c2==']' ){
343 if( c==']' ) seen = 1;
344 c2 = *(zGlob++);
346 while( c2 && c2!=']' ){
347 if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
348 c2 = *(zGlob++);
349 if( c>=prior_c && c<=c2 ) seen = 1;
350 prior_c = 0;
351 }else{
352 if( c==c2 ){
353 seen = 1;
355 prior_c = c2;
357 c2 = *(zGlob++);
359 if( c2==0 || (seen ^ invert)==0 ) return 0;
360 }else if( c=='/' ){
361 if( z[0]!='/' && z[0]!='\\' ) return 0;
362 z++;
363 }else{
364 if( c!=(*(z++)) ) return 0;
367 return *z==0;
371 /* Find a quotaGroup given the filename.
373 ** Return a pointer to the quotaGroup object. Return NULL if not found.
375 static quotaGroup *quotaGroupFind(const char *zFilename){
376 quotaGroup *p;
377 for(p=gQuota.pGroup; p && quotaStrglob(p->zPattern, zFilename)==0;
378 p=p->pNext){}
379 return p;
382 /* Translate an sqlite3_file* that is really a quotaConn* into
383 ** the sqlite3_file* for the underlying original VFS.
385 static sqlite3_file *quotaSubOpen(sqlite3_file *pConn){
386 quotaConn *p = (quotaConn*)pConn;
387 return (sqlite3_file*)&p[1];
390 /* Find a file in a quota group and return a pointer to that file.
391 ** Return NULL if the file is not in the group.
393 static quotaFile *quotaFindFile(
394 quotaGroup *pGroup, /* Group in which to look for the file */
395 const char *zName, /* Full pathname of the file */
396 int createFlag /* Try to create the file if not found */
398 quotaFile *pFile = pGroup->pFiles;
399 while( pFile && strcmp(pFile->zFilename, zName)!=0 ){
400 pFile = pFile->pNext;
402 if( pFile==0 && createFlag ){
403 int nName = (int)(strlen(zName) & 0x3fffffff);
404 pFile = (quotaFile *)sqlite3_malloc( sizeof(*pFile) + nName + 1 );
405 if( pFile ){
406 memset(pFile, 0, sizeof(*pFile));
407 pFile->zFilename = (char*)&pFile[1];
408 memcpy(pFile->zFilename, zName, nName+1);
409 pFile->pNext = pGroup->pFiles;
410 if( pGroup->pFiles ) pGroup->pFiles->ppPrev = &pFile->pNext;
411 pFile->ppPrev = &pGroup->pFiles;
412 pGroup->pFiles = pFile;
413 pFile->pGroup = pGroup;
416 return pFile;
419 ** Translate UTF8 to MBCS for use in fopen() calls. Return a pointer to the
420 ** translated text.. Call quota_mbcs_free() to deallocate any memory
421 ** used to store the returned pointer when done.
423 static char *quota_utf8_to_mbcs(const char *zUtf8){
424 #if SQLITE_OS_WIN
425 size_t n; /* Bytes in zUtf8 */
426 int nWide; /* number of UTF-16 characters */
427 int nMbcs; /* Bytes of MBCS */
428 LPWSTR zTmpWide; /* The UTF16 text */
429 char *zMbcs; /* The MBCS text */
430 int codepage; /* Code page used by fopen() */
432 n = strlen(zUtf8);
433 nWide = MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, NULL, 0);
434 if( nWide==0 ) return 0;
435 zTmpWide = (LPWSTR)sqlite3_malloc( (nWide+1)*sizeof(zTmpWide[0]) );
436 if( zTmpWide==0 ) return 0;
437 MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zTmpWide, nWide);
438 codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;
439 nMbcs = WideCharToMultiByte(codepage, 0, zTmpWide, nWide, 0, 0, 0, 0);
440 zMbcs = nMbcs ? (char*)sqlite3_malloc( nMbcs+1 ) : 0;
441 if( zMbcs ){
442 WideCharToMultiByte(codepage, 0, zTmpWide, nWide, zMbcs, nMbcs, 0, 0);
444 sqlite3_free(zTmpWide);
445 return zMbcs;
446 #else
447 return (char*)zUtf8; /* No-op on unix */
448 #endif
452 ** Deallocate any memory allocated by quota_utf8_to_mbcs().
454 static void quota_mbcs_free(char *zOld){
455 #if SQLITE_OS_WIN
456 sqlite3_free(zOld);
457 #else
458 /* No-op on unix */
459 #endif
462 /************************* VFS Method Wrappers *****************************/
464 ** This is the xOpen method used for the "quota" VFS.
466 ** Most of the work is done by the underlying original VFS. This method
467 ** simply links the new file into the appropriate quota group if it is a
468 ** file that needs to be tracked.
470 static int quotaOpen(
471 sqlite3_vfs *pVfs, /* The quota VFS */
472 const char *zName, /* Name of file to be opened */
473 sqlite3_file *pConn, /* Fill in this file descriptor */
474 int flags, /* Flags to control the opening */
475 int *pOutFlags /* Flags showing results of opening */
477 int rc; /* Result code */
478 quotaConn *pQuotaOpen; /* The new quota file descriptor */
479 quotaFile *pFile; /* Corresponding quotaFile obj */
480 quotaGroup *pGroup; /* The group file belongs to */
481 sqlite3_file *pSubOpen; /* Real file descriptor */
482 sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs; /* Real VFS */
484 /* If the file is not a main database file or a WAL, then use the
485 ** normal xOpen method.
487 if( (flags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_WAL))==0 ){
488 return pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags);
491 /* If the name of the file does not match any quota group, then
492 ** use the normal xOpen method.
494 quotaEnter();
495 pGroup = quotaGroupFind(zName);
496 if( pGroup==0 ){
497 rc = pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags);
498 }else{
499 /* If we get to this point, it means the file needs to be quota tracked.
501 pQuotaOpen = (quotaConn*)pConn;
502 pSubOpen = quotaSubOpen(pConn);
503 rc = pOrigVfs->xOpen(pOrigVfs, zName, pSubOpen, flags, pOutFlags);
504 if( rc==SQLITE_OK ){
505 pFile = quotaFindFile(pGroup, zName, 1);
506 if( pFile==0 ){
507 quotaLeave();
508 pSubOpen->pMethods->xClose(pSubOpen);
509 return SQLITE_NOMEM;
511 pFile->deleteOnClose = (flags & SQLITE_OPEN_DELETEONCLOSE)!=0;
512 pFile->nRef++;
513 pQuotaOpen->pFile = pFile;
514 if( pSubOpen->pMethods->iVersion==1 ){
515 pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV1;
516 }else{
517 pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV2;
521 quotaLeave();
522 return rc;
526 ** This is the xDelete method used for the "quota" VFS.
528 ** If the file being deleted is part of the quota group, then reduce
529 ** the size of the quota group accordingly. And remove the file from
530 ** the set of files in the quota group.
532 static int quotaDelete(
533 sqlite3_vfs *pVfs, /* The quota VFS */
534 const char *zName, /* Name of file to be deleted */
535 int syncDir /* Do a directory sync after deleting */
537 int rc; /* Result code */
538 quotaFile *pFile; /* Files in the quota */
539 quotaGroup *pGroup; /* The group file belongs to */
540 sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs; /* Real VFS */
542 /* Do the actual file delete */
543 rc = pOrigVfs->xDelete(pOrigVfs, zName, syncDir);
545 /* If the file just deleted is a member of a quota group, then remove
546 ** it from that quota group.
548 if( rc==SQLITE_OK ){
549 quotaEnter();
550 pGroup = quotaGroupFind(zName);
551 if( pGroup ){
552 pFile = quotaFindFile(pGroup, zName, 0);
553 if( pFile ){
554 if( pFile->nRef ){
555 pFile->deleteOnClose = 1;
556 }else{
557 quotaRemoveFile(pFile);
558 quotaGroupDeref(pGroup);
562 quotaLeave();
564 return rc;
568 /************************ I/O Method Wrappers *******************************/
570 /* xClose requests get passed through to the original VFS. But we
571 ** also have to unlink the quotaConn from the quotaFile and quotaGroup.
572 ** The quotaFile and/or quotaGroup are freed if they are no longer in use.
574 static int quotaClose(sqlite3_file *pConn){
575 quotaConn *p = (quotaConn*)pConn;
576 quotaFile *pFile = p->pFile;
577 sqlite3_file *pSubOpen = quotaSubOpen(pConn);
578 int rc;
579 rc = pSubOpen->pMethods->xClose(pSubOpen);
580 quotaEnter();
581 pFile->nRef--;
582 if( pFile->nRef==0 ){
583 quotaGroup *pGroup = pFile->pGroup;
584 if( pFile->deleteOnClose ){
585 gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0);
586 quotaRemoveFile(pFile);
588 quotaGroupDeref(pGroup);
590 quotaLeave();
591 return rc;
594 /* Pass xRead requests directory thru to the original VFS without
595 ** further processing.
597 static int quotaRead(
598 sqlite3_file *pConn,
599 void *pBuf,
600 int iAmt,
601 sqlite3_int64 iOfst
603 sqlite3_file *pSubOpen = quotaSubOpen(pConn);
604 return pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst);
607 /* Check xWrite requests to see if they expand the file. If they do,
608 ** the perform a quota check before passing them through to the
609 ** original VFS.
611 static int quotaWrite(
612 sqlite3_file *pConn,
613 const void *pBuf,
614 int iAmt,
615 sqlite3_int64 iOfst
617 quotaConn *p = (quotaConn*)pConn;
618 sqlite3_file *pSubOpen = quotaSubOpen(pConn);
619 sqlite3_int64 iEnd = iOfst+iAmt;
620 quotaGroup *pGroup;
621 quotaFile *pFile = p->pFile;
622 sqlite3_int64 szNew;
624 if( pFile->iSize<iEnd ){
625 pGroup = pFile->pGroup;
626 quotaEnter();
627 szNew = pGroup->iSize - pFile->iSize + iEnd;
628 if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
629 if( pGroup->xCallback ){
630 pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew,
631 pGroup->pArg);
633 if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
634 quotaLeave();
635 return SQLITE_FULL;
638 pGroup->iSize = szNew;
639 pFile->iSize = iEnd;
640 quotaLeave();
642 return pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst);
645 /* Pass xTruncate requests thru to the original VFS. If the
646 ** success, update the file size.
648 static int quotaTruncate(sqlite3_file *pConn, sqlite3_int64 size){
649 quotaConn *p = (quotaConn*)pConn;
650 sqlite3_file *pSubOpen = quotaSubOpen(pConn);
651 int rc = pSubOpen->pMethods->xTruncate(pSubOpen, size);
652 quotaFile *pFile = p->pFile;
653 quotaGroup *pGroup;
654 if( rc==SQLITE_OK ){
655 quotaEnter();
656 pGroup = pFile->pGroup;
657 pGroup->iSize -= pFile->iSize;
658 pFile->iSize = size;
659 pGroup->iSize += size;
660 quotaLeave();
662 return rc;
665 /* Pass xSync requests through to the original VFS without change
667 static int quotaSync(sqlite3_file *pConn, int flags){
668 sqlite3_file *pSubOpen = quotaSubOpen(pConn);
669 return pSubOpen->pMethods->xSync(pSubOpen, flags);
672 /* Pass xFileSize requests through to the original VFS but then
673 ** update the quotaGroup with the new size before returning.
675 static int quotaFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){
676 quotaConn *p = (quotaConn*)pConn;
677 sqlite3_file *pSubOpen = quotaSubOpen(pConn);
678 quotaFile *pFile = p->pFile;
679 quotaGroup *pGroup;
680 sqlite3_int64 sz;
681 int rc;
683 rc = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);
684 if( rc==SQLITE_OK ){
685 quotaEnter();
686 pGroup = pFile->pGroup;
687 pGroup->iSize -= pFile->iSize;
688 pFile->iSize = sz;
689 pGroup->iSize += sz;
690 quotaLeave();
691 *pSize = sz;
693 return rc;
696 /* Pass xLock requests through to the original VFS unchanged.
698 static int quotaLock(sqlite3_file *pConn, int lock){
699 sqlite3_file *pSubOpen = quotaSubOpen(pConn);
700 return pSubOpen->pMethods->xLock(pSubOpen, lock);
703 /* Pass xUnlock requests through to the original VFS unchanged.
705 static int quotaUnlock(sqlite3_file *pConn, int lock){
706 sqlite3_file *pSubOpen = quotaSubOpen(pConn);
707 return pSubOpen->pMethods->xUnlock(pSubOpen, lock);
710 /* Pass xCheckReservedLock requests through to the original VFS unchanged.
712 static int quotaCheckReservedLock(sqlite3_file *pConn, int *pResOut){
713 sqlite3_file *pSubOpen = quotaSubOpen(pConn);
714 return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut);
717 /* Pass xFileControl requests through to the original VFS unchanged.
719 static int quotaFileControl(sqlite3_file *pConn, int op, void *pArg){
720 sqlite3_file *pSubOpen = quotaSubOpen(pConn);
721 int rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
722 #if defined(SQLITE_FCNTL_VFSNAME)
723 if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){
724 *(char**)pArg = sqlite3_mprintf("quota/%z", *(char**)pArg);
726 #endif
727 return rc;
730 /* Pass xSectorSize requests through to the original VFS unchanged.
732 static int quotaSectorSize(sqlite3_file *pConn){
733 sqlite3_file *pSubOpen = quotaSubOpen(pConn);
734 return pSubOpen->pMethods->xSectorSize(pSubOpen);
737 /* Pass xDeviceCharacteristics requests through to the original VFS unchanged.
739 static int quotaDeviceCharacteristics(sqlite3_file *pConn){
740 sqlite3_file *pSubOpen = quotaSubOpen(pConn);
741 return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen);
744 /* Pass xShmMap requests through to the original VFS unchanged.
746 static int quotaShmMap(
747 sqlite3_file *pConn, /* Handle open on database file */
748 int iRegion, /* Region to retrieve */
749 int szRegion, /* Size of regions */
750 int bExtend, /* True to extend file if necessary */
751 void volatile **pp /* OUT: Mapped memory */
753 sqlite3_file *pSubOpen = quotaSubOpen(pConn);
754 return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend, pp);
757 /* Pass xShmLock requests through to the original VFS unchanged.
759 static int quotaShmLock(
760 sqlite3_file *pConn, /* Database file holding the shared memory */
761 int ofst, /* First lock to acquire or release */
762 int n, /* Number of locks to acquire or release */
763 int flags /* What to do with the lock */
765 sqlite3_file *pSubOpen = quotaSubOpen(pConn);
766 return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags);
769 /* Pass xShmBarrier requests through to the original VFS unchanged.
771 static void quotaShmBarrier(sqlite3_file *pConn){
772 sqlite3_file *pSubOpen = quotaSubOpen(pConn);
773 pSubOpen->pMethods->xShmBarrier(pSubOpen);
776 /* Pass xShmUnmap requests through to the original VFS unchanged.
778 static int quotaShmUnmap(sqlite3_file *pConn, int deleteFlag){
779 sqlite3_file *pSubOpen = quotaSubOpen(pConn);
780 return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag);
783 /************************** Public Interfaces *****************************/
785 ** Initialize the quota VFS shim. Use the VFS named zOrigVfsName
786 ** as the VFS that does the actual work. Use the default if
787 ** zOrigVfsName==NULL.
789 ** The quota VFS shim is named "quota". It will become the default
790 ** VFS if makeDefault is non-zero.
792 ** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once
793 ** during start-up.
795 int sqlite3_quota_initialize(const char *zOrigVfsName, int makeDefault){
796 sqlite3_vfs *pOrigVfs;
797 if( gQuota.isInitialized ) return SQLITE_MISUSE;
798 pOrigVfs = sqlite3_vfs_find(zOrigVfsName);
799 if( pOrigVfs==0 ) return SQLITE_ERROR;
800 assert( pOrigVfs!=&gQuota.sThisVfs );
801 gQuota.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
802 if( !gQuota.pMutex ){
803 return SQLITE_NOMEM;
805 gQuota.isInitialized = 1;
806 gQuota.pOrigVfs = pOrigVfs;
807 gQuota.sThisVfs = *pOrigVfs;
808 gQuota.sThisVfs.xOpen = quotaOpen;
809 gQuota.sThisVfs.xDelete = quotaDelete;
810 gQuota.sThisVfs.szOsFile += sizeof(quotaConn);
811 gQuota.sThisVfs.zName = "quota";
812 gQuota.sIoMethodsV1.iVersion = 1;
813 gQuota.sIoMethodsV1.xClose = quotaClose;
814 gQuota.sIoMethodsV1.xRead = quotaRead;
815 gQuota.sIoMethodsV1.xWrite = quotaWrite;
816 gQuota.sIoMethodsV1.xTruncate = quotaTruncate;
817 gQuota.sIoMethodsV1.xSync = quotaSync;
818 gQuota.sIoMethodsV1.xFileSize = quotaFileSize;
819 gQuota.sIoMethodsV1.xLock = quotaLock;
820 gQuota.sIoMethodsV1.xUnlock = quotaUnlock;
821 gQuota.sIoMethodsV1.xCheckReservedLock = quotaCheckReservedLock;
822 gQuota.sIoMethodsV1.xFileControl = quotaFileControl;
823 gQuota.sIoMethodsV1.xSectorSize = quotaSectorSize;
824 gQuota.sIoMethodsV1.xDeviceCharacteristics = quotaDeviceCharacteristics;
825 gQuota.sIoMethodsV2 = gQuota.sIoMethodsV1;
826 gQuota.sIoMethodsV2.iVersion = 2;
827 gQuota.sIoMethodsV2.xShmMap = quotaShmMap;
828 gQuota.sIoMethodsV2.xShmLock = quotaShmLock;
829 gQuota.sIoMethodsV2.xShmBarrier = quotaShmBarrier;
830 gQuota.sIoMethodsV2.xShmUnmap = quotaShmUnmap;
831 sqlite3_vfs_register(&gQuota.sThisVfs, makeDefault);
832 return SQLITE_OK;
836 ** Shutdown the quota system.
838 ** All SQLite database connections must be closed before calling this
839 ** routine.
841 ** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once while
842 ** shutting down in order to free all remaining quota groups.
844 int sqlite3_quota_shutdown(void){
845 quotaGroup *pGroup;
846 if( gQuota.isInitialized==0 ) return SQLITE_MISUSE;
847 for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){
848 if( quotaGroupOpenFileCount(pGroup)>0 ) return SQLITE_MISUSE;
850 while( gQuota.pGroup ){
851 pGroup = gQuota.pGroup;
852 gQuota.pGroup = pGroup->pNext;
853 pGroup->iLimit = 0;
854 assert( quotaGroupOpenFileCount(pGroup)==0 );
855 quotaGroupDeref(pGroup);
857 gQuota.isInitialized = 0;
858 sqlite3_mutex_free(gQuota.pMutex);
859 sqlite3_vfs_unregister(&gQuota.sThisVfs);
860 memset(&gQuota, 0, sizeof(gQuota));
861 return SQLITE_OK;
865 ** Create or destroy a quota group.
867 ** The quota group is defined by the zPattern. When calling this routine
868 ** with a zPattern for a quota group that already exists, this routine
869 ** merely updates the iLimit, xCallback, and pArg values for that quota
870 ** group. If zPattern is new, then a new quota group is created.
872 ** If the iLimit for a quota group is set to zero, then the quota group
873 ** is disabled and will be deleted when the last database connection using
874 ** the quota group is closed.
876 ** Calling this routine on a zPattern that does not exist and with a
877 ** zero iLimit is a no-op.
879 ** A quota group must exist with a non-zero iLimit prior to opening
880 ** database connections if those connections are to participate in the
881 ** quota group. Creating a quota group does not affect database connections
882 ** that are already open.
884 int sqlite3_quota_set(
885 const char *zPattern, /* The filename pattern */
886 sqlite3_int64 iLimit, /* New quota to set for this quota group */
887 void (*xCallback)( /* Callback invoked when going over quota */
888 const char *zFilename, /* Name of file whose size increases */
889 sqlite3_int64 *piLimit, /* IN/OUT: The current limit */
890 sqlite3_int64 iSize, /* Total size of all files in the group */
891 void *pArg /* Client data */
893 void *pArg, /* client data passed thru to callback */
894 void (*xDestroy)(void*) /* Optional destructor for pArg */
896 quotaGroup *pGroup;
897 quotaEnter();
898 pGroup = gQuota.pGroup;
899 while( pGroup && strcmp(pGroup->zPattern, zPattern)!=0 ){
900 pGroup = pGroup->pNext;
902 if( pGroup==0 ){
903 int nPattern = (int)(strlen(zPattern) & 0x3fffffff);
904 if( iLimit<=0 ){
905 quotaLeave();
906 return SQLITE_OK;
908 pGroup = (quotaGroup *)sqlite3_malloc( sizeof(*pGroup) + nPattern + 1 );
909 if( pGroup==0 ){
910 quotaLeave();
911 return SQLITE_NOMEM;
913 memset(pGroup, 0, sizeof(*pGroup));
914 pGroup->zPattern = (char*)&pGroup[1];
915 memcpy((char *)pGroup->zPattern, zPattern, nPattern+1);
916 if( gQuota.pGroup ) gQuota.pGroup->ppPrev = &pGroup->pNext;
917 pGroup->pNext = gQuota.pGroup;
918 pGroup->ppPrev = &gQuota.pGroup;
919 gQuota.pGroup = pGroup;
921 pGroup->iLimit = iLimit;
922 pGroup->xCallback = xCallback;
923 if( pGroup->xDestroy && pGroup->pArg!=pArg ){
924 pGroup->xDestroy(pGroup->pArg);
926 pGroup->pArg = pArg;
927 pGroup->xDestroy = xDestroy;
928 quotaGroupDeref(pGroup);
929 quotaLeave();
930 return SQLITE_OK;
934 ** Bring the named file under quota management. Or if it is already under
935 ** management, update its size.
937 int sqlite3_quota_file(const char *zFilename){
938 char *zFull;
939 sqlite3_file *fd;
940 int rc;
941 int outFlags = 0;
942 sqlite3_int64 iSize;
943 int nAlloc = gQuota.sThisVfs.szOsFile + gQuota.sThisVfs.mxPathname+2;
945 /* Allocate space for a file-handle and the full path for file zFilename */
946 fd = (sqlite3_file *)sqlite3_malloc(nAlloc);
947 if( fd==0 ){
948 rc = SQLITE_NOMEM;
949 }else{
950 zFull = &((char *)fd)[gQuota.sThisVfs.szOsFile];
951 rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
952 gQuota.sThisVfs.mxPathname+1, zFull);
955 if( rc==SQLITE_OK ){
956 zFull[strlen(zFull)+1] = '\0';
957 rc = quotaOpen(&gQuota.sThisVfs, zFull, fd,
958 SQLITE_OPEN_READONLY | SQLITE_OPEN_MAIN_DB, &outFlags);
959 if( rc==SQLITE_OK ){
960 fd->pMethods->xFileSize(fd, &iSize);
961 fd->pMethods->xClose(fd);
962 }else if( rc==SQLITE_CANTOPEN ){
963 quotaGroup *pGroup;
964 quotaFile *pFile;
965 quotaEnter();
966 pGroup = quotaGroupFind(zFull);
967 if( pGroup ){
968 pFile = quotaFindFile(pGroup, zFull, 0);
969 if( pFile ) quotaRemoveFile(pFile);
971 quotaLeave();
975 sqlite3_free(fd);
976 return rc;
980 ** Open a potentially quotaed file for I/O.
982 quota_FILE *sqlite3_quota_fopen(const char *zFilename, const char *zMode){
983 quota_FILE *p = 0;
984 char *zFull = 0;
985 char *zFullTranslated = 0;
986 int rc;
987 quotaGroup *pGroup;
988 quotaFile *pFile;
990 zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1);
991 if( zFull==0 ) return 0;
992 rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
993 gQuota.sThisVfs.mxPathname+1, zFull);
994 if( rc ) goto quota_fopen_error;
995 p = (quota_FILE*)sqlite3_malloc(sizeof(*p));
996 if( p==0 ) goto quota_fopen_error;
997 memset(p, 0, sizeof(*p));
998 zFullTranslated = quota_utf8_to_mbcs(zFull);
999 if( zFullTranslated==0 ) goto quota_fopen_error;
1000 p->f = fopen(zFullTranslated, zMode);
1001 if( p->f==0 ) goto quota_fopen_error;
1002 quotaEnter();
1003 pGroup = quotaGroupFind(zFull);
1004 if( pGroup ){
1005 pFile = quotaFindFile(pGroup, zFull, 1);
1006 if( pFile==0 ){
1007 quotaLeave();
1008 goto quota_fopen_error;
1010 pFile->nRef++;
1011 p->pFile = pFile;
1013 quotaLeave();
1014 sqlite3_free(zFull);
1015 #if SQLITE_OS_WIN
1016 p->zMbcsName = zFullTranslated;
1017 #endif
1018 return p;
1020 quota_fopen_error:
1021 quota_mbcs_free(zFullTranslated);
1022 sqlite3_free(zFull);
1023 if( p && p->f ) fclose(p->f);
1024 sqlite3_free(p);
1025 return 0;
1029 ** Read content from a quota_FILE
1031 size_t sqlite3_quota_fread(
1032 void *pBuf, /* Store the content here */
1033 size_t size, /* Size of each element */
1034 size_t nmemb, /* Number of elements to read */
1035 quota_FILE *p /* Read from this quota_FILE object */
1037 return fread(pBuf, size, nmemb, p->f);
1041 ** Write content into a quota_FILE. Invoke the quota callback and block
1042 ** the write if we exceed quota.
1044 size_t sqlite3_quota_fwrite(
1045 void *pBuf, /* Take content to write from here */
1046 size_t size, /* Size of each element */
1047 size_t nmemb, /* Number of elements */
1048 quota_FILE *p /* Write to this quota_FILE objecct */
1050 sqlite3_int64 iOfst;
1051 sqlite3_int64 iEnd;
1052 sqlite3_int64 szNew;
1053 quotaFile *pFile;
1054 size_t rc;
1056 iOfst = ftell(p->f);
1057 iEnd = iOfst + size*nmemb;
1058 pFile = p->pFile;
1059 if( pFile && pFile->iSize<iEnd ){
1060 quotaGroup *pGroup = pFile->pGroup;
1061 quotaEnter();
1062 szNew = pGroup->iSize - pFile->iSize + iEnd;
1063 if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
1064 if( pGroup->xCallback ){
1065 pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew,
1066 pGroup->pArg);
1068 if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
1069 iEnd = pGroup->iLimit - pGroup->iSize + pFile->iSize;
1070 nmemb = (size_t)((iEnd - iOfst)/size);
1071 iEnd = iOfst + size*nmemb;
1072 szNew = pGroup->iSize - pFile->iSize + iEnd;
1075 pGroup->iSize = szNew;
1076 pFile->iSize = iEnd;
1077 quotaLeave();
1078 }else{
1079 pFile = 0;
1081 rc = fwrite(pBuf, size, nmemb, p->f);
1083 /* If the write was incomplete, adjust the file size and group size
1084 ** downward */
1085 if( rc<nmemb && pFile ){
1086 size_t nWritten = rc>=0 ? rc : 0;
1087 sqlite3_int64 iNewEnd = iOfst + size*nWritten;
1088 if( iNewEnd<iEnd ) iNewEnd = iEnd;
1089 quotaEnter();
1090 pFile->pGroup->iSize += iNewEnd - pFile->iSize;
1091 pFile->iSize = iNewEnd;
1092 quotaLeave();
1094 return rc;
1098 ** Close an open quota_FILE stream.
1100 int sqlite3_quota_fclose(quota_FILE *p){
1101 int rc;
1102 quotaFile *pFile;
1103 rc = fclose(p->f);
1104 pFile = p->pFile;
1105 if( pFile ){
1106 quotaEnter();
1107 pFile->nRef--;
1108 if( pFile->nRef==0 ){
1109 quotaGroup *pGroup = pFile->pGroup;
1110 if( pFile->deleteOnClose ){
1111 gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0);
1112 quotaRemoveFile(pFile);
1114 quotaGroupDeref(pGroup);
1116 quotaLeave();
1118 #if SQLITE_OS_WIN
1119 quota_mbcs_free(p->zMbcsName);
1120 #endif
1121 sqlite3_free(p);
1122 return rc;
1126 ** Flush memory buffers for a quota_FILE to disk.
1128 int sqlite3_quota_fflush(quota_FILE *p, int doFsync){
1129 int rc;
1130 rc = fflush(p->f);
1131 if( rc==0 && doFsync ){
1132 #if SQLITE_OS_UNIX
1133 rc = fsync(fileno(p->f));
1134 #endif
1135 #if SQLITE_OS_WIN
1136 rc = _commit(_fileno(p->f));
1137 #endif
1139 return rc!=0;
1143 ** Seek on a quota_FILE stream.
1145 int sqlite3_quota_fseek(quota_FILE *p, long offset, int whence){
1146 return fseek(p->f, offset, whence);
1150 ** rewind a quota_FILE stream.
1152 void sqlite3_quota_rewind(quota_FILE *p){
1153 rewind(p->f);
1157 ** Tell the current location of a quota_FILE stream.
1159 long sqlite3_quota_ftell(quota_FILE *p){
1160 return ftell(p->f);
1164 ** Truncate a file to szNew bytes.
1166 int sqlite3_quota_ftruncate(quota_FILE *p, sqlite3_int64 szNew){
1167 quotaFile *pFile = p->pFile;
1168 int rc;
1169 if( (pFile = p->pFile)!=0 && pFile->iSize<szNew ){
1170 quotaGroup *pGroup;
1171 if( pFile->iSize<szNew ){
1172 /* This routine cannot be used to extend a file that is under
1173 ** quota management. Only true truncation is allowed. */
1174 return -1;
1176 pGroup = pFile->pGroup;
1177 quotaEnter();
1178 pGroup->iSize += szNew - pFile->iSize;
1179 quotaLeave();
1181 #if SQLITE_OS_UNIX
1182 rc = ftruncate(fileno(p->f), szNew);
1183 #endif
1184 #if SQLITE_OS_WIN
1185 rc = _chsize_s(_fileno(p->f), szNew);
1186 #endif
1187 if( pFile && rc==0 ){
1188 quotaGroup *pGroup = pFile->pGroup;
1189 quotaEnter();
1190 pGroup->iSize += szNew - pFile->iSize;
1191 pFile->iSize = szNew;
1192 quotaLeave();
1194 return rc;
1198 ** Determine the time that the given file was last modified, in
1199 ** seconds size 1970. Write the result into *pTime. Return 0 on
1200 ** success and non-zero on any kind of error.
1202 int sqlite3_quota_file_mtime(quota_FILE *p, time_t *pTime){
1203 int rc;
1204 #if SQLITE_OS_UNIX
1205 struct stat buf;
1206 rc = fstat(fileno(p->f), &buf);
1207 #endif
1208 #if SQLITE_OS_WIN
1209 struct _stati64 buf;
1210 rc = _stati64(p->zMbcsName, &buf);
1211 #endif
1212 if( rc==0 ) *pTime = buf.st_mtime;
1213 return rc;
1217 ** Return the true size of the file, as reported by the operating
1218 ** system.
1220 sqlite3_int64 sqlite3_quota_file_truesize(quota_FILE *p){
1221 int rc;
1222 #if SQLITE_OS_UNIX
1223 struct stat buf;
1224 rc = fstat(fileno(p->f), &buf);
1225 #endif
1226 #if SQLITE_OS_WIN
1227 struct _stati64 buf;
1228 rc = _stati64(p->zMbcsName, &buf);
1229 #endif
1230 return rc==0 ? buf.st_size : -1;
1234 ** Return the size of the file, as it is known to the quota subsystem.
1236 sqlite3_int64 sqlite3_quota_file_size(quota_FILE *p){
1237 return p->pFile ? p->pFile->iSize : -1;
1241 ** Remove a managed file. Update quotas accordingly.
1243 int sqlite3_quota_remove(const char *zFilename){
1244 char *zFull; /* Full pathname for zFilename */
1245 size_t nFull; /* Number of bytes in zFilename */
1246 int rc; /* Result code */
1247 quotaGroup *pGroup; /* Group containing zFilename */
1248 quotaFile *pFile; /* A file in the group */
1249 quotaFile *pNextFile; /* next file in the group */
1250 int diff; /* Difference between filenames */
1251 char c; /* First character past end of pattern */
1253 zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1);
1254 if( zFull==0 ) return SQLITE_NOMEM;
1255 rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
1256 gQuota.sThisVfs.mxPathname+1, zFull);
1257 if( rc ){
1258 sqlite3_free(zFull);
1259 return rc;
1262 /* Figure out the length of the full pathname. If the name ends with
1263 ** / (or \ on windows) then remove the trailing /.
1265 nFull = strlen(zFull);
1266 if( nFull>0 && (zFull[nFull-1]=='/' || zFull[nFull-1]=='\\') ){
1267 nFull--;
1268 zFull[nFull] = 0;
1271 quotaEnter();
1272 pGroup = quotaGroupFind(zFull);
1273 if( pGroup ){
1274 for(pFile=pGroup->pFiles; pFile && rc==SQLITE_OK; pFile=pNextFile){
1275 pNextFile = pFile->pNext;
1276 diff = memcmp(zFull, pFile->zFilename, nFull);
1277 if( diff==0 && ((c = pFile->zFilename[nFull])==0 || c=='/' || c=='\\') ){
1278 if( pFile->nRef ){
1279 pFile->deleteOnClose = 1;
1280 }else{
1281 rc = gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0);
1282 quotaRemoveFile(pFile);
1283 quotaGroupDeref(pGroup);
1288 quotaLeave();
1289 sqlite3_free(zFull);
1290 return rc;
1293 /***************************** Test Code ***********************************/
1294 #ifdef SQLITE_TEST
1295 #include <tcl.h>
1298 ** Argument passed to a TCL quota-over-limit callback.
1300 typedef struct TclQuotaCallback TclQuotaCallback;
1301 struct TclQuotaCallback {
1302 Tcl_Interp *interp; /* Interpreter in which to run the script */
1303 Tcl_Obj *pScript; /* Script to be run */
1306 extern const char *sqlite3TestErrorName(int);
1310 ** This is the callback from a quota-over-limit.
1312 static void tclQuotaCallback(
1313 const char *zFilename, /* Name of file whose size increases */
1314 sqlite3_int64 *piLimit, /* IN/OUT: The current limit */
1315 sqlite3_int64 iSize, /* Total size of all files in the group */
1316 void *pArg /* Client data */
1318 TclQuotaCallback *p; /* Callback script object */
1319 Tcl_Obj *pEval; /* Script to evaluate */
1320 Tcl_Obj *pVarname; /* Name of variable to pass as 2nd arg */
1321 unsigned int rnd; /* Random part of pVarname */
1322 int rc; /* Tcl error code */
1324 p = (TclQuotaCallback *)pArg;
1325 if( p==0 ) return;
1327 pVarname = Tcl_NewStringObj("::piLimit_", -1);
1328 Tcl_IncrRefCount(pVarname);
1329 sqlite3_randomness(sizeof(rnd), (void *)&rnd);
1330 Tcl_AppendObjToObj(pVarname, Tcl_NewIntObj((int)(rnd&0x7FFFFFFF)));
1331 Tcl_ObjSetVar2(p->interp, pVarname, 0, Tcl_NewWideIntObj(*piLimit), 0);
1333 pEval = Tcl_DuplicateObj(p->pScript);
1334 Tcl_IncrRefCount(pEval);
1335 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zFilename, -1));
1336 Tcl_ListObjAppendElement(0, pEval, pVarname);
1337 Tcl_ListObjAppendElement(0, pEval, Tcl_NewWideIntObj(iSize));
1338 rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
1340 if( rc==TCL_OK ){
1341 Tcl_Obj *pLimit = Tcl_ObjGetVar2(p->interp, pVarname, 0, 0);
1342 rc = Tcl_GetWideIntFromObj(p->interp, pLimit, piLimit);
1343 Tcl_UnsetVar(p->interp, Tcl_GetString(pVarname), 0);
1346 Tcl_DecrRefCount(pEval);
1347 Tcl_DecrRefCount(pVarname);
1348 if( rc!=TCL_OK ) Tcl_BackgroundError(p->interp);
1352 ** Destructor for a TCL quota-over-limit callback.
1354 static void tclCallbackDestructor(void *pObj){
1355 TclQuotaCallback *p = (TclQuotaCallback*)pObj;
1356 if( p ){
1357 Tcl_DecrRefCount(p->pScript);
1358 sqlite3_free((char *)p);
1363 ** tclcmd: sqlite3_quota_initialize NAME MAKEDEFAULT
1365 static int test_quota_initialize(
1366 void * clientData,
1367 Tcl_Interp *interp,
1368 int objc,
1369 Tcl_Obj *CONST objv[]
1371 const char *zName; /* Name of new quota VFS */
1372 int makeDefault; /* True to make the new VFS the default */
1373 int rc; /* Value returned by quota_initialize() */
1375 /* Process arguments */
1376 if( objc!=3 ){
1377 Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT");
1378 return TCL_ERROR;
1380 zName = Tcl_GetString(objv[1]);
1381 if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR;
1382 if( zName[0]=='\0' ) zName = 0;
1384 /* Call sqlite3_quota_initialize() */
1385 rc = sqlite3_quota_initialize(zName, makeDefault);
1386 Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
1388 return TCL_OK;
1392 ** tclcmd: sqlite3_quota_shutdown
1394 static int test_quota_shutdown(
1395 void * clientData,
1396 Tcl_Interp *interp,
1397 int objc,
1398 Tcl_Obj *CONST objv[]
1400 int rc; /* Value returned by quota_shutdown() */
1402 if( objc!=1 ){
1403 Tcl_WrongNumArgs(interp, 1, objv, "");
1404 return TCL_ERROR;
1407 /* Call sqlite3_quota_shutdown() */
1408 rc = sqlite3_quota_shutdown();
1409 Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
1411 return TCL_OK;
1415 ** tclcmd: sqlite3_quota_set PATTERN LIMIT SCRIPT
1417 static int test_quota_set(
1418 void * clientData,
1419 Tcl_Interp *interp,
1420 int objc,
1421 Tcl_Obj *CONST objv[]
1423 const char *zPattern; /* File pattern to configure */
1424 sqlite3_int64 iLimit; /* Initial quota in bytes */
1425 Tcl_Obj *pScript; /* Tcl script to invoke to increase quota */
1426 int rc; /* Value returned by quota_set() */
1427 TclQuotaCallback *p; /* Callback object */
1428 int nScript; /* Length of callback script */
1429 void (*xDestroy)(void*); /* Optional destructor for pArg */
1430 void (*xCallback)(const char *, sqlite3_int64 *, sqlite3_int64, void *);
1432 /* Process arguments */
1433 if( objc!=4 ){
1434 Tcl_WrongNumArgs(interp, 1, objv, "PATTERN LIMIT SCRIPT");
1435 return TCL_ERROR;
1437 zPattern = Tcl_GetString(objv[1]);
1438 if( Tcl_GetWideIntFromObj(interp, objv[2], &iLimit) ) return TCL_ERROR;
1439 pScript = objv[3];
1440 Tcl_GetStringFromObj(pScript, &nScript);
1442 if( nScript>0 ){
1443 /* Allocate a TclQuotaCallback object */
1444 p = (TclQuotaCallback *)sqlite3_malloc(sizeof(TclQuotaCallback));
1445 if( !p ){
1446 Tcl_SetResult(interp, (char *)"SQLITE_NOMEM", TCL_STATIC);
1447 return TCL_OK;
1449 memset(p, 0, sizeof(TclQuotaCallback));
1450 p->interp = interp;
1451 Tcl_IncrRefCount(pScript);
1452 p->pScript = pScript;
1453 xDestroy = tclCallbackDestructor;
1454 xCallback = tclQuotaCallback;
1455 }else{
1456 p = 0;
1457 xDestroy = 0;
1458 xCallback = 0;
1461 /* Invoke sqlite3_quota_set() */
1462 rc = sqlite3_quota_set(zPattern, iLimit, xCallback, (void*)p, xDestroy);
1464 Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
1465 return TCL_OK;
1469 ** tclcmd: sqlite3_quota_file FILENAME
1471 static int test_quota_file(
1472 void * clientData,
1473 Tcl_Interp *interp,
1474 int objc,
1475 Tcl_Obj *CONST objv[]
1477 const char *zFilename; /* File pattern to configure */
1478 int rc; /* Value returned by quota_file() */
1480 /* Process arguments */
1481 if( objc!=2 ){
1482 Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
1483 return TCL_ERROR;
1485 zFilename = Tcl_GetString(objv[1]);
1487 /* Invoke sqlite3_quota_file() */
1488 rc = sqlite3_quota_file(zFilename);
1490 Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
1491 return TCL_OK;
1495 ** tclcmd: sqlite3_quota_dump
1497 static int test_quota_dump(
1498 void * clientData,
1499 Tcl_Interp *interp,
1500 int objc,
1501 Tcl_Obj *CONST objv[]
1503 Tcl_Obj *pResult;
1504 Tcl_Obj *pGroupTerm;
1505 Tcl_Obj *pFileTerm;
1506 quotaGroup *pGroup;
1507 quotaFile *pFile;
1509 pResult = Tcl_NewObj();
1510 quotaEnter();
1511 for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){
1512 pGroupTerm = Tcl_NewObj();
1513 Tcl_ListObjAppendElement(interp, pGroupTerm,
1514 Tcl_NewStringObj(pGroup->zPattern, -1));
1515 Tcl_ListObjAppendElement(interp, pGroupTerm,
1516 Tcl_NewWideIntObj(pGroup->iLimit));
1517 Tcl_ListObjAppendElement(interp, pGroupTerm,
1518 Tcl_NewWideIntObj(pGroup->iSize));
1519 for(pFile=pGroup->pFiles; pFile; pFile=pFile->pNext){
1520 int i;
1521 char zTemp[1000];
1522 pFileTerm = Tcl_NewObj();
1523 sqlite3_snprintf(sizeof(zTemp), zTemp, "%s", pFile->zFilename);
1524 for(i=0; zTemp[i]; i++){ if( zTemp[i]=='\\' ) zTemp[i] = '/'; }
1525 Tcl_ListObjAppendElement(interp, pFileTerm,
1526 Tcl_NewStringObj(zTemp, -1));
1527 Tcl_ListObjAppendElement(interp, pFileTerm,
1528 Tcl_NewWideIntObj(pFile->iSize));
1529 Tcl_ListObjAppendElement(interp, pFileTerm,
1530 Tcl_NewWideIntObj(pFile->nRef));
1531 Tcl_ListObjAppendElement(interp, pFileTerm,
1532 Tcl_NewWideIntObj(pFile->deleteOnClose));
1533 Tcl_ListObjAppendElement(interp, pGroupTerm, pFileTerm);
1535 Tcl_ListObjAppendElement(interp, pResult, pGroupTerm);
1537 quotaLeave();
1538 Tcl_SetObjResult(interp, pResult);
1539 return TCL_OK;
1543 ** tclcmd: sqlite3_quota_fopen FILENAME MODE
1545 static int test_quota_fopen(
1546 void * clientData,
1547 Tcl_Interp *interp,
1548 int objc,
1549 Tcl_Obj *CONST objv[]
1551 const char *zFilename; /* File pattern to configure */
1552 const char *zMode; /* Mode string */
1553 quota_FILE *p; /* Open string object */
1554 char zReturn[50]; /* Name of pointer to return */
1556 /* Process arguments */
1557 if( objc!=3 ){
1558 Tcl_WrongNumArgs(interp, 1, objv, "FILENAME MODE");
1559 return TCL_ERROR;
1561 zFilename = Tcl_GetString(objv[1]);
1562 zMode = Tcl_GetString(objv[2]);
1563 p = sqlite3_quota_fopen(zFilename, zMode);
1564 sqlite3_snprintf(sizeof(zReturn), zReturn, "%p", p);
1565 Tcl_SetResult(interp, zReturn, TCL_VOLATILE);
1566 return TCL_OK;
1569 /* Defined in test1.c */
1570 extern void *sqlite3TestTextToPtr(const char*);
1573 ** tclcmd: sqlite3_quota_fread HANDLE SIZE NELEM
1575 static int test_quota_fread(
1576 void * clientData,
1577 Tcl_Interp *interp,
1578 int objc,
1579 Tcl_Obj *CONST objv[]
1581 quota_FILE *p;
1582 char *zBuf;
1583 int sz;
1584 int nElem;
1585 size_t got;
1587 if( objc!=4 ){
1588 Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM");
1589 return TCL_ERROR;
1591 p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1592 if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR;
1593 if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR;
1594 zBuf = (char*)sqlite3_malloc( sz*nElem + 1 );
1595 if( zBuf==0 ){
1596 Tcl_SetResult(interp, "out of memory", TCL_STATIC);
1597 return TCL_ERROR;
1599 got = sqlite3_quota_fread(zBuf, sz, nElem, p);
1600 if( got<0 ) got = 0;
1601 zBuf[got*sz] = 0;
1602 Tcl_SetResult(interp, zBuf, TCL_VOLATILE);
1603 sqlite3_free(zBuf);
1604 return TCL_OK;
1608 ** tclcmd: sqlite3_quota_fwrite HANDLE SIZE NELEM CONTENT
1610 static int test_quota_fwrite(
1611 void * clientData,
1612 Tcl_Interp *interp,
1613 int objc,
1614 Tcl_Obj *CONST objv[]
1616 quota_FILE *p;
1617 char *zBuf;
1618 int sz;
1619 int nElem;
1620 size_t got;
1622 if( objc!=5 ){
1623 Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM CONTENT");
1624 return TCL_ERROR;
1626 p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1627 if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR;
1628 if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR;
1629 zBuf = Tcl_GetString(objv[4]);
1630 got = sqlite3_quota_fwrite(zBuf, sz, nElem, p);
1631 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(got));
1632 return TCL_OK;
1636 ** tclcmd: sqlite3_quota_fclose HANDLE
1638 static int test_quota_fclose(
1639 void * clientData,
1640 Tcl_Interp *interp,
1641 int objc,
1642 Tcl_Obj *CONST objv[]
1644 quota_FILE *p;
1645 int rc;
1647 if( objc!=2 ){
1648 Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
1649 return TCL_ERROR;
1651 p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1652 rc = sqlite3_quota_fclose(p);
1653 Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
1654 return TCL_OK;
1658 ** tclcmd: sqlite3_quota_fflush HANDLE ?HARDSYNC?
1660 static int test_quota_fflush(
1661 void * clientData,
1662 Tcl_Interp *interp,
1663 int objc,
1664 Tcl_Obj *CONST objv[]
1666 quota_FILE *p;
1667 int rc;
1668 int doSync = 0;
1670 if( objc!=2 && objc!=3 ){
1671 Tcl_WrongNumArgs(interp, 1, objv, "HANDLE ?HARDSYNC?");
1672 return TCL_ERROR;
1674 p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1675 if( objc==3 ){
1676 if( Tcl_GetBooleanFromObj(interp, objv[2], &doSync) ) return TCL_ERROR;
1678 rc = sqlite3_quota_fflush(p, doSync);
1679 Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
1680 return TCL_OK;
1684 ** tclcmd: sqlite3_quota_fseek HANDLE OFFSET WHENCE
1686 static int test_quota_fseek(
1687 void * clientData,
1688 Tcl_Interp *interp,
1689 int objc,
1690 Tcl_Obj *CONST objv[]
1692 quota_FILE *p;
1693 int ofst;
1694 const char *zWhence;
1695 int whence;
1696 int rc;
1698 if( objc!=4 ){
1699 Tcl_WrongNumArgs(interp, 1, objv, "HANDLE OFFSET WHENCE");
1700 return TCL_ERROR;
1702 p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1703 if( Tcl_GetIntFromObj(interp, objv[2], &ofst) ) return TCL_ERROR;
1704 zWhence = Tcl_GetString(objv[3]);
1705 if( strcmp(zWhence, "SEEK_SET")==0 ){
1706 whence = SEEK_SET;
1707 }else if( strcmp(zWhence, "SEEK_CUR")==0 ){
1708 whence = SEEK_CUR;
1709 }else if( strcmp(zWhence, "SEEK_END")==0 ){
1710 whence = SEEK_END;
1711 }else{
1712 Tcl_AppendResult(interp,
1713 "WHENCE should be SEEK_SET, SEEK_CUR, or SEEK_END", (char*)0);
1714 return TCL_ERROR;
1716 rc = sqlite3_quota_fseek(p, ofst, whence);
1717 Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
1718 return TCL_OK;
1722 ** tclcmd: sqlite3_quota_rewind HANDLE
1724 static int test_quota_rewind(
1725 void * clientData,
1726 Tcl_Interp *interp,
1727 int objc,
1728 Tcl_Obj *CONST objv[]
1730 quota_FILE *p;
1731 if( objc!=2 ){
1732 Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
1733 return TCL_ERROR;
1735 p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1736 sqlite3_quota_rewind(p);
1737 return TCL_OK;
1741 ** tclcmd: sqlite3_quota_ftell HANDLE
1743 static int test_quota_ftell(
1744 void * clientData,
1745 Tcl_Interp *interp,
1746 int objc,
1747 Tcl_Obj *CONST objv[]
1749 quota_FILE *p;
1750 sqlite3_int64 x;
1751 if( objc!=2 ){
1752 Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
1753 return TCL_ERROR;
1755 p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1756 x = sqlite3_quota_ftell(p);
1757 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x));
1758 return TCL_OK;
1762 ** tclcmd: sqlite3_quota_ftruncate HANDLE SIZE
1764 static int test_quota_ftruncate(
1765 void * clientData,
1766 Tcl_Interp *interp,
1767 int objc,
1768 Tcl_Obj *CONST objv[]
1770 quota_FILE *p;
1771 sqlite3_int64 x;
1772 Tcl_WideInt w;
1773 int rc;
1774 if( objc!=3 ){
1775 Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE");
1776 return TCL_ERROR;
1778 p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1779 if( Tcl_GetWideIntFromObj(interp, objv[2], &w) ) return TCL_ERROR;
1780 x = (sqlite3_int64)w;
1781 rc = sqlite3_quota_ftruncate(p, x);
1782 Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
1783 return TCL_OK;
1787 ** tclcmd: sqlite3_quota_file_size HANDLE
1789 static int test_quota_file_size(
1790 void * clientData,
1791 Tcl_Interp *interp,
1792 int objc,
1793 Tcl_Obj *CONST objv[]
1795 quota_FILE *p;
1796 sqlite3_int64 x;
1797 if( objc!=2 ){
1798 Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
1799 return TCL_ERROR;
1801 p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1802 x = sqlite3_quota_file_size(p);
1803 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x));
1804 return TCL_OK;
1808 ** tclcmd: sqlite3_quota_file_truesize HANDLE
1810 static int test_quota_file_truesize(
1811 void * clientData,
1812 Tcl_Interp *interp,
1813 int objc,
1814 Tcl_Obj *CONST objv[]
1816 quota_FILE *p;
1817 sqlite3_int64 x;
1818 if( objc!=2 ){
1819 Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
1820 return TCL_ERROR;
1822 p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1823 x = sqlite3_quota_file_truesize(p);
1824 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x));
1825 return TCL_OK;
1829 ** tclcmd: sqlite3_quota_file_mtime HANDLE
1831 static int test_quota_file_mtime(
1832 void * clientData,
1833 Tcl_Interp *interp,
1834 int objc,
1835 Tcl_Obj *CONST objv[]
1837 quota_FILE *p;
1838 time_t t;
1839 if( objc!=2 ){
1840 Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
1841 return TCL_ERROR;
1843 p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1844 t = 0;
1845 sqlite3_quota_file_mtime(p, &t);
1846 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(t));
1847 return TCL_OK;
1852 ** tclcmd: sqlite3_quota_remove FILENAME
1854 static int test_quota_remove(
1855 void * clientData,
1856 Tcl_Interp *interp,
1857 int objc,
1858 Tcl_Obj *CONST objv[]
1860 const char *zFilename; /* File pattern to configure */
1861 int rc;
1862 if( objc!=2 ){
1863 Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
1864 return TCL_ERROR;
1866 zFilename = Tcl_GetString(objv[1]);
1867 rc = sqlite3_quota_remove(zFilename);
1868 Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
1869 return TCL_OK;
1873 ** tclcmd: sqlite3_quota_glob PATTERN TEXT
1875 ** Test the glob pattern matching. Return 1 if TEXT matches PATTERN
1876 ** and return 0 if it does not.
1878 static int test_quota_glob(
1879 void * clientData,
1880 Tcl_Interp *interp,
1881 int objc,
1882 Tcl_Obj *CONST objv[]
1884 const char *zPattern; /* The glob pattern */
1885 const char *zText; /* Text to compare agains the pattern */
1886 int rc;
1887 if( objc!=3 ){
1888 Tcl_WrongNumArgs(interp, 1, objv, "PATTERN TEXT");
1889 return TCL_ERROR;
1891 zPattern = Tcl_GetString(objv[1]);
1892 zText = Tcl_GetString(objv[2]);
1893 rc = quotaStrglob(zPattern, zText);
1894 Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
1895 return TCL_OK;
1899 ** This routine registers the custom TCL commands defined in this
1900 ** module. This should be the only procedure visible from outside
1901 ** of this module.
1903 int Sqlitequota_Init(Tcl_Interp *interp){
1904 static struct {
1905 char *zName;
1906 Tcl_ObjCmdProc *xProc;
1907 } aCmd[] = {
1908 { "sqlite3_quota_initialize", test_quota_initialize },
1909 { "sqlite3_quota_shutdown", test_quota_shutdown },
1910 { "sqlite3_quota_set", test_quota_set },
1911 { "sqlite3_quota_file", test_quota_file },
1912 { "sqlite3_quota_dump", test_quota_dump },
1913 { "sqlite3_quota_fopen", test_quota_fopen },
1914 { "sqlite3_quota_fread", test_quota_fread },
1915 { "sqlite3_quota_fwrite", test_quota_fwrite },
1916 { "sqlite3_quota_fclose", test_quota_fclose },
1917 { "sqlite3_quota_fflush", test_quota_fflush },
1918 { "sqlite3_quota_fseek", test_quota_fseek },
1919 { "sqlite3_quota_rewind", test_quota_rewind },
1920 { "sqlite3_quota_ftell", test_quota_ftell },
1921 { "sqlite3_quota_ftruncate", test_quota_ftruncate },
1922 { "sqlite3_quota_file_size", test_quota_file_size },
1923 { "sqlite3_quota_file_truesize", test_quota_file_truesize },
1924 { "sqlite3_quota_file_mtime", test_quota_file_mtime },
1925 { "sqlite3_quota_remove", test_quota_remove },
1926 { "sqlite3_quota_glob", test_quota_glob },
1928 int i;
1930 for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
1931 Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
1934 return TCL_OK;
1936 #endif