Annotation of embedaddon/sqlite3/src/test_quota.c, revision 1.1.1.1
1.1 misho 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.
10: **
11: *************************************************************************
12: **
13: ** This file contains a VFS "shim" - a layer that sits in between the
14: ** pager and the real VFS.
15: **
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.
22: **
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.
29: */
30: #include "test_quota.h"
31: #include <string.h>
32: #include <assert.h>
33:
34: /*
35: ** For an build without mutexes, no-op the mutex calls.
36: */
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 */
46:
47:
48: /************************ Object Definitions ******************************/
49:
50: /* Forward declaration of all object types */
51: typedef struct quotaGroup quotaGroup;
52: typedef struct quotaConn quotaConn;
53: typedef struct quotaFile quotaFile;
54:
55: /*
56: ** A "quota group" is a collection of files whose collective size we want
57: ** to limit. Each quota group is defined by a GLOB pattern.
58: **
59: ** There is an instance of the following object for each defined quota
60: ** group. This object records the GLOB pattern that defines which files
61: ** belong to the quota group. The object also remembers the size limit
62: ** for the group (the quota) and the callback to be invoked when the
63: ** sum of the sizes of the files within the group goes over the limit.
64: **
65: ** A quota group must be established (using sqlite3_quota_set(...))
66: ** prior to opening any of the database connections that access files
67: ** within the quota group.
68: */
69: struct quotaGroup {
70: const char *zPattern; /* Filename pattern to be quotaed */
71: sqlite3_int64 iLimit; /* Upper bound on total file size */
72: sqlite3_int64 iSize; /* Current size of all files */
73: void (*xCallback)( /* Callback invoked when going over quota */
74: const char *zFilename, /* Name of file whose size increases */
75: sqlite3_int64 *piLimit, /* IN/OUT: The current limit */
76: sqlite3_int64 iSize, /* Total size of all files in the group */
77: void *pArg /* Client data */
78: );
79: void *pArg; /* Third argument to the xCallback() */
80: void (*xDestroy)(void*); /* Optional destructor for pArg */
81: quotaGroup *pNext, **ppPrev; /* Doubly linked list of all quota objects */
82: quotaFile *pFiles; /* Files within this group */
83: };
84:
85: /*
86: ** An instance of this structure represents a single file that is part
87: ** of a quota group. A single file can be opened multiple times. In
88: ** order keep multiple openings of the same file from causing the size
89: ** of the file to count against the quota multiple times, each file
90: ** has a unique instance of this object and multiple open connections
91: ** to the same file each point to a single instance of this object.
92: */
93: struct quotaFile {
94: char *zFilename; /* Name of this file */
95: quotaGroup *pGroup; /* Quota group to which this file belongs */
96: sqlite3_int64 iSize; /* Current size of this file */
97: int nRef; /* Number of times this file is open */
98: int deleteOnClose; /* True to delete this file when it closes */
99: quotaFile *pNext, **ppPrev; /* Linked list of files in the same group */
100: };
101:
102: /*
103: ** An instance of the following object represents each open connection
104: ** to a file that participates in quota tracking. This object is a
105: ** subclass of sqlite3_file. The sqlite3_file object for the underlying
106: ** VFS is appended to this structure.
107: */
108: struct quotaConn {
109: sqlite3_file base; /* Base class - must be first */
110: quotaFile *pFile; /* The underlying file */
111: /* The underlying VFS sqlite3_file is appended to this object */
112: };
113:
114: /*
115: ** An instance of the following object records the state of an
116: ** open file. This object is opaque to all users - the internal
117: ** structure is only visible to the functions below.
118: */
119: struct quota_FILE {
120: FILE *f; /* Open stdio file pointer */
121: sqlite3_int64 iOfst; /* Current offset into the file */
122: quotaFile *pFile; /* The file record in the quota system */
123: };
124:
125:
126: /************************* Global Variables **********************************/
127: /*
128: ** All global variables used by this file are containing within the following
129: ** gQuota structure.
130: */
131: static struct {
132: /* The pOrigVfs is the real, original underlying VFS implementation.
133: ** Most operations pass-through to the real VFS. This value is read-only
134: ** during operation. It is only modified at start-time and thus does not
135: ** require a mutex.
136: */
137: sqlite3_vfs *pOrigVfs;
138:
139: /* The sThisVfs is the VFS structure used by this shim. It is initialized
140: ** at start-time and thus does not require a mutex
141: */
142: sqlite3_vfs sThisVfs;
143:
144: /* The sIoMethods defines the methods used by sqlite3_file objects
145: ** associated with this shim. It is initialized at start-time and does
146: ** not require a mutex.
147: **
148: ** When the underlying VFS is called to open a file, it might return
149: ** either a version 1 or a version 2 sqlite3_file object. This shim
150: ** has to create a wrapper sqlite3_file of the same version. Hence
151: ** there are two I/O method structures, one for version 1 and the other
152: ** for version 2.
153: */
154: sqlite3_io_methods sIoMethodsV1;
155: sqlite3_io_methods sIoMethodsV2;
156:
157: /* True when this shim as been initialized.
158: */
159: int isInitialized;
160:
161: /* For run-time access any of the other global data structures in this
162: ** shim, the following mutex must be held.
163: */
164: sqlite3_mutex *pMutex;
165:
166: /* List of quotaGroup objects.
167: */
168: quotaGroup *pGroup;
169:
170: } gQuota;
171:
172: /************************* Utility Routines *********************************/
173: /*
174: ** Acquire and release the mutex used to serialize access to the
175: ** list of quotaGroups.
176: */
177: static void quotaEnter(void){ sqlite3_mutex_enter(gQuota.pMutex); }
178: static void quotaLeave(void){ sqlite3_mutex_leave(gQuota.pMutex); }
179:
180: /* Count the number of open files in a quotaGroup
181: */
182: static int quotaGroupOpenFileCount(quotaGroup *pGroup){
183: int N = 0;
184: quotaFile *pFile = pGroup->pFiles;
185: while( pFile ){
186: if( pFile->nRef ) N++;
187: pFile = pFile->pNext;
188: }
189: return N;
190: }
191:
192: /* Remove a file from a quota group.
193: */
194: static void quotaRemoveFile(quotaFile *pFile){
195: quotaGroup *pGroup = pFile->pGroup;
196: pGroup->iSize -= pFile->iSize;
197: *pFile->ppPrev = pFile->pNext;
198: if( pFile->pNext ) pFile->pNext->ppPrev = pFile->ppPrev;
199: sqlite3_free(pFile);
200: }
201:
202: /* Remove all files from a quota group. It is always the case that
203: ** all files will be closed when this routine is called.
204: */
205: static void quotaRemoveAllFiles(quotaGroup *pGroup){
206: while( pGroup->pFiles ){
207: assert( pGroup->pFiles->nRef==0 );
208: quotaRemoveFile(pGroup->pFiles);
209: }
210: }
211:
212:
213: /* If the reference count and threshold for a quotaGroup are both
214: ** zero, then destroy the quotaGroup.
215: */
216: static void quotaGroupDeref(quotaGroup *pGroup){
217: if( pGroup->iLimit==0 && quotaGroupOpenFileCount(pGroup)==0 ){
218: quotaRemoveAllFiles(pGroup);
219: *pGroup->ppPrev = pGroup->pNext;
220: if( pGroup->pNext ) pGroup->pNext->ppPrev = pGroup->ppPrev;
221: if( pGroup->xDestroy ) pGroup->xDestroy(pGroup->pArg);
222: sqlite3_free(pGroup);
223: }
224: }
225:
226: /*
227: ** Return TRUE if string z matches glob pattern zGlob.
228: **
229: ** Globbing rules:
230: **
231: ** '*' Matches any sequence of zero or more characters.
232: **
233: ** '?' Matches exactly one character.
234: **
235: ** [...] Matches one character from the enclosed list of
236: ** characters.
237: **
238: ** [^...] Matches one character not in the enclosed list.
239: **
240: ** / Matches "/" or "\\"
241: **
242: */
243: static int quotaStrglob(const char *zGlob, const char *z){
244: int c, c2, cx;
245: int invert;
246: int seen;
247:
248: while( (c = (*(zGlob++)))!=0 ){
249: if( c=='*' ){
250: while( (c=(*(zGlob++))) == '*' || c=='?' ){
251: if( c=='?' && (*(z++))==0 ) return 0;
252: }
253: if( c==0 ){
254: return 1;
255: }else if( c=='[' ){
256: while( *z && quotaStrglob(zGlob-1,z)==0 ){
257: z++;
258: }
259: return (*z)!=0;
260: }
261: cx = (c=='/') ? '\\' : c;
262: while( (c2 = (*(z++)))!=0 ){
263: while( c2!=c && c2!=cx ){
264: c2 = *(z++);
265: if( c2==0 ) return 0;
266: }
267: if( quotaStrglob(zGlob,z) ) return 1;
268: }
269: return 0;
270: }else if( c=='?' ){
271: if( (*(z++))==0 ) return 0;
272: }else if( c=='[' ){
273: int prior_c = 0;
274: seen = 0;
275: invert = 0;
276: c = *(z++);
277: if( c==0 ) return 0;
278: c2 = *(zGlob++);
279: if( c2=='^' ){
280: invert = 1;
281: c2 = *(zGlob++);
282: }
283: if( c2==']' ){
284: if( c==']' ) seen = 1;
285: c2 = *(zGlob++);
286: }
287: while( c2 && c2!=']' ){
288: if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
289: c2 = *(zGlob++);
290: if( c>=prior_c && c<=c2 ) seen = 1;
291: prior_c = 0;
292: }else{
293: if( c==c2 ){
294: seen = 1;
295: }
296: prior_c = c2;
297: }
298: c2 = *(zGlob++);
299: }
300: if( c2==0 || (seen ^ invert)==0 ) return 0;
301: }else if( c=='/' ){
302: if( z[0]!='/' && z[0]!='\\' ) return 0;
303: z++;
304: }else{
305: if( c!=(*(z++)) ) return 0;
306: }
307: }
308: return *z==0;
309: }
310:
311:
312: /* Find a quotaGroup given the filename.
313: **
314: ** Return a pointer to the quotaGroup object. Return NULL if not found.
315: */
316: static quotaGroup *quotaGroupFind(const char *zFilename){
317: quotaGroup *p;
318: for(p=gQuota.pGroup; p && quotaStrglob(p->zPattern, zFilename)==0;
319: p=p->pNext){}
320: return p;
321: }
322:
323: /* Translate an sqlite3_file* that is really a quotaConn* into
324: ** the sqlite3_file* for the underlying original VFS.
325: */
326: static sqlite3_file *quotaSubOpen(sqlite3_file *pConn){
327: quotaConn *p = (quotaConn*)pConn;
328: return (sqlite3_file*)&p[1];
329: }
330:
331: /* Find a file in a quota group and return a pointer to that file.
332: ** Return NULL if the file is not in the group.
333: */
334: static quotaFile *quotaFindFile(
335: quotaGroup *pGroup, /* Group in which to look for the file */
336: const char *zName, /* Full pathname of the file */
337: int createFlag /* Try to create the file if not found */
338: ){
339: quotaFile *pFile = pGroup->pFiles;
340: while( pFile && strcmp(pFile->zFilename, zName)!=0 ){
341: pFile = pFile->pNext;
342: }
343: if( pFile==0 && createFlag ){
344: int nName = strlen(zName);
345: pFile = (quotaFile *)sqlite3_malloc( sizeof(*pFile) + nName + 1 );
346: if( pFile ){
347: memset(pFile, 0, sizeof(*pFile));
348: pFile->zFilename = (char*)&pFile[1];
349: memcpy(pFile->zFilename, zName, nName+1);
350: pFile->pNext = pGroup->pFiles;
351: if( pGroup->pFiles ) pGroup->pFiles->ppPrev = &pFile->pNext;
352: pFile->ppPrev = &pGroup->pFiles;
353: pGroup->pFiles = pFile;
354: pFile->pGroup = pGroup;
355: }
356: }
357: return pFile;
358: }
359:
360: /*
361: ** Figure out if we are dealing with Unix, Windows, or some other
362: ** operating system. After the following block of preprocess macros,
363: ** all of SQLITE_OS_UNIX, SQLITE_OS_WIN, SQLITE_OS_OS2, and SQLITE_OS_OTHER
364: ** will defined to either 1 or 0. One of the four will be 1. The other
365: ** three will be 0.
366: */
367: #if defined(SQLITE_OS_OTHER)
368: # if SQLITE_OS_OTHER==1
369: # undef SQLITE_OS_UNIX
370: # define SQLITE_OS_UNIX 0
371: # undef SQLITE_OS_WIN
372: # define SQLITE_OS_WIN 0
373: # undef SQLITE_OS_OS2
374: # define SQLITE_OS_OS2 0
375: # else
376: # undef SQLITE_OS_OTHER
377: # endif
378: #endif
379: #if !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_OTHER)
380: # define SQLITE_OS_OTHER 0
381: # ifndef SQLITE_OS_WIN
382: # if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) \
383: || defined(__MINGW32__) || defined(__BORLANDC__)
384: # define SQLITE_OS_WIN 1
385: # define SQLITE_OS_UNIX 0
386: # define SQLITE_OS_OS2 0
387: # elif defined(__EMX__) || defined(_OS2) || defined(OS2) \
388: || defined(_OS2_) || defined(__OS2__)
389: # define SQLITE_OS_WIN 0
390: # define SQLITE_OS_UNIX 0
391: # define SQLITE_OS_OS2 1
392: # else
393: # define SQLITE_OS_WIN 0
394: # define SQLITE_OS_UNIX 1
395: # define SQLITE_OS_OS2 0
396: # endif
397: # else
398: # define SQLITE_OS_UNIX 0
399: # define SQLITE_OS_OS2 0
400: # endif
401: #else
402: # ifndef SQLITE_OS_WIN
403: # define SQLITE_OS_WIN 0
404: # endif
405: #endif
406:
407: #if SQLITE_OS_UNIX
408: # include <unistd.h>
409: #endif
410: #if SQLITE_OS_WIN
411: # include <windows.h>
412: # include <io.h>
413: #endif
414:
415: /*
416: ** Translate UTF8 to MBCS for use in fopen() calls. Return a pointer to the
417: ** translated text.. Call quota_mbcs_free() to deallocate any memory
418: ** used to store the returned pointer when done.
419: */
420: static char *quota_utf8_to_mbcs(const char *zUtf8){
421: #if SQLITE_OS_WIN
422: int n; /* Bytes in zUtf8 */
423: int nWide; /* number of UTF-16 characters */
424: int nMbcs; /* Bytes of MBCS */
425: LPWSTR zTmpWide; /* The UTF16 text */
426: char *zMbcs; /* The MBCS text */
427: int codepage; /* Code page used by fopen() */
428:
429: n = strlen(zUtf8);
430: nWide = MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, NULL, 0);
431: if( nWide==0 ) return 0;
432: zTmpWide = (LPWSTR)sqlite3_malloc( (nWide+1)*sizeof(zTmpWide[0]) );
433: if( zTmpWide==0 ) return 0;
434: MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zTmpWide, nWide);
435: codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;
436: nMbcs = WideCharToMultiByte(codepage, 0, zTmpWide, nWide, 0, 0, 0, 0);
437: zMbcs = nMbcs ? (char*)sqlite3_malloc( nMbcs+1 ) : 0;
438: if( zMbcs ){
439: WideCharToMultiByte(codepage, 0, zTmpWide, nWide, zMbcs, nMbcs, 0, 0);
440: }
441: sqlite3_free(zTmpWide);
442: return zMbcs;
443: #else
444: return (char*)zUtf8; /* No-op on unix */
445: #endif
446: }
447:
448: /*
449: ** Deallocate any memory allocated by quota_utf8_to_mbcs().
450: */
451: static void quota_mbcs_free(char *zOld){
452: #if SQLITE_OS_WIN
453: sqlite3_free(zOld);
454: #else
455: /* No-op on unix */
456: #endif
457: }
458:
459: /************************* VFS Method Wrappers *****************************/
460: /*
461: ** This is the xOpen method used for the "quota" VFS.
462: **
463: ** Most of the work is done by the underlying original VFS. This method
464: ** simply links the new file into the appropriate quota group if it is a
465: ** file that needs to be tracked.
466: */
467: static int quotaOpen(
468: sqlite3_vfs *pVfs, /* The quota VFS */
469: const char *zName, /* Name of file to be opened */
470: sqlite3_file *pConn, /* Fill in this file descriptor */
471: int flags, /* Flags to control the opening */
472: int *pOutFlags /* Flags showing results of opening */
473: ){
474: int rc; /* Result code */
475: quotaConn *pQuotaOpen; /* The new quota file descriptor */
476: quotaFile *pFile; /* Corresponding quotaFile obj */
477: quotaGroup *pGroup; /* The group file belongs to */
478: sqlite3_file *pSubOpen; /* Real file descriptor */
479: sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs; /* Real VFS */
480:
481: /* If the file is not a main database file or a WAL, then use the
482: ** normal xOpen method.
483: */
484: if( (flags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_WAL))==0 ){
485: return pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags);
486: }
487:
488: /* If the name of the file does not match any quota group, then
489: ** use the normal xOpen method.
490: */
491: quotaEnter();
492: pGroup = quotaGroupFind(zName);
493: if( pGroup==0 ){
494: rc = pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags);
495: }else{
496: /* If we get to this point, it means the file needs to be quota tracked.
497: */
498: pQuotaOpen = (quotaConn*)pConn;
499: pSubOpen = quotaSubOpen(pConn);
500: rc = pOrigVfs->xOpen(pOrigVfs, zName, pSubOpen, flags, pOutFlags);
501: if( rc==SQLITE_OK ){
502: pFile = quotaFindFile(pGroup, zName, 1);
503: if( pFile==0 ){
504: quotaLeave();
505: pSubOpen->pMethods->xClose(pSubOpen);
506: return SQLITE_NOMEM;
507: }
508: pFile->deleteOnClose = (flags & SQLITE_OPEN_DELETEONCLOSE)!=0;
509: pFile->nRef++;
510: pQuotaOpen->pFile = pFile;
511: if( pSubOpen->pMethods->iVersion==1 ){
512: pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV1;
513: }else{
514: pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV2;
515: }
516: }
517: }
518: quotaLeave();
519: return rc;
520: }
521:
522: /*
523: ** This is the xDelete method used for the "quota" VFS.
524: **
525: ** If the file being deleted is part of the quota group, then reduce
526: ** the size of the quota group accordingly. And remove the file from
527: ** the set of files in the quota group.
528: */
529: static int quotaDelete(
530: sqlite3_vfs *pVfs, /* The quota VFS */
531: const char *zName, /* Name of file to be deleted */
532: int syncDir /* Do a directory sync after deleting */
533: ){
534: int rc; /* Result code */
535: quotaFile *pFile; /* Files in the quota */
536: quotaGroup *pGroup; /* The group file belongs to */
537: sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs; /* Real VFS */
538:
539: /* Do the actual file delete */
540: rc = pOrigVfs->xDelete(pOrigVfs, zName, syncDir);
541:
542: /* If the file just deleted is a member of a quota group, then remove
543: ** it from that quota group.
544: */
545: if( rc==SQLITE_OK ){
546: quotaEnter();
547: pGroup = quotaGroupFind(zName);
548: if( pGroup ){
549: pFile = quotaFindFile(pGroup, zName, 0);
550: if( pFile ){
551: if( pFile->nRef ){
552: pFile->deleteOnClose = 1;
553: }else{
554: quotaRemoveFile(pFile);
555: quotaGroupDeref(pGroup);
556: }
557: }
558: }
559: quotaLeave();
560: }
561: return rc;
562: }
563:
564:
565: /************************ I/O Method Wrappers *******************************/
566:
567: /* xClose requests get passed through to the original VFS. But we
568: ** also have to unlink the quotaConn from the quotaFile and quotaGroup.
569: ** The quotaFile and/or quotaGroup are freed if they are no longer in use.
570: */
571: static int quotaClose(sqlite3_file *pConn){
572: quotaConn *p = (quotaConn*)pConn;
573: quotaFile *pFile = p->pFile;
574: sqlite3_file *pSubOpen = quotaSubOpen(pConn);
575: int rc;
576: rc = pSubOpen->pMethods->xClose(pSubOpen);
577: quotaEnter();
578: pFile->nRef--;
579: if( pFile->nRef==0 ){
580: quotaGroup *pGroup = pFile->pGroup;
581: if( pFile->deleteOnClose ){
582: gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0);
583: quotaRemoveFile(pFile);
584: }
585: quotaGroupDeref(pGroup);
586: }
587: quotaLeave();
588: return rc;
589: }
590:
591: /* Pass xRead requests directory thru to the original VFS without
592: ** further processing.
593: */
594: static int quotaRead(
595: sqlite3_file *pConn,
596: void *pBuf,
597: int iAmt,
598: sqlite3_int64 iOfst
599: ){
600: sqlite3_file *pSubOpen = quotaSubOpen(pConn);
601: return pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst);
602: }
603:
604: /* Check xWrite requests to see if they expand the file. If they do,
605: ** the perform a quota check before passing them through to the
606: ** original VFS.
607: */
608: static int quotaWrite(
609: sqlite3_file *pConn,
610: const void *pBuf,
611: int iAmt,
612: sqlite3_int64 iOfst
613: ){
614: quotaConn *p = (quotaConn*)pConn;
615: sqlite3_file *pSubOpen = quotaSubOpen(pConn);
616: sqlite3_int64 iEnd = iOfst+iAmt;
617: quotaGroup *pGroup;
618: quotaFile *pFile = p->pFile;
619: sqlite3_int64 szNew;
620:
621: if( pFile->iSize<iEnd ){
622: pGroup = pFile->pGroup;
623: quotaEnter();
624: szNew = pGroup->iSize - pFile->iSize + iEnd;
625: if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
626: if( pGroup->xCallback ){
627: pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew,
628: pGroup->pArg);
629: }
630: if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
631: quotaLeave();
632: return SQLITE_FULL;
633: }
634: }
635: pGroup->iSize = szNew;
636: pFile->iSize = iEnd;
637: quotaLeave();
638: }
639: return pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst);
640: }
641:
642: /* Pass xTruncate requests thru to the original VFS. If the
643: ** success, update the file size.
644: */
645: static int quotaTruncate(sqlite3_file *pConn, sqlite3_int64 size){
646: quotaConn *p = (quotaConn*)pConn;
647: sqlite3_file *pSubOpen = quotaSubOpen(pConn);
648: int rc = pSubOpen->pMethods->xTruncate(pSubOpen, size);
649: quotaFile *pFile = p->pFile;
650: quotaGroup *pGroup;
651: if( rc==SQLITE_OK ){
652: quotaEnter();
653: pGroup = pFile->pGroup;
654: pGroup->iSize -= pFile->iSize;
655: pFile->iSize = size;
656: pGroup->iSize += size;
657: quotaLeave();
658: }
659: return rc;
660: }
661:
662: /* Pass xSync requests through to the original VFS without change
663: */
664: static int quotaSync(sqlite3_file *pConn, int flags){
665: sqlite3_file *pSubOpen = quotaSubOpen(pConn);
666: return pSubOpen->pMethods->xSync(pSubOpen, flags);
667: }
668:
669: /* Pass xFileSize requests through to the original VFS but then
670: ** update the quotaGroup with the new size before returning.
671: */
672: static int quotaFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){
673: quotaConn *p = (quotaConn*)pConn;
674: sqlite3_file *pSubOpen = quotaSubOpen(pConn);
675: quotaFile *pFile = p->pFile;
676: quotaGroup *pGroup;
677: sqlite3_int64 sz;
678: int rc;
679:
680: rc = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);
681: if( rc==SQLITE_OK ){
682: quotaEnter();
683: pGroup = pFile->pGroup;
684: pGroup->iSize -= pFile->iSize;
685: pFile->iSize = sz;
686: pGroup->iSize += sz;
687: quotaLeave();
688: *pSize = sz;
689: }
690: return rc;
691: }
692:
693: /* Pass xLock requests through to the original VFS unchanged.
694: */
695: static int quotaLock(sqlite3_file *pConn, int lock){
696: sqlite3_file *pSubOpen = quotaSubOpen(pConn);
697: return pSubOpen->pMethods->xLock(pSubOpen, lock);
698: }
699:
700: /* Pass xUnlock requests through to the original VFS unchanged.
701: */
702: static int quotaUnlock(sqlite3_file *pConn, int lock){
703: sqlite3_file *pSubOpen = quotaSubOpen(pConn);
704: return pSubOpen->pMethods->xUnlock(pSubOpen, lock);
705: }
706:
707: /* Pass xCheckReservedLock requests through to the original VFS unchanged.
708: */
709: static int quotaCheckReservedLock(sqlite3_file *pConn, int *pResOut){
710: sqlite3_file *pSubOpen = quotaSubOpen(pConn);
711: return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut);
712: }
713:
714: /* Pass xFileControl requests through to the original VFS unchanged.
715: */
716: static int quotaFileControl(sqlite3_file *pConn, int op, void *pArg){
717: sqlite3_file *pSubOpen = quotaSubOpen(pConn);
718: int rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
719: #if defined(SQLITE_FCNTL_VFSNAME)
720: if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){
721: *(char**)pArg = sqlite3_mprintf("quota/%z", *(char**)pArg);
722: }
723: #endif
724: return rc;
725: }
726:
727: /* Pass xSectorSize requests through to the original VFS unchanged.
728: */
729: static int quotaSectorSize(sqlite3_file *pConn){
730: sqlite3_file *pSubOpen = quotaSubOpen(pConn);
731: return pSubOpen->pMethods->xSectorSize(pSubOpen);
732: }
733:
734: /* Pass xDeviceCharacteristics requests through to the original VFS unchanged.
735: */
736: static int quotaDeviceCharacteristics(sqlite3_file *pConn){
737: sqlite3_file *pSubOpen = quotaSubOpen(pConn);
738: return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen);
739: }
740:
741: /* Pass xShmMap requests through to the original VFS unchanged.
742: */
743: static int quotaShmMap(
744: sqlite3_file *pConn, /* Handle open on database file */
745: int iRegion, /* Region to retrieve */
746: int szRegion, /* Size of regions */
747: int bExtend, /* True to extend file if necessary */
748: void volatile **pp /* OUT: Mapped memory */
749: ){
750: sqlite3_file *pSubOpen = quotaSubOpen(pConn);
751: return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend, pp);
752: }
753:
754: /* Pass xShmLock requests through to the original VFS unchanged.
755: */
756: static int quotaShmLock(
757: sqlite3_file *pConn, /* Database file holding the shared memory */
758: int ofst, /* First lock to acquire or release */
759: int n, /* Number of locks to acquire or release */
760: int flags /* What to do with the lock */
761: ){
762: sqlite3_file *pSubOpen = quotaSubOpen(pConn);
763: return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags);
764: }
765:
766: /* Pass xShmBarrier requests through to the original VFS unchanged.
767: */
768: static void quotaShmBarrier(sqlite3_file *pConn){
769: sqlite3_file *pSubOpen = quotaSubOpen(pConn);
770: pSubOpen->pMethods->xShmBarrier(pSubOpen);
771: }
772:
773: /* Pass xShmUnmap requests through to the original VFS unchanged.
774: */
775: static int quotaShmUnmap(sqlite3_file *pConn, int deleteFlag){
776: sqlite3_file *pSubOpen = quotaSubOpen(pConn);
777: return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag);
778: }
779:
780: /************************** Public Interfaces *****************************/
781: /*
782: ** Initialize the quota VFS shim. Use the VFS named zOrigVfsName
783: ** as the VFS that does the actual work. Use the default if
784: ** zOrigVfsName==NULL.
785: **
786: ** The quota VFS shim is named "quota". It will become the default
787: ** VFS if makeDefault is non-zero.
788: **
789: ** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once
790: ** during start-up.
791: */
792: int sqlite3_quota_initialize(const char *zOrigVfsName, int makeDefault){
793: sqlite3_vfs *pOrigVfs;
794: if( gQuota.isInitialized ) return SQLITE_MISUSE;
795: pOrigVfs = sqlite3_vfs_find(zOrigVfsName);
796: if( pOrigVfs==0 ) return SQLITE_ERROR;
797: assert( pOrigVfs!=&gQuota.sThisVfs );
798: gQuota.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
799: if( !gQuota.pMutex ){
800: return SQLITE_NOMEM;
801: }
802: gQuota.isInitialized = 1;
803: gQuota.pOrigVfs = pOrigVfs;
804: gQuota.sThisVfs = *pOrigVfs;
805: gQuota.sThisVfs.xOpen = quotaOpen;
806: gQuota.sThisVfs.xDelete = quotaDelete;
807: gQuota.sThisVfs.szOsFile += sizeof(quotaConn);
808: gQuota.sThisVfs.zName = "quota";
809: gQuota.sIoMethodsV1.iVersion = 1;
810: gQuota.sIoMethodsV1.xClose = quotaClose;
811: gQuota.sIoMethodsV1.xRead = quotaRead;
812: gQuota.sIoMethodsV1.xWrite = quotaWrite;
813: gQuota.sIoMethodsV1.xTruncate = quotaTruncate;
814: gQuota.sIoMethodsV1.xSync = quotaSync;
815: gQuota.sIoMethodsV1.xFileSize = quotaFileSize;
816: gQuota.sIoMethodsV1.xLock = quotaLock;
817: gQuota.sIoMethodsV1.xUnlock = quotaUnlock;
818: gQuota.sIoMethodsV1.xCheckReservedLock = quotaCheckReservedLock;
819: gQuota.sIoMethodsV1.xFileControl = quotaFileControl;
820: gQuota.sIoMethodsV1.xSectorSize = quotaSectorSize;
821: gQuota.sIoMethodsV1.xDeviceCharacteristics = quotaDeviceCharacteristics;
822: gQuota.sIoMethodsV2 = gQuota.sIoMethodsV1;
823: gQuota.sIoMethodsV2.iVersion = 2;
824: gQuota.sIoMethodsV2.xShmMap = quotaShmMap;
825: gQuota.sIoMethodsV2.xShmLock = quotaShmLock;
826: gQuota.sIoMethodsV2.xShmBarrier = quotaShmBarrier;
827: gQuota.sIoMethodsV2.xShmUnmap = quotaShmUnmap;
828: sqlite3_vfs_register(&gQuota.sThisVfs, makeDefault);
829: return SQLITE_OK;
830: }
831:
832: /*
833: ** Shutdown the quota system.
834: **
835: ** All SQLite database connections must be closed before calling this
836: ** routine.
837: **
838: ** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once while
839: ** shutting down in order to free all remaining quota groups.
840: */
841: int sqlite3_quota_shutdown(void){
842: quotaGroup *pGroup;
843: if( gQuota.isInitialized==0 ) return SQLITE_MISUSE;
844: for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){
845: if( quotaGroupOpenFileCount(pGroup)>0 ) return SQLITE_MISUSE;
846: }
847: while( gQuota.pGroup ){
848: pGroup = gQuota.pGroup;
849: gQuota.pGroup = pGroup->pNext;
850: pGroup->iLimit = 0;
851: assert( quotaGroupOpenFileCount(pGroup)==0 );
852: quotaGroupDeref(pGroup);
853: }
854: gQuota.isInitialized = 0;
855: sqlite3_mutex_free(gQuota.pMutex);
856: sqlite3_vfs_unregister(&gQuota.sThisVfs);
857: memset(&gQuota, 0, sizeof(gQuota));
858: return SQLITE_OK;
859: }
860:
861: /*
862: ** Create or destroy a quota group.
863: **
864: ** The quota group is defined by the zPattern. When calling this routine
865: ** with a zPattern for a quota group that already exists, this routine
866: ** merely updates the iLimit, xCallback, and pArg values for that quota
867: ** group. If zPattern is new, then a new quota group is created.
868: **
869: ** If the iLimit for a quota group is set to zero, then the quota group
870: ** is disabled and will be deleted when the last database connection using
871: ** the quota group is closed.
872: **
873: ** Calling this routine on a zPattern that does not exist and with a
874: ** zero iLimit is a no-op.
875: **
876: ** A quota group must exist with a non-zero iLimit prior to opening
877: ** database connections if those connections are to participate in the
878: ** quota group. Creating a quota group does not affect database connections
879: ** that are already open.
880: */
881: int sqlite3_quota_set(
882: const char *zPattern, /* The filename pattern */
883: sqlite3_int64 iLimit, /* New quota to set for this quota group */
884: void (*xCallback)( /* Callback invoked when going over quota */
885: const char *zFilename, /* Name of file whose size increases */
886: sqlite3_int64 *piLimit, /* IN/OUT: The current limit */
887: sqlite3_int64 iSize, /* Total size of all files in the group */
888: void *pArg /* Client data */
889: ),
890: void *pArg, /* client data passed thru to callback */
891: void (*xDestroy)(void*) /* Optional destructor for pArg */
892: ){
893: quotaGroup *pGroup;
894: quotaEnter();
895: pGroup = gQuota.pGroup;
896: while( pGroup && strcmp(pGroup->zPattern, zPattern)!=0 ){
897: pGroup = pGroup->pNext;
898: }
899: if( pGroup==0 ){
900: int nPattern = strlen(zPattern);
901: if( iLimit<=0 ){
902: quotaLeave();
903: return SQLITE_OK;
904: }
905: pGroup = (quotaGroup *)sqlite3_malloc( sizeof(*pGroup) + nPattern + 1 );
906: if( pGroup==0 ){
907: quotaLeave();
908: return SQLITE_NOMEM;
909: }
910: memset(pGroup, 0, sizeof(*pGroup));
911: pGroup->zPattern = (char*)&pGroup[1];
912: memcpy((char *)pGroup->zPattern, zPattern, nPattern+1);
913: if( gQuota.pGroup ) gQuota.pGroup->ppPrev = &pGroup->pNext;
914: pGroup->pNext = gQuota.pGroup;
915: pGroup->ppPrev = &gQuota.pGroup;
916: gQuota.pGroup = pGroup;
917: }
918: pGroup->iLimit = iLimit;
919: pGroup->xCallback = xCallback;
920: if( pGroup->xDestroy && pGroup->pArg!=pArg ){
921: pGroup->xDestroy(pGroup->pArg);
922: }
923: pGroup->pArg = pArg;
924: pGroup->xDestroy = xDestroy;
925: quotaGroupDeref(pGroup);
926: quotaLeave();
927: return SQLITE_OK;
928: }
929:
930: /*
931: ** Bring the named file under quota management. Or if it is already under
932: ** management, update its size.
933: */
934: int sqlite3_quota_file(const char *zFilename){
935: char *zFull;
936: sqlite3_file *fd;
937: int rc;
938: int outFlags = 0;
939: sqlite3_int64 iSize;
940: int nAlloc = gQuota.sThisVfs.szOsFile + gQuota.sThisVfs.mxPathname+2;
941:
942: /* Allocate space for a file-handle and the full path for file zFilename */
943: fd = (sqlite3_file *)sqlite3_malloc(nAlloc);
944: if( fd==0 ){
945: rc = SQLITE_NOMEM;
946: }else{
947: zFull = &((char *)fd)[gQuota.sThisVfs.szOsFile];
948: rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
949: gQuota.sThisVfs.mxPathname+1, zFull);
950: }
951:
952: if( rc==SQLITE_OK ){
953: zFull[strlen(zFull)+1] = '\0';
954: rc = quotaOpen(&gQuota.sThisVfs, zFull, fd,
955: SQLITE_OPEN_READONLY | SQLITE_OPEN_MAIN_DB, &outFlags);
956: if( rc==SQLITE_OK ){
957: fd->pMethods->xFileSize(fd, &iSize);
958: fd->pMethods->xClose(fd);
959: }else if( rc==SQLITE_CANTOPEN ){
960: quotaGroup *pGroup;
961: quotaFile *pFile;
962: quotaEnter();
963: pGroup = quotaGroupFind(zFull);
964: if( pGroup ){
965: pFile = quotaFindFile(pGroup, zFull, 0);
966: if( pFile ) quotaRemoveFile(pFile);
967: }
968: quotaLeave();
969: }
970: }
971:
972: sqlite3_free(fd);
973: return rc;
974: }
975:
976: /*
977: ** Open a potentially quotaed file for I/O.
978: */
979: quota_FILE *sqlite3_quota_fopen(const char *zFilename, const char *zMode){
980: quota_FILE *p = 0;
981: char *zFull = 0;
982: char *zFullTranslated;
983: int rc;
984: quotaGroup *pGroup;
985: quotaFile *pFile;
986:
987: zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1);
988: if( zFull==0 ) return 0;
989: rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
990: gQuota.sThisVfs.mxPathname+1, zFull);
991: if( rc ) goto quota_fopen_error;
992: p = (quota_FILE*)sqlite3_malloc(sizeof(*p));
993: if( p==0 ) goto quota_fopen_error;
994: memset(p, 0, sizeof(*p));
995: zFullTranslated = quota_utf8_to_mbcs(zFull);
996: if( zFullTranslated==0 ) goto quota_fopen_error;
997: p->f = fopen(zFullTranslated, zMode);
998: quota_mbcs_free(zFullTranslated);
999: if( p->f==0 ) goto quota_fopen_error;
1000: quotaEnter();
1001: pGroup = quotaGroupFind(zFull);
1002: if( pGroup ){
1003: pFile = quotaFindFile(pGroup, zFull, 1);
1004: if( pFile==0 ){
1005: quotaLeave();
1006: goto quota_fopen_error;
1007: }
1008: pFile->nRef++;
1009: p->pFile = pFile;
1010: }
1011: quotaLeave();
1012: sqlite3_free(zFull);
1013: return p;
1014:
1015: quota_fopen_error:
1016: sqlite3_free(zFull);
1017: if( p && p->f ) fclose(p->f);
1018: sqlite3_free(p);
1019: return 0;
1020: }
1021:
1022: /*
1023: ** Read content from a quota_FILE
1024: */
1025: size_t sqlite3_quota_fread(
1026: void *pBuf, /* Store the content here */
1027: size_t size, /* Size of each element */
1028: size_t nmemb, /* Number of elements to read */
1029: quota_FILE *p /* Read from this quota_FILE object */
1030: ){
1031: return fread(pBuf, size, nmemb, p->f);
1032: }
1033:
1034: /*
1035: ** Write content into a quota_FILE. Invoke the quota callback and block
1036: ** the write if we exceed quota.
1037: */
1038: size_t sqlite3_quota_fwrite(
1039: void *pBuf, /* Take content to write from here */
1040: size_t size, /* Size of each element */
1041: size_t nmemb, /* Number of elements */
1042: quota_FILE *p /* Write to this quota_FILE objecct */
1043: ){
1044: sqlite3_int64 iOfst;
1045: sqlite3_int64 iEnd;
1046: sqlite3_int64 szNew;
1047: quotaFile *pFile;
1048:
1049: iOfst = ftell(p->f);
1050: iEnd = iOfst + size*nmemb;
1051: pFile = p->pFile;
1052: if( pFile && pFile->iSize<iEnd ){
1053: quotaGroup *pGroup = pFile->pGroup;
1054: quotaEnter();
1055: szNew = pGroup->iSize - pFile->iSize + iEnd;
1056: if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
1057: if( pGroup->xCallback ){
1058: pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew,
1059: pGroup->pArg);
1060: }
1061: if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
1062: iEnd = pGroup->iLimit - pGroup->iSize + pFile->iSize;
1063: nmemb = (iEnd - iOfst)/size;
1064: iEnd = iOfst + size*nmemb;
1065: szNew = pGroup->iSize - pFile->iSize + iEnd;
1066: }
1067: }
1068: pGroup->iSize = szNew;
1069: pFile->iSize = iEnd;
1070: quotaLeave();
1071: }
1072: return fwrite(pBuf, size, nmemb, p->f);
1073: }
1074:
1075: /*
1076: ** Close an open quota_FILE stream.
1077: */
1078: int sqlite3_quota_fclose(quota_FILE *p){
1079: int rc;
1080: quotaFile *pFile;
1081: rc = fclose(p->f);
1082: pFile = p->pFile;
1083: if( pFile ){
1084: quotaEnter();
1085: pFile->nRef--;
1086: if( pFile->nRef==0 ){
1087: quotaGroup *pGroup = pFile->pGroup;
1088: if( pFile->deleteOnClose ){
1089: gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0);
1090: quotaRemoveFile(pFile);
1091: }
1092: quotaGroupDeref(pGroup);
1093: }
1094: quotaLeave();
1095: }
1096: sqlite3_free(p);
1097: return rc;
1098: }
1099:
1100: /*
1101: ** Flush memory buffers for a quota_FILE to disk.
1102: */
1103: int sqlite3_quota_fflush(quota_FILE *p, int doFsync){
1104: int rc;
1105: rc = fflush(p->f);
1106: if( rc==0 && doFsync ){
1107: #if SQLITE_OS_UNIX
1108: rc = fsync(fileno(p->f));
1109: #endif
1110: #if SQLITE_OS_WIN
1111: rc = _commit(_fileno(p->f));
1112: #endif
1113: }
1114: return rc!=0;
1115: }
1116:
1117: /*
1118: ** Seek on a quota_FILE stream.
1119: */
1120: int sqlite3_quota_fseek(quota_FILE *p, long offset, int whence){
1121: return fseek(p->f, offset, whence);
1122: }
1123:
1124: /*
1125: ** rewind a quota_FILE stream.
1126: */
1127: void sqlite3_quota_rewind(quota_FILE *p){
1128: rewind(p->f);
1129: }
1130:
1131: /*
1132: ** Tell the current location of a quota_FILE stream.
1133: */
1134: long sqlite3_quota_ftell(quota_FILE *p){
1135: return ftell(p->f);
1136: }
1137:
1138: /*
1139: ** Remove a managed file. Update quotas accordingly.
1140: */
1141: int sqlite3_quota_remove(const char *zFilename){
1142: char *zFull; /* Full pathname for zFilename */
1143: int nFull; /* Number of bytes in zFilename */
1144: int rc; /* Result code */
1145: quotaGroup *pGroup; /* Group containing zFilename */
1146: quotaFile *pFile; /* A file in the group */
1147: quotaFile *pNextFile; /* next file in the group */
1148: int diff; /* Difference between filenames */
1149: char c; /* First character past end of pattern */
1150:
1151: zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1);
1152: if( zFull==0 ) return SQLITE_NOMEM;
1153: rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
1154: gQuota.sThisVfs.mxPathname+1, zFull);
1155: if( rc ){
1156: sqlite3_free(zFull);
1157: return rc;
1158: }
1159:
1160: /* Figure out the length of the full pathname. If the name ends with
1161: ** / (or \ on windows) then remove the trailing /.
1162: */
1163: nFull = strlen(zFull);
1164: if( nFull>0 && (zFull[nFull-1]=='/' || zFull[nFull-1]=='\\') ){
1165: nFull--;
1166: zFull[nFull] = 0;
1167: }
1168:
1169: quotaEnter();
1170: pGroup = quotaGroupFind(zFull);
1171: if( pGroup ){
1172: for(pFile=pGroup->pFiles; pFile && rc==SQLITE_OK; pFile=pNextFile){
1173: pNextFile = pFile->pNext;
1174: diff = memcmp(zFull, pFile->zFilename, nFull);
1175: if( diff==0 && ((c = pFile->zFilename[nFull])==0 || c=='/' || c=='\\') ){
1176: if( pFile->nRef ){
1177: pFile->deleteOnClose = 1;
1178: }else{
1179: rc = gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0);
1180: quotaRemoveFile(pFile);
1181: quotaGroupDeref(pGroup);
1182: }
1183: }
1184: }
1185: }
1186: quotaLeave();
1187: sqlite3_free(zFull);
1188: return rc;
1189: }
1190:
1191: /***************************** Test Code ***********************************/
1192: #ifdef SQLITE_TEST
1193: #include <tcl.h>
1194:
1195: /*
1196: ** Argument passed to a TCL quota-over-limit callback.
1197: */
1198: typedef struct TclQuotaCallback TclQuotaCallback;
1199: struct TclQuotaCallback {
1200: Tcl_Interp *interp; /* Interpreter in which to run the script */
1201: Tcl_Obj *pScript; /* Script to be run */
1202: };
1203:
1204: extern const char *sqlite3TestErrorName(int);
1205:
1206:
1207: /*
1208: ** This is the callback from a quota-over-limit.
1209: */
1210: static void tclQuotaCallback(
1211: const char *zFilename, /* Name of file whose size increases */
1212: sqlite3_int64 *piLimit, /* IN/OUT: The current limit */
1213: sqlite3_int64 iSize, /* Total size of all files in the group */
1214: void *pArg /* Client data */
1215: ){
1216: TclQuotaCallback *p; /* Callback script object */
1217: Tcl_Obj *pEval; /* Script to evaluate */
1218: Tcl_Obj *pVarname; /* Name of variable to pass as 2nd arg */
1219: unsigned int rnd; /* Random part of pVarname */
1220: int rc; /* Tcl error code */
1221:
1222: p = (TclQuotaCallback *)pArg;
1223: if( p==0 ) return;
1224:
1225: pVarname = Tcl_NewStringObj("::piLimit_", -1);
1226: Tcl_IncrRefCount(pVarname);
1227: sqlite3_randomness(sizeof(rnd), (void *)&rnd);
1228: Tcl_AppendObjToObj(pVarname, Tcl_NewIntObj((int)(rnd&0x7FFFFFFF)));
1229: Tcl_ObjSetVar2(p->interp, pVarname, 0, Tcl_NewWideIntObj(*piLimit), 0);
1230:
1231: pEval = Tcl_DuplicateObj(p->pScript);
1232: Tcl_IncrRefCount(pEval);
1233: Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zFilename, -1));
1234: Tcl_ListObjAppendElement(0, pEval, pVarname);
1235: Tcl_ListObjAppendElement(0, pEval, Tcl_NewWideIntObj(iSize));
1236: rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
1237:
1238: if( rc==TCL_OK ){
1239: Tcl_Obj *pLimit = Tcl_ObjGetVar2(p->interp, pVarname, 0, 0);
1240: rc = Tcl_GetWideIntFromObj(p->interp, pLimit, piLimit);
1241: Tcl_UnsetVar(p->interp, Tcl_GetString(pVarname), 0);
1242: }
1243:
1244: Tcl_DecrRefCount(pEval);
1245: Tcl_DecrRefCount(pVarname);
1246: if( rc!=TCL_OK ) Tcl_BackgroundError(p->interp);
1247: }
1248:
1249: /*
1250: ** Destructor for a TCL quota-over-limit callback.
1251: */
1252: static void tclCallbackDestructor(void *pObj){
1253: TclQuotaCallback *p = (TclQuotaCallback*)pObj;
1254: if( p ){
1255: Tcl_DecrRefCount(p->pScript);
1256: sqlite3_free((char *)p);
1257: }
1258: }
1259:
1260: /*
1261: ** tclcmd: sqlite3_quota_initialize NAME MAKEDEFAULT
1262: */
1263: static int test_quota_initialize(
1264: void * clientData,
1265: Tcl_Interp *interp,
1266: int objc,
1267: Tcl_Obj *CONST objv[]
1268: ){
1269: const char *zName; /* Name of new quota VFS */
1270: int makeDefault; /* True to make the new VFS the default */
1271: int rc; /* Value returned by quota_initialize() */
1272:
1273: /* Process arguments */
1274: if( objc!=3 ){
1275: Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT");
1276: return TCL_ERROR;
1277: }
1278: zName = Tcl_GetString(objv[1]);
1279: if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR;
1280: if( zName[0]=='\0' ) zName = 0;
1281:
1282: /* Call sqlite3_quota_initialize() */
1283: rc = sqlite3_quota_initialize(zName, makeDefault);
1284: Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
1285:
1286: return TCL_OK;
1287: }
1288:
1289: /*
1290: ** tclcmd: sqlite3_quota_shutdown
1291: */
1292: static int test_quota_shutdown(
1293: void * clientData,
1294: Tcl_Interp *interp,
1295: int objc,
1296: Tcl_Obj *CONST objv[]
1297: ){
1298: int rc; /* Value returned by quota_shutdown() */
1299:
1300: if( objc!=1 ){
1301: Tcl_WrongNumArgs(interp, 1, objv, "");
1302: return TCL_ERROR;
1303: }
1304:
1305: /* Call sqlite3_quota_shutdown() */
1306: rc = sqlite3_quota_shutdown();
1307: Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
1308:
1309: return TCL_OK;
1310: }
1311:
1312: /*
1313: ** tclcmd: sqlite3_quota_set PATTERN LIMIT SCRIPT
1314: */
1315: static int test_quota_set(
1316: void * clientData,
1317: Tcl_Interp *interp,
1318: int objc,
1319: Tcl_Obj *CONST objv[]
1320: ){
1321: const char *zPattern; /* File pattern to configure */
1322: sqlite3_int64 iLimit; /* Initial quota in bytes */
1323: Tcl_Obj *pScript; /* Tcl script to invoke to increase quota */
1324: int rc; /* Value returned by quota_set() */
1325: TclQuotaCallback *p; /* Callback object */
1326: int nScript; /* Length of callback script */
1327: void (*xDestroy)(void*); /* Optional destructor for pArg */
1328: void (*xCallback)(const char *, sqlite3_int64 *, sqlite3_int64, void *);
1329:
1330: /* Process arguments */
1331: if( objc!=4 ){
1332: Tcl_WrongNumArgs(interp, 1, objv, "PATTERN LIMIT SCRIPT");
1333: return TCL_ERROR;
1334: }
1335: zPattern = Tcl_GetString(objv[1]);
1336: if( Tcl_GetWideIntFromObj(interp, objv[2], &iLimit) ) return TCL_ERROR;
1337: pScript = objv[3];
1338: Tcl_GetStringFromObj(pScript, &nScript);
1339:
1340: if( nScript>0 ){
1341: /* Allocate a TclQuotaCallback object */
1342: p = (TclQuotaCallback *)sqlite3_malloc(sizeof(TclQuotaCallback));
1343: if( !p ){
1344: Tcl_SetResult(interp, (char *)"SQLITE_NOMEM", TCL_STATIC);
1345: return TCL_OK;
1346: }
1347: memset(p, 0, sizeof(TclQuotaCallback));
1348: p->interp = interp;
1349: Tcl_IncrRefCount(pScript);
1350: p->pScript = pScript;
1351: xDestroy = tclCallbackDestructor;
1352: xCallback = tclQuotaCallback;
1353: }else{
1354: p = 0;
1355: xDestroy = 0;
1356: xCallback = 0;
1357: }
1358:
1359: /* Invoke sqlite3_quota_set() */
1360: rc = sqlite3_quota_set(zPattern, iLimit, xCallback, (void*)p, xDestroy);
1361:
1362: Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
1363: return TCL_OK;
1364: }
1365:
1366: /*
1367: ** tclcmd: sqlite3_quota_file FILENAME
1368: */
1369: static int test_quota_file(
1370: void * clientData,
1371: Tcl_Interp *interp,
1372: int objc,
1373: Tcl_Obj *CONST objv[]
1374: ){
1375: const char *zFilename; /* File pattern to configure */
1376: int rc; /* Value returned by quota_file() */
1377:
1378: /* Process arguments */
1379: if( objc!=2 ){
1380: Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
1381: return TCL_ERROR;
1382: }
1383: zFilename = Tcl_GetString(objv[1]);
1384:
1385: /* Invoke sqlite3_quota_file() */
1386: rc = sqlite3_quota_file(zFilename);
1387:
1388: Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
1389: return TCL_OK;
1390: }
1391:
1392: /*
1393: ** tclcmd: sqlite3_quota_dump
1394: */
1395: static int test_quota_dump(
1396: void * clientData,
1397: Tcl_Interp *interp,
1398: int objc,
1399: Tcl_Obj *CONST objv[]
1400: ){
1401: Tcl_Obj *pResult;
1402: Tcl_Obj *pGroupTerm;
1403: Tcl_Obj *pFileTerm;
1404: quotaGroup *pGroup;
1405: quotaFile *pFile;
1406:
1407: pResult = Tcl_NewObj();
1408: quotaEnter();
1409: for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){
1410: pGroupTerm = Tcl_NewObj();
1411: Tcl_ListObjAppendElement(interp, pGroupTerm,
1412: Tcl_NewStringObj(pGroup->zPattern, -1));
1413: Tcl_ListObjAppendElement(interp, pGroupTerm,
1414: Tcl_NewWideIntObj(pGroup->iLimit));
1415: Tcl_ListObjAppendElement(interp, pGroupTerm,
1416: Tcl_NewWideIntObj(pGroup->iSize));
1417: for(pFile=pGroup->pFiles; pFile; pFile=pFile->pNext){
1418: int i;
1419: char zTemp[1000];
1420: pFileTerm = Tcl_NewObj();
1421: sqlite3_snprintf(sizeof(zTemp), zTemp, "%s", pFile->zFilename);
1422: for(i=0; zTemp[i]; i++){ if( zTemp[i]=='\\' ) zTemp[i] = '/'; }
1423: Tcl_ListObjAppendElement(interp, pFileTerm,
1424: Tcl_NewStringObj(zTemp, -1));
1425: Tcl_ListObjAppendElement(interp, pFileTerm,
1426: Tcl_NewWideIntObj(pFile->iSize));
1427: Tcl_ListObjAppendElement(interp, pFileTerm,
1428: Tcl_NewWideIntObj(pFile->nRef));
1429: Tcl_ListObjAppendElement(interp, pFileTerm,
1430: Tcl_NewWideIntObj(pFile->deleteOnClose));
1431: Tcl_ListObjAppendElement(interp, pGroupTerm, pFileTerm);
1432: }
1433: Tcl_ListObjAppendElement(interp, pResult, pGroupTerm);
1434: }
1435: quotaLeave();
1436: Tcl_SetObjResult(interp, pResult);
1437: return TCL_OK;
1438: }
1439:
1440: /*
1441: ** tclcmd: sqlite3_quota_fopen FILENAME MODE
1442: */
1443: static int test_quota_fopen(
1444: void * clientData,
1445: Tcl_Interp *interp,
1446: int objc,
1447: Tcl_Obj *CONST objv[]
1448: ){
1449: const char *zFilename; /* File pattern to configure */
1450: const char *zMode; /* Mode string */
1451: quota_FILE *p; /* Open string object */
1452: char zReturn[50]; /* Name of pointer to return */
1453:
1454: /* Process arguments */
1455: if( objc!=3 ){
1456: Tcl_WrongNumArgs(interp, 1, objv, "FILENAME MODE");
1457: return TCL_ERROR;
1458: }
1459: zFilename = Tcl_GetString(objv[1]);
1460: zMode = Tcl_GetString(objv[2]);
1461: p = sqlite3_quota_fopen(zFilename, zMode);
1462: sqlite3_snprintf(sizeof(zReturn), zReturn, "%p", p);
1463: Tcl_SetResult(interp, zReturn, TCL_VOLATILE);
1464: return TCL_OK;
1465: }
1466:
1467: /* Defined in test1.c */
1468: extern void *sqlite3TestTextToPtr(const char*);
1469:
1470: /*
1471: ** tclcmd: sqlite3_quota_fread HANDLE SIZE NELEM
1472: */
1473: static int test_quota_fread(
1474: void * clientData,
1475: Tcl_Interp *interp,
1476: int objc,
1477: Tcl_Obj *CONST objv[]
1478: ){
1479: quota_FILE *p;
1480: char *zBuf;
1481: int sz;
1482: int nElem;
1483: int got;
1484:
1485: if( objc!=4 ){
1486: Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM");
1487: return TCL_ERROR;
1488: }
1489: p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1490: if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR;
1491: if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR;
1492: zBuf = (char*)sqlite3_malloc( sz*nElem + 1 );
1493: if( zBuf==0 ){
1494: Tcl_SetResult(interp, "out of memory", TCL_STATIC);
1495: return TCL_ERROR;
1496: }
1497: got = sqlite3_quota_fread(zBuf, sz, nElem, p);
1498: if( got<0 ) got = 0;
1499: zBuf[got*sz] = 0;
1500: Tcl_SetResult(interp, zBuf, TCL_VOLATILE);
1501: sqlite3_free(zBuf);
1502: return TCL_OK;
1503: }
1504:
1505: /*
1506: ** tclcmd: sqlite3_quota_fwrite HANDLE SIZE NELEM CONTENT
1507: */
1508: static int test_quota_fwrite(
1509: void * clientData,
1510: Tcl_Interp *interp,
1511: int objc,
1512: Tcl_Obj *CONST objv[]
1513: ){
1514: quota_FILE *p;
1515: char *zBuf;
1516: int sz;
1517: int nElem;
1518: int got;
1519:
1520: if( objc!=5 ){
1521: Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM CONTENT");
1522: return TCL_ERROR;
1523: }
1524: p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1525: if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR;
1526: if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR;
1527: zBuf = Tcl_GetString(objv[4]);
1528: got = sqlite3_quota_fwrite(zBuf, sz, nElem, p);
1529: Tcl_SetObjResult(interp, Tcl_NewIntObj(got));
1530: return TCL_OK;
1531: }
1532:
1533: /*
1534: ** tclcmd: sqlite3_quota_fclose HANDLE
1535: */
1536: static int test_quota_fclose(
1537: void * clientData,
1538: Tcl_Interp *interp,
1539: int objc,
1540: Tcl_Obj *CONST objv[]
1541: ){
1542: quota_FILE *p;
1543: int rc;
1544:
1545: if( objc!=2 ){
1546: Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
1547: return TCL_ERROR;
1548: }
1549: p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1550: rc = sqlite3_quota_fclose(p);
1551: Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
1552: return TCL_OK;
1553: }
1554:
1555: /*
1556: ** tclcmd: sqlite3_quota_fflush HANDLE ?HARDSYNC?
1557: */
1558: static int test_quota_fflush(
1559: void * clientData,
1560: Tcl_Interp *interp,
1561: int objc,
1562: Tcl_Obj *CONST objv[]
1563: ){
1564: quota_FILE *p;
1565: int rc;
1566: int doSync = 0;
1567:
1568: if( objc!=2 && objc!=3 ){
1569: Tcl_WrongNumArgs(interp, 1, objv, "HANDLE ?HARDSYNC?");
1570: return TCL_ERROR;
1571: }
1572: p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1573: if( objc==3 ){
1574: if( Tcl_GetBooleanFromObj(interp, objv[2], &doSync) ) return TCL_ERROR;
1575: }
1576: rc = sqlite3_quota_fflush(p, doSync);
1577: Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
1578: return TCL_OK;
1579: }
1580:
1581: /*
1582: ** tclcmd: sqlite3_quota_fseek HANDLE OFFSET WHENCE
1583: */
1584: static int test_quota_fseek(
1585: void * clientData,
1586: Tcl_Interp *interp,
1587: int objc,
1588: Tcl_Obj *CONST objv[]
1589: ){
1590: quota_FILE *p;
1591: int ofst;
1592: const char *zWhence;
1593: int whence;
1594: int rc;
1595:
1596: if( objc!=4 ){
1597: Tcl_WrongNumArgs(interp, 1, objv, "HANDLE OFFSET WHENCE");
1598: return TCL_ERROR;
1599: }
1600: p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1601: if( Tcl_GetIntFromObj(interp, objv[2], &ofst) ) return TCL_ERROR;
1602: zWhence = Tcl_GetString(objv[3]);
1603: if( strcmp(zWhence, "SEEK_SET")==0 ){
1604: whence = SEEK_SET;
1605: }else if( strcmp(zWhence, "SEEK_CUR")==0 ){
1606: whence = SEEK_CUR;
1607: }else if( strcmp(zWhence, "SEEK_END")==0 ){
1608: whence = SEEK_END;
1609: }else{
1610: Tcl_AppendResult(interp,
1611: "WHENCE should be SEEK_SET, SEEK_CUR, or SEEK_END", (char*)0);
1612: return TCL_ERROR;
1613: }
1614: rc = sqlite3_quota_fseek(p, ofst, whence);
1615: Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
1616: return TCL_OK;
1617: }
1618:
1619: /*
1620: ** tclcmd: sqlite3_quota_rewind HANDLE
1621: */
1622: static int test_quota_rewind(
1623: void * clientData,
1624: Tcl_Interp *interp,
1625: int objc,
1626: Tcl_Obj *CONST objv[]
1627: ){
1628: quota_FILE *p;
1629: if( objc!=2 ){
1630: Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
1631: return TCL_ERROR;
1632: }
1633: p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1634: sqlite3_quota_rewind(p);
1635: return TCL_OK;
1636: }
1637:
1638: /*
1639: ** tclcmd: sqlite3_quota_ftell HANDLE
1640: */
1641: static int test_quota_ftell(
1642: void * clientData,
1643: Tcl_Interp *interp,
1644: int objc,
1645: Tcl_Obj *CONST objv[]
1646: ){
1647: quota_FILE *p;
1648: sqlite3_int64 x;
1649: if( objc!=2 ){
1650: Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
1651: return TCL_ERROR;
1652: }
1653: p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
1654: x = sqlite3_quota_ftell(p);
1655: Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x));
1656: return TCL_OK;
1657: }
1658:
1659: /*
1660: ** tclcmd: sqlite3_quota_remove FILENAME
1661: */
1662: static int test_quota_remove(
1663: void * clientData,
1664: Tcl_Interp *interp,
1665: int objc,
1666: Tcl_Obj *CONST objv[]
1667: ){
1668: const char *zFilename; /* File pattern to configure */
1669: int rc;
1670: if( objc!=2 ){
1671: Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
1672: return TCL_ERROR;
1673: }
1674: zFilename = Tcl_GetString(objv[1]);
1675: rc = sqlite3_quota_remove(zFilename);
1676: Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
1677: return TCL_OK;
1678: }
1679:
1680: /*
1681: ** tclcmd: sqlite3_quota_glob PATTERN TEXT
1682: **
1683: ** Test the glob pattern matching. Return 1 if TEXT matches PATTERN
1684: ** and return 0 if it does not.
1685: */
1686: static int test_quota_glob(
1687: void * clientData,
1688: Tcl_Interp *interp,
1689: int objc,
1690: Tcl_Obj *CONST objv[]
1691: ){
1692: const char *zPattern; /* The glob pattern */
1693: const char *zText; /* Text to compare agains the pattern */
1694: int rc;
1695: if( objc!=3 ){
1696: Tcl_WrongNumArgs(interp, 1, objv, "PATTERN TEXT");
1697: return TCL_ERROR;
1698: }
1699: zPattern = Tcl_GetString(objv[1]);
1700: zText = Tcl_GetString(objv[2]);
1701: rc = quotaStrglob(zPattern, zText);
1702: Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
1703: return TCL_OK;
1704: }
1705:
1706: /*
1707: ** This routine registers the custom TCL commands defined in this
1708: ** module. This should be the only procedure visible from outside
1709: ** of this module.
1710: */
1711: int Sqlitequota_Init(Tcl_Interp *interp){
1712: static struct {
1713: char *zName;
1714: Tcl_ObjCmdProc *xProc;
1715: } aCmd[] = {
1716: { "sqlite3_quota_initialize", test_quota_initialize },
1717: { "sqlite3_quota_shutdown", test_quota_shutdown },
1718: { "sqlite3_quota_set", test_quota_set },
1719: { "sqlite3_quota_file", test_quota_file },
1720: { "sqlite3_quota_dump", test_quota_dump },
1721: { "sqlite3_quota_fopen", test_quota_fopen },
1722: { "sqlite3_quota_fread", test_quota_fread },
1723: { "sqlite3_quota_fwrite", test_quota_fwrite },
1724: { "sqlite3_quota_fclose", test_quota_fclose },
1725: { "sqlite3_quota_fflush", test_quota_fflush },
1726: { "sqlite3_quota_fseek", test_quota_fseek },
1727: { "sqlite3_quota_rewind", test_quota_rewind },
1728: { "sqlite3_quota_ftell", test_quota_ftell },
1729: { "sqlite3_quota_remove", test_quota_remove },
1730: { "sqlite3_quota_glob", test_quota_glob },
1731: };
1732: int i;
1733:
1734: for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
1735: Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
1736: }
1737:
1738: return TCL_OK;
1739: }
1740: #endif
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>