Annotation of embedaddon/sqlite3/src/test_quota.c, revision 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>