/ Changes On Branch quota-stdio
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch quota-stdio Excluding Merge-Ins

This is equivalent to a diff from 431556cac0 to e85cfe9a17

2011-12-15
17:44
Add stdio support to the quota VFS. (check-in: 322bd15f97 user: drh tags: trunk)
17:42
Use _commit() rather than FlushFileBuffers() as a substitute for fsync() on windows. Also cast for C++ and add support for SQLITE_FCNTL_VFSNAME. (Closed-Leaf check-in: e85cfe9a17 user: drh tags: quota-stdio)
2011-12-14
00:04
Harden the utf8-to-mbcs converter in the quota module against failures. (check-in: 1cda511deb user: drh tags: quota-stdio)
2011-12-06
00:47
Merge the winGetLastError fixes into trunk. (check-in: 5b03ba9db0 user: drh tags: trunk)
2011-12-01
18:44
Add stdio-like I/O interfaces to the test_quota VFS. This is a prototype change for discussion and is mostly untested. This is an alternative to adding stdio-like I/O interfaces in the core. There is no guarantee that this code will make it into the trunk. If it does get to trunk, there could be many changes to the interface first. (check-in: bd3ce723f1 user: drh tags: quota-stdio)
02:32
Add a prototype implementation of stdio-like routines for accessing the VFS. This is intended as documentation. The code is untested. There is no guarantee that any of this will ever make it into trunk. Substantial revision is possible prior to reaching trunk, if it ever does. (Closed-Leaf check-in: 8936542b22 user: drh tags: vfs-stdio)
2011-11-29
15:40
Remove unused fields from the Parse object. Documentation and formatting improvements on data structure definitions. (check-in: 431556cac0 user: drh tags: trunk)
15:04
Remove unused boolean fields from the UnpackedRecord object. (check-in: b10d091ec0 user: drh tags: trunk)

Changes to src/test_quota.c.

    23     23   ** However, before returning SQLITE_FULL, the write requests invoke
    24     24   ** a callback function that is configurable for each quota group.
    25     25   ** This callback has the opportunity to enlarge the quota.  If the
    26     26   ** callback does enlarge the quota such that the total size of all
    27     27   ** files within the group is less than the new quota, then the write
    28     28   ** continues as if nothing had happened.
    29     29   */
    30         -#include "sqlite3.h"
           30  +#include "test_quota.h"
    31     31   #include <string.h>
    32     32   #include <assert.h>
    33     33   
    34     34   /*
    35     35   ** For an build without mutexes, no-op the mutex calls.
    36     36   */
    37     37   #if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0
................................................................................
   106    106   ** VFS is appended to this structure.
   107    107   */
   108    108   struct quotaConn {
   109    109     sqlite3_file base;              /* Base class - must be first */
   110    110     quotaFile *pFile;               /* The underlying file */
   111    111     /* The underlying VFS sqlite3_file is appended to this object */
   112    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  +
   113    125   
   114    126   /************************* Global Variables **********************************/
   115    127   /*
   116    128   ** All global variables used by this file are containing within the following
   117    129   ** gQuota structure.
   118    130   */
   119    131   static struct {
................................................................................
   221    233   **      '?'       Matches exactly one character.
   222    234   **
   223    235   **     [...]      Matches one character from the enclosed list of
   224    236   **                characters.
   225    237   **
   226    238   **     [^...]     Matches one character not in the enclosed list.
   227    239   **
          240  +**     /          Matches "/" or "\\"
          241  +**
   228    242   */
   229    243   static int quotaStrglob(const char *zGlob, const char *z){
   230         -  int c, c2;
          244  +  int c, c2, cx;
   231    245     int invert;
   232    246     int seen;
   233    247   
   234    248     while( (c = (*(zGlob++)))!=0 ){
   235    249       if( c=='*' ){
   236    250         while( (c=(*(zGlob++))) == '*' || c=='?' ){
   237    251           if( c=='?' && (*(z++))==0 ) return 0;
................................................................................
   240    254           return 1;
   241    255         }else if( c=='[' ){
   242    256           while( *z && quotaStrglob(zGlob-1,z)==0 ){
   243    257             z++;
   244    258           }
   245    259           return (*z)!=0;
   246    260         }
          261  +      cx = (c=='/') ? '\\' : c;
   247    262         while( (c2 = (*(z++)))!=0 ){
   248         -        while( c2!=c ){
          263  +        while( c2!=c && c2!=cx ){
   249    264             c2 = *(z++);
   250    265             if( c2==0 ) return 0;
   251    266           }
   252    267           if( quotaStrglob(zGlob,z) ) return 1;
   253    268         }
   254    269         return 0;
   255    270       }else if( c=='?' ){
................................................................................
   279    294               seen = 1;
   280    295             }
   281    296             prior_c = c2;
   282    297           }
   283    298           c2 = *(zGlob++);
   284    299         }
   285    300         if( c2==0 || (seen ^ invert)==0 ) return 0;
          301  +    }else if( c=='/' ){
          302  +      if( z[0]!='/' && z[0]!='\\' ) return 0;
          303  +      z++;
   286    304       }else{
   287    305         if( c!=(*(z++)) ) return 0;
   288    306       }
   289    307     }
   290    308     return *z==0;
   291    309   }
   292    310   
................................................................................
   309    327     quotaConn *p = (quotaConn*)pConn;
   310    328     return (sqlite3_file*)&p[1];
   311    329   }
   312    330   
   313    331   /* Find a file in a quota group and return a pointer to that file.
   314    332   ** Return NULL if the file is not in the group.
   315    333   */
   316         -static quotaFile *quotaFindFile(quotaGroup *pGroup, const char *zName){
          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  +){
   317    339     quotaFile *pFile = pGroup->pFiles;
   318    340     while( pFile && strcmp(pFile->zFilename, zName)!=0 ){
   319    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  +    }
   320    356     }
   321    357     return pFile;
   322    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  +}
   323    458   
   324    459   /************************* VFS Method Wrappers *****************************/
   325    460   /*
   326    461   ** This is the xOpen method used for the "quota" VFS.
   327    462   **
   328    463   ** Most of the work is done by the underlying original VFS.  This method
   329    464   ** simply links the new file into the appropriate quota group if it is a
................................................................................
   360    495     }else{
   361    496       /* If we get to this point, it means the file needs to be quota tracked.
   362    497       */
   363    498       pQuotaOpen = (quotaConn*)pConn;
   364    499       pSubOpen = quotaSubOpen(pConn);
   365    500       rc = pOrigVfs->xOpen(pOrigVfs, zName, pSubOpen, flags, pOutFlags);
   366    501       if( rc==SQLITE_OK ){
   367         -      pFile = quotaFindFile(pGroup, zName);
          502  +      pFile = quotaFindFile(pGroup, zName, 1);
   368    503         if( pFile==0 ){
   369         -        int nName = strlen(zName);
   370         -        pFile = (quotaFile *)sqlite3_malloc( sizeof(*pFile) + nName + 1 );
   371         -        if( pFile==0 ){
   372         -          quotaLeave();
   373         -          pSubOpen->pMethods->xClose(pSubOpen);
   374         -          return SQLITE_NOMEM;
   375         -        }
   376         -        memset(pFile, 0, sizeof(*pFile));
   377         -        pFile->zFilename = (char*)&pFile[1];
   378         -        memcpy(pFile->zFilename, zName, nName+1);
   379         -        pFile->pNext = pGroup->pFiles;
   380         -        if( pGroup->pFiles ) pGroup->pFiles->ppPrev = &pFile->pNext;
   381         -        pFile->ppPrev = &pGroup->pFiles;
   382         -        pGroup->pFiles = pFile;
   383         -        pFile->pGroup = pGroup;
   384         -        pFile->deleteOnClose = (flags & SQLITE_OPEN_DELETEONCLOSE)!=0;
          504  +        quotaLeave();
          505  +        pSubOpen->pMethods->xClose(pSubOpen);
          506  +        return SQLITE_NOMEM;
   385    507         }
          508  +      pFile->deleteOnClose = (flags & SQLITE_OPEN_DELETEONCLOSE)!=0;
   386    509         pFile->nRef++;
   387    510         pQuotaOpen->pFile = pFile;
   388    511         if( pSubOpen->pMethods->iVersion==1 ){
   389    512           pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV1;
   390    513         }else{
   391    514           pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV2;
   392    515         }
................................................................................
   419    542     /* If the file just deleted is a member of a quota group, then remove
   420    543     ** it from that quota group.
   421    544     */
   422    545     if( rc==SQLITE_OK ){
   423    546       quotaEnter();
   424    547       pGroup = quotaGroupFind(zName);
   425    548       if( pGroup ){
   426         -      pFile = quotaFindFile(pGroup, zName);
          549  +      pFile = quotaFindFile(pGroup, zName, 0);
   427    550         if( pFile ){
   428    551           if( pFile->nRef ){
   429    552             pFile->deleteOnClose = 1;
   430    553           }else{
   431    554             quotaRemoveFile(pFile);
   432    555             quotaGroupDeref(pGroup);
   433    556           }
................................................................................
   451    574     sqlite3_file *pSubOpen = quotaSubOpen(pConn);
   452    575     int rc;
   453    576     rc = pSubOpen->pMethods->xClose(pSubOpen);
   454    577     quotaEnter();
   455    578     pFile->nRef--;
   456    579     if( pFile->nRef==0 ){
   457    580       quotaGroup *pGroup = pFile->pGroup;
   458         -    if( pFile->deleteOnClose ) quotaRemoveFile(pFile);
          581  +    if( pFile->deleteOnClose ){
          582  +      gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0);
          583  +      quotaRemoveFile(pFile);
          584  +    }
   459    585       quotaGroupDeref(pGroup);
   460    586     }
   461    587     quotaLeave();
   462    588     return rc;
   463    589   }
   464    590   
   465    591   /* Pass xRead requests directory thru to the original VFS without
................................................................................
   585    711     return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut);
   586    712   }
   587    713   
   588    714   /* Pass xFileControl requests through to the original VFS unchanged.
   589    715   */
   590    716   static int quotaFileControl(sqlite3_file *pConn, int op, void *pArg){
   591    717     sqlite3_file *pSubOpen = quotaSubOpen(pConn);
   592         -  return pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
          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;
   593    725   }
   594    726   
   595    727   /* Pass xSectorSize requests through to the original VFS unchanged.
   596    728   */
   597    729   static int quotaSectorSize(sqlite3_file *pConn){
   598    730     sqlite3_file *pSubOpen = quotaSubOpen(pConn);
   599    731     return pSubOpen->pMethods->xSectorSize(pSubOpen);
................................................................................
   801    933   */
   802    934   int sqlite3_quota_file(const char *zFilename){
   803    935     char *zFull;
   804    936     sqlite3_file *fd;
   805    937     int rc;
   806    938     int outFlags = 0;
   807    939     sqlite3_int64 iSize;
   808         -  fd = sqlite3_malloc(gQuota.sThisVfs.szOsFile + gQuota.sThisVfs.mxPathname+1);
          940  +  fd = (sqlite3_file*)sqlite3_malloc(gQuota.sThisVfs.szOsFile +
          941  +                                     gQuota.sThisVfs.mxPathname+1);
   809    942     if( fd==0 ) return SQLITE_NOMEM;
   810    943     zFull = gQuota.sThisVfs.szOsFile + (char*)fd;
   811    944     rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
   812    945                                         gQuota.sThisVfs.mxPathname+1, zFull);
   813    946     if( rc==SQLITE_OK ){
   814    947       rc = quotaOpen(&gQuota.sThisVfs, zFull, fd, 
   815    948                      SQLITE_OPEN_READONLY | SQLITE_OPEN_MAIN_DB, &outFlags);
................................................................................
   819    952       fd->pMethods->xClose(fd);
   820    953     }else if( rc==SQLITE_CANTOPEN ){
   821    954       quotaGroup *pGroup;
   822    955       quotaFile *pFile;
   823    956       quotaEnter();
   824    957       pGroup = quotaGroupFind(zFull);
   825    958       if( pGroup ){
   826         -      pFile = quotaFindFile(pGroup, zFull);
          959  +      pFile = quotaFindFile(pGroup, zFull, 0);
   827    960         if( pFile ) quotaRemoveFile(pFile);
   828    961       }
   829    962       quotaLeave();
   830    963     }
   831    964     sqlite3_free(fd);
   832    965     return rc;
   833    966   }
   834    967   
          968  +/*
          969  +** Open a potentially quotaed file for I/O.
          970  +*/
          971  +quota_FILE *sqlite3_quota_fopen(const char *zFilename, const char *zMode){
          972  +  quota_FILE *p = 0;
          973  +  char *zFull = 0;
          974  +  char *zFullTranslated;
          975  +  int rc;
          976  +  quotaGroup *pGroup;
          977  +  quotaFile *pFile;
          978  +
          979  +  zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1);
          980  +  if( zFull==0 ) return 0;
          981  +  rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
          982  +                                      gQuota.sThisVfs.mxPathname+1, zFull);
          983  +  if( rc ) goto quota_fopen_error;
          984  +  p = (quota_FILE*)sqlite3_malloc(sizeof(*p));
          985  +  if( p==0 ) goto quota_fopen_error;
          986  +  memset(p, 0, sizeof(*p));
          987  +  zFullTranslated = quota_utf8_to_mbcs(zFull);
          988  +  if( zFullTranslated==0 ) goto quota_fopen_error;
          989  +  p->f = fopen(zFullTranslated, zMode);
          990  +  quota_mbcs_free(zFullTranslated);
          991  +  if( p->f==0 ) goto quota_fopen_error;
          992  +  quotaEnter();
          993  +  pGroup = quotaGroupFind(zFull);
          994  +  if( pGroup ){
          995  +    pFile = quotaFindFile(pGroup, zFull, 1);
          996  +    if( pFile==0 ){
          997  +      quotaLeave();
          998  +      goto quota_fopen_error;
          999  +    }
         1000  +    pFile->nRef++;
         1001  +    p->pFile = pFile;
         1002  +  }
         1003  +  quotaLeave();
         1004  +  sqlite3_free(zFull);
         1005  +  return p;
         1006  +
         1007  +quota_fopen_error:
         1008  +  sqlite3_free(zFull);
         1009  +  if( p && p->f ) fclose(p->f);
         1010  +  sqlite3_free(p);
         1011  +  return 0;
         1012  +}
         1013  +
         1014  +/*
         1015  +** Read content from a quota_FILE
         1016  +*/
         1017  +size_t sqlite3_quota_fread(
         1018  +  void *pBuf,            /* Store the content here */
         1019  +  size_t size,           /* Size of each element */
         1020  +  size_t nmemb,          /* Number of elements to read */
         1021  +  quota_FILE *p          /* Read from this quota_FILE object */
         1022  +){
         1023  +  return fread(pBuf, size, nmemb, p->f);
         1024  +}
         1025  +
         1026  +/*
         1027  +** Write content into a quota_FILE.  Invoke the quota callback and block
         1028  +** the write if we exceed quota.
         1029  +*/
         1030  +size_t sqlite3_quota_fwrite(
         1031  +  void *pBuf,            /* Take content to write from here */
         1032  +  size_t size,           /* Size of each element */
         1033  +  size_t nmemb,          /* Number of elements */
         1034  +  quota_FILE *p          /* Write to this quota_FILE objecct */
         1035  +){
         1036  +  sqlite3_int64 iOfst;
         1037  +  sqlite3_int64 iEnd;
         1038  +  sqlite3_int64 szNew;
         1039  +  quotaFile *pFile;
         1040  +  
         1041  +  iOfst = ftell(p->f);
         1042  +  iEnd = iOfst + size*nmemb;
         1043  +  pFile = p->pFile;
         1044  +  if( pFile && pFile->iSize<iEnd ){
         1045  +    quotaGroup *pGroup = pFile->pGroup;
         1046  +    quotaEnter();
         1047  +    szNew = pGroup->iSize - pFile->iSize + iEnd;
         1048  +    if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
         1049  +      if( pGroup->xCallback ){
         1050  +        pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew, 
         1051  +                          pGroup->pArg);
         1052  +      }
         1053  +      if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
         1054  +        iEnd = pGroup->iLimit - pGroup->iSize + pFile->iSize;
         1055  +        nmemb = (iEnd - iOfst)/size;
         1056  +        iEnd = iOfst + size*nmemb;
         1057  +        szNew = pGroup->iSize - pFile->iSize + iEnd;
         1058  +      }
         1059  +    }
         1060  +    pGroup->iSize = szNew;
         1061  +    pFile->iSize = iEnd;
         1062  +    quotaLeave();
         1063  +  }
         1064  +  return fwrite(pBuf, size, nmemb, p->f);
         1065  +}
         1066  +
         1067  +/*
         1068  +** Close an open quota_FILE stream.
         1069  +*/
         1070  +int sqlite3_quota_fclose(quota_FILE *p){
         1071  +  int rc;
         1072  +  quotaFile *pFile;
         1073  +  rc = fclose(p->f);
         1074  +  pFile = p->pFile;
         1075  +  if( pFile ){
         1076  +    quotaEnter();
         1077  +    pFile->nRef--;
         1078  +    if( pFile->nRef==0 ){
         1079  +      quotaGroup *pGroup = pFile->pGroup;
         1080  +      if( pFile->deleteOnClose ){
         1081  +        gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0);
         1082  +        quotaRemoveFile(pFile);
         1083  +      }
         1084  +      quotaGroupDeref(pGroup);
         1085  +    }
         1086  +    quotaLeave();
         1087  +  }
         1088  +  sqlite3_free(p);
         1089  +  return rc;
         1090  +}
         1091  +
         1092  +/*
         1093  +** Flush memory buffers for a quota_FILE to disk.
         1094  +*/
         1095  +int sqlite3_quota_fflush(quota_FILE *p, int doFsync){
         1096  +  int rc;
         1097  +  rc = fflush(p->f);
         1098  +  if( rc==0 && doFsync ){
         1099  +#if SQLITE_OS_UNIX
         1100  +    rc = fsync(fileno(p->f));
         1101  +#endif
         1102  +#if SQLITE_OS_WIN
         1103  +    rc = _commit(_fileno(p->f));
         1104  +#endif
         1105  +  }
         1106  +  return rc!=0;
         1107  +}
         1108  +
         1109  +/*
         1110  +** Seek on a quota_FILE stream.
         1111  +*/
         1112  +int sqlite3_quota_fseek(quota_FILE *p, long offset, int whence){
         1113  +  return fseek(p->f, offset, whence);
         1114  +}
         1115  +
         1116  +/*
         1117  +** rewind a quota_FILE stream.
         1118  +*/
         1119  +void sqlite3_quota_rewind(quota_FILE *p){
         1120  +  rewind(p->f);
         1121  +}
         1122  +
         1123  +/*
         1124  +** Tell the current location of a quota_FILE stream.
         1125  +*/
         1126  +long sqlite3_quota_ftell(quota_FILE *p){
         1127  +  return ftell(p->f);
         1128  +}
         1129  +
         1130  +/*
         1131  +** Remove a managed file.  Update quotas accordingly.
         1132  +*/
         1133  +int sqlite3_quota_remove(const char *zFilename){
         1134  +  char *zFull;            /* Full pathname for zFilename */
         1135  +  int nFull;              /* Number of bytes in zFilename */
         1136  +  int rc;                 /* Result code */
         1137  +  quotaGroup *pGroup;     /* Group containing zFilename */
         1138  +  quotaFile *pFile;       /* A file in the group */
         1139  +  quotaFile *pNextFile;   /* next file in the group */
         1140  +  int diff;               /* Difference between filenames */
         1141  +  char c;                 /* First character past end of pattern */
         1142  +
         1143  +  zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1);
         1144  +  if( zFull==0 ) return SQLITE_NOMEM;
         1145  +  rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
         1146  +                                      gQuota.sThisVfs.mxPathname+1, zFull);
         1147  +  if( rc ){
         1148  +    sqlite3_free(zFull);
         1149  +    return rc;
         1150  +  }
         1151  +
         1152  +  /* Figure out the length of the full pathname.  If the name ends with
         1153  +  ** / (or \ on windows) then remove the trailing /.
         1154  +  */
         1155  +  nFull = strlen(zFull);
         1156  +  if( nFull>0 && (zFull[nFull-1]=='/' || zFull[nFull-1]=='\\') ){
         1157  +    nFull--;
         1158  +    zFull[nFull] = 0;
         1159  +  }
         1160  +
         1161  +  quotaEnter();
         1162  +  pGroup = quotaGroupFind(zFull);
         1163  +  if( pGroup ){
         1164  +    for(pFile=pGroup->pFiles; pFile && rc==SQLITE_OK; pFile=pNextFile){
         1165  +      pNextFile = pFile->pNext;
         1166  +      diff = memcmp(zFull, pFile->zFilename, nFull);
         1167  +      if( diff==0 && ((c = pFile->zFilename[nFull])==0 || c=='/' || c=='\\') ){
         1168  +        if( pFile->nRef ){
         1169  +          pFile->deleteOnClose = 1;
         1170  +        }else{
         1171  +          rc = gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0);
         1172  +          quotaRemoveFile(pFile);
         1173  +          quotaGroupDeref(pGroup);
         1174  +        }
         1175  +      }
         1176  +    }
         1177  +  }
         1178  +  quotaLeave();
         1179  +  sqlite3_free(zFull);
         1180  +  return rc;
         1181  +}
   835   1182     
   836   1183   /***************************** Test Code ***********************************/
   837   1184   #ifdef SQLITE_TEST
   838   1185   #include <tcl.h>
   839   1186   
   840   1187   /*
   841   1188   ** Argument passed to a TCL quota-over-limit callback.
................................................................................
  1056   1403       Tcl_ListObjAppendElement(interp, pGroupTerm,
  1057   1404             Tcl_NewStringObj(pGroup->zPattern, -1));
  1058   1405       Tcl_ListObjAppendElement(interp, pGroupTerm,
  1059   1406             Tcl_NewWideIntObj(pGroup->iLimit));
  1060   1407       Tcl_ListObjAppendElement(interp, pGroupTerm,
  1061   1408             Tcl_NewWideIntObj(pGroup->iSize));
  1062   1409       for(pFile=pGroup->pFiles; pFile; pFile=pFile->pNext){
         1410  +      int i;
         1411  +      char zTemp[1000];
  1063   1412         pFileTerm = Tcl_NewObj();
         1413  +      sqlite3_snprintf(sizeof(zTemp), zTemp, "%s", pFile->zFilename);
         1414  +      for(i=0; zTemp[i]; i++){ if( zTemp[i]=='\\' ) zTemp[i] = '/'; }
  1064   1415         Tcl_ListObjAppendElement(interp, pFileTerm,
  1065         -            Tcl_NewStringObj(pFile->zFilename, -1));
         1416  +            Tcl_NewStringObj(zTemp, -1));
  1066   1417         Tcl_ListObjAppendElement(interp, pFileTerm,
  1067   1418               Tcl_NewWideIntObj(pFile->iSize));
  1068   1419         Tcl_ListObjAppendElement(interp, pFileTerm,
  1069   1420               Tcl_NewWideIntObj(pFile->nRef));
  1070   1421         Tcl_ListObjAppendElement(interp, pFileTerm,
  1071   1422               Tcl_NewWideIntObj(pFile->deleteOnClose));
  1072   1423         Tcl_ListObjAppendElement(interp, pGroupTerm, pFileTerm);
................................................................................
  1073   1424       }
  1074   1425       Tcl_ListObjAppendElement(interp, pResult, pGroupTerm);
  1075   1426     }
  1076   1427     quotaLeave();
  1077   1428     Tcl_SetObjResult(interp, pResult);
  1078   1429     return TCL_OK;
  1079   1430   }
         1431  +
         1432  +/*
         1433  +** tclcmd: sqlite3_quota_fopen FILENAME MODE
         1434  +*/
         1435  +static int test_quota_fopen(
         1436  +  void * clientData,
         1437  +  Tcl_Interp *interp,
         1438  +  int objc,
         1439  +  Tcl_Obj *CONST objv[]
         1440  +){
         1441  +  const char *zFilename;          /* File pattern to configure */
         1442  +  const char *zMode;              /* Mode string */
         1443  +  quota_FILE *p;                  /* Open string object */
         1444  +  char zReturn[50];               /* Name of pointer to return */
         1445  +
         1446  +  /* Process arguments */
         1447  +  if( objc!=3 ){
         1448  +    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME MODE");
         1449  +    return TCL_ERROR;
         1450  +  }
         1451  +  zFilename = Tcl_GetString(objv[1]);
         1452  +  zMode = Tcl_GetString(objv[2]);
         1453  +  p = sqlite3_quota_fopen(zFilename, zMode);
         1454  +  sqlite3_snprintf(sizeof(zReturn), zReturn, "%p", p);
         1455  +  Tcl_SetResult(interp, zReturn, TCL_VOLATILE);
         1456  +  return TCL_OK;
         1457  +}
         1458  +
         1459  +/* Defined in test1.c */
         1460  +extern void *sqlite3TestTextToPtr(const char*);
         1461  +
         1462  +/*
         1463  +** tclcmd: sqlite3_quota_fread HANDLE SIZE NELEM
         1464  +*/
         1465  +static int test_quota_fread(
         1466  +  void * clientData,
         1467  +  Tcl_Interp *interp,
         1468  +  int objc,
         1469  +  Tcl_Obj *CONST objv[]
         1470  +){
         1471  +  quota_FILE *p;
         1472  +  char *zBuf;
         1473  +  int sz;
         1474  +  int nElem;
         1475  +  int got;
         1476  +
         1477  +  if( objc!=4 ){
         1478  +    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM");
         1479  +    return TCL_ERROR;
         1480  +  }
         1481  +  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
         1482  +  if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR;
         1483  +  if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR;
         1484  +  zBuf = (char*)sqlite3_malloc( sz*nElem + 1 );
         1485  +  if( zBuf==0 ){
         1486  +    Tcl_SetResult(interp, "out of memory", TCL_STATIC);
         1487  +    return TCL_ERROR;
         1488  +  }
         1489  +  got = sqlite3_quota_fread(zBuf, sz, nElem, p);
         1490  +  if( got<0 ) got = 0;
         1491  +  zBuf[got*sz] = 0;
         1492  +  Tcl_SetResult(interp, zBuf, TCL_VOLATILE);
         1493  +  sqlite3_free(zBuf);
         1494  +  return TCL_OK;
         1495  +}
         1496  +
         1497  +/*
         1498  +** tclcmd: sqlite3_quota_fwrite HANDLE SIZE NELEM CONTENT
         1499  +*/
         1500  +static int test_quota_fwrite(
         1501  +  void * clientData,
         1502  +  Tcl_Interp *interp,
         1503  +  int objc,
         1504  +  Tcl_Obj *CONST objv[]
         1505  +){
         1506  +  quota_FILE *p;
         1507  +  char *zBuf;
         1508  +  int sz;
         1509  +  int nElem;
         1510  +  int got;
         1511  +
         1512  +  if( objc!=5 ){
         1513  +    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM CONTENT");
         1514  +    return TCL_ERROR;
         1515  +  }
         1516  +  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
         1517  +  if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR;
         1518  +  if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR;
         1519  +  zBuf = Tcl_GetString(objv[4]);
         1520  +  got = sqlite3_quota_fwrite(zBuf, sz, nElem, p);
         1521  +  Tcl_SetObjResult(interp, Tcl_NewIntObj(got));
         1522  +  return TCL_OK;
         1523  +}
         1524  +
         1525  +/*
         1526  +** tclcmd: sqlite3_quota_fclose HANDLE
         1527  +*/
         1528  +static int test_quota_fclose(
         1529  +  void * clientData,
         1530  +  Tcl_Interp *interp,
         1531  +  int objc,
         1532  +  Tcl_Obj *CONST objv[]
         1533  +){
         1534  +  quota_FILE *p;
         1535  +  int rc;
         1536  +
         1537  +  if( objc!=2 ){
         1538  +    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
         1539  +    return TCL_ERROR;
         1540  +  }
         1541  +  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
         1542  +  rc = sqlite3_quota_fclose(p);
         1543  +  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
         1544  +  return TCL_OK;
         1545  +}
         1546  +
         1547  +/*
         1548  +** tclcmd: sqlite3_quota_fflush HANDLE ?HARDSYNC?
         1549  +*/
         1550  +static int test_quota_fflush(
         1551  +  void * clientData,
         1552  +  Tcl_Interp *interp,
         1553  +  int objc,
         1554  +  Tcl_Obj *CONST objv[]
         1555  +){
         1556  +  quota_FILE *p;
         1557  +  int rc;
         1558  +  int doSync = 0;
         1559  +
         1560  +  if( objc!=2 && objc!=3 ){
         1561  +    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE ?HARDSYNC?");
         1562  +    return TCL_ERROR;
         1563  +  }
         1564  +  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
         1565  +  if( objc==3 ){
         1566  +    if( Tcl_GetBooleanFromObj(interp, objv[2], &doSync) ) return TCL_ERROR;
         1567  +  }
         1568  +  rc = sqlite3_quota_fflush(p, doSync);
         1569  +  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
         1570  +  return TCL_OK;
         1571  +}
         1572  +
         1573  +/*
         1574  +** tclcmd: sqlite3_quota_fseek HANDLE OFFSET WHENCE
         1575  +*/
         1576  +static int test_quota_fseek(
         1577  +  void * clientData,
         1578  +  Tcl_Interp *interp,
         1579  +  int objc,
         1580  +  Tcl_Obj *CONST objv[]
         1581  +){
         1582  +  quota_FILE *p;
         1583  +  int ofst;
         1584  +  const char *zWhence;
         1585  +  int whence;
         1586  +  int rc;
         1587  +
         1588  +  if( objc!=4 ){
         1589  +    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE OFFSET WHENCE");
         1590  +    return TCL_ERROR;
         1591  +  }
         1592  +  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
         1593  +  if( Tcl_GetIntFromObj(interp, objv[2], &ofst) ) return TCL_ERROR;
         1594  +  zWhence = Tcl_GetString(objv[3]);
         1595  +  if( strcmp(zWhence, "SEEK_SET")==0 ){
         1596  +    whence = SEEK_SET;
         1597  +  }else if( strcmp(zWhence, "SEEK_CUR")==0 ){
         1598  +    whence = SEEK_CUR;
         1599  +  }else if( strcmp(zWhence, "SEEK_END")==0 ){
         1600  +    whence = SEEK_END;
         1601  +  }else{
         1602  +    Tcl_AppendResult(interp,
         1603  +           "WHENCE should be SEEK_SET, SEEK_CUR, or SEEK_END", (char*)0);
         1604  +    return TCL_ERROR;
         1605  +  }
         1606  +  rc = sqlite3_quota_fseek(p, ofst, whence);
         1607  +  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
         1608  +  return TCL_OK;
         1609  +}
         1610  +
         1611  +/*
         1612  +** tclcmd: sqlite3_quota_rewind HANDLE
         1613  +*/
         1614  +static int test_quota_rewind(
         1615  +  void * clientData,
         1616  +  Tcl_Interp *interp,
         1617  +  int objc,
         1618  +  Tcl_Obj *CONST objv[]
         1619  +){
         1620  +  quota_FILE *p;
         1621  +  if( objc!=2 ){
         1622  +    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
         1623  +    return TCL_ERROR;
         1624  +  }
         1625  +  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
         1626  +  sqlite3_quota_rewind(p);
         1627  +  return TCL_OK;
         1628  +}
         1629  +
         1630  +/*
         1631  +** tclcmd: sqlite3_quota_ftell HANDLE
         1632  +*/
         1633  +static int test_quota_ftell(
         1634  +  void * clientData,
         1635  +  Tcl_Interp *interp,
         1636  +  int objc,
         1637  +  Tcl_Obj *CONST objv[]
         1638  +){
         1639  +  quota_FILE *p;
         1640  +  sqlite3_int64 x;
         1641  +  if( objc!=2 ){
         1642  +    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
         1643  +    return TCL_ERROR;
         1644  +  }
         1645  +  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
         1646  +  x = sqlite3_quota_ftell(p);
         1647  +  Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x));
         1648  +  return TCL_OK;
         1649  +}
         1650  +
         1651  +/*
         1652  +** tclcmd: sqlite3_quota_remove FILENAME
         1653  +*/
         1654  +static int test_quota_remove(
         1655  +  void * clientData,
         1656  +  Tcl_Interp *interp,
         1657  +  int objc,
         1658  +  Tcl_Obj *CONST objv[]
         1659  +){
         1660  +  const char *zFilename;          /* File pattern to configure */
         1661  +  int rc;
         1662  +  if( objc!=2 ){
         1663  +    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
         1664  +    return TCL_ERROR;
         1665  +  }
         1666  +  zFilename = Tcl_GetString(objv[1]);
         1667  +  rc = sqlite3_quota_remove(zFilename);
         1668  +  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
         1669  +  return TCL_OK;
         1670  +}
         1671  +
         1672  +/*
         1673  +** tclcmd: sqlite3_quota_glob PATTERN TEXT
         1674  +**
         1675  +** Test the glob pattern matching.  Return 1 if TEXT matches PATTERN
         1676  +** and return 0 if it does not.
         1677  +*/
         1678  +static int test_quota_glob(
         1679  +  void * clientData,
         1680  +  Tcl_Interp *interp,
         1681  +  int objc,
         1682  +  Tcl_Obj *CONST objv[]
         1683  +){
         1684  +  const char *zPattern;          /* The glob pattern */
         1685  +  const char *zText;             /* Text to compare agains the pattern */
         1686  +  int rc;
         1687  +  if( objc!=3 ){
         1688  +    Tcl_WrongNumArgs(interp, 1, objv, "PATTERN TEXT");
         1689  +    return TCL_ERROR;
         1690  +  }
         1691  +  zPattern = Tcl_GetString(objv[1]);
         1692  +  zText = Tcl_GetString(objv[2]);
         1693  +  rc = quotaStrglob(zPattern, zText);
         1694  +  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
         1695  +  return TCL_OK;
         1696  +}
  1080   1697   
  1081   1698   /*
  1082   1699   ** This routine registers the custom TCL commands defined in this
  1083   1700   ** module.  This should be the only procedure visible from outside
  1084   1701   ** of this module.
  1085   1702   */
  1086   1703   int Sqlitequota_Init(Tcl_Interp *interp){
  1087   1704     static struct {
  1088   1705        char *zName;
  1089   1706        Tcl_ObjCmdProc *xProc;
  1090   1707     } aCmd[] = {
  1091   1708       { "sqlite3_quota_initialize", test_quota_initialize },
  1092         -    { "sqlite3_quota_shutdown", test_quota_shutdown },
  1093         -    { "sqlite3_quota_set", test_quota_set },
  1094         -    { "sqlite3_quota_file", test_quota_file },
  1095         -    { "sqlite3_quota_dump", test_quota_dump },
         1709  +    { "sqlite3_quota_shutdown",   test_quota_shutdown },
         1710  +    { "sqlite3_quota_set",        test_quota_set },
         1711  +    { "sqlite3_quota_file",       test_quota_file },
         1712  +    { "sqlite3_quota_dump",       test_quota_dump },
         1713  +    { "sqlite3_quota_fopen",      test_quota_fopen },
         1714  +    { "sqlite3_quota_fread",      test_quota_fread },
         1715  +    { "sqlite3_quota_fwrite",     test_quota_fwrite },
         1716  +    { "sqlite3_quota_fclose",     test_quota_fclose },
         1717  +    { "sqlite3_quota_fflush",     test_quota_fflush },
         1718  +    { "sqlite3_quota_fseek",      test_quota_fseek },
         1719  +    { "sqlite3_quota_rewind",     test_quota_rewind },
         1720  +    { "sqlite3_quota_ftell",      test_quota_ftell },
         1721  +    { "sqlite3_quota_remove",     test_quota_remove },
         1722  +    { "sqlite3_quota_glob",       test_quota_glob },
  1096   1723     };
  1097   1724     int i;
  1098   1725   
  1099   1726     for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
  1100   1727       Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
  1101   1728     }
  1102   1729   
  1103   1730     return TCL_OK;
  1104   1731   }
  1105   1732   #endif

Added src/test_quota.h.

            1  +/*
            2  +** 2011 December 1
            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 the interface definition for the quota a VFS shim.
           14  +**
           15  +** This particular shim enforces a quota system on files.  One or more
           16  +** database files are in a "quota group" that is defined by a GLOB
           17  +** pattern.  A quota is set for the combined size of all files in the
           18  +** the group.  A quota of zero means "no limit".  If the total size
           19  +** of all files in the quota group is greater than the limit, then
           20  +** write requests that attempt to enlarge a file fail with SQLITE_FULL.
           21  +**
           22  +** However, before returning SQLITE_FULL, the write requests invoke
           23  +** a callback function that is configurable for each quota group.
           24  +** This callback has the opportunity to enlarge the quota.  If the
           25  +** callback does enlarge the quota such that the total size of all
           26  +** files within the group is less than the new quota, then the write
           27  +** continues as if nothing had happened.
           28  +*/
           29  +#ifndef _QUOTA_H_
           30  +#include "sqlite3.h"
           31  +#include <stdio.h>
           32  +
           33  +/* Make this callable from C++ */
           34  +#ifdef __cplusplus
           35  +extern "C" {
           36  +#endif
           37  +
           38  +/*
           39  +** Initialize the quota VFS shim.  Use the VFS named zOrigVfsName
           40  +** as the VFS that does the actual work.  Use the default if
           41  +** zOrigVfsName==NULL.  
           42  +**
           43  +** The quota VFS shim is named "quota".  It will become the default
           44  +** VFS if makeDefault is non-zero.
           45  +**
           46  +** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once
           47  +** during start-up.
           48  +*/
           49  +int sqlite3_quota_initialize(const char *zOrigVfsName, int makeDefault);
           50  +
           51  +/*
           52  +** Shutdown the quota system.
           53  +**
           54  +** All SQLite database connections must be closed before calling this
           55  +** routine.
           56  +**
           57  +** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once while
           58  +** shutting down in order to free all remaining quota groups.
           59  +*/
           60  +int sqlite3_quota_shutdown(void);
           61  +
           62  +/*
           63  +** Create or destroy a quota group.
           64  +**
           65  +** The quota group is defined by the zPattern.  When calling this routine
           66  +** with a zPattern for a quota group that already exists, this routine
           67  +** merely updates the iLimit, xCallback, and pArg values for that quota
           68  +** group.  If zPattern is new, then a new quota group is created.
           69  +**
           70  +** The zPattern is always compared against the full pathname of the file.
           71  +** Even if APIs are called with relative pathnames, SQLite converts the
           72  +** name to a full pathname before comparing it against zPattern.  zPattern
           73  +** is a glob pattern with the following matching rules:
           74  +**
           75  +**      '*'       Matches any sequence of zero or more characters.
           76  +**
           77  +**      '?'       Matches exactly one character.
           78  +**
           79  +**     [...]      Matches one character from the enclosed list of
           80  +**                characters.  "]" can be part of the list if it is
           81  +**                the first character.  Within the list "X-Y" matches
           82  +**                characters X or Y or any character in between the
           83  +**                two.  Ex:  "[0-9]" matches any digit.
           84  +**
           85  +**     [^...]     Matches one character not in the enclosed list.
           86  +**
           87  +**     /          Matches either / or \.  This allows glob patterns
           88  +**                containing / to work on both unix and windows.
           89  +**
           90  +** Note that, unlike unix shell globbing, the directory separator "/"
           91  +** can match a wildcard.  So, for example, the pattern "/abc/xyz/" "*"
           92  +** matches any files anywhere in the directory hierarchy beneath
           93  +** /abc/xyz.
           94  +**
           95  +** The glob algorithm works on bytes.  Multi-byte UTF8 characters are
           96  +** matched as if each byte were a separate character.
           97  +**
           98  +** If the iLimit for a quota group is set to zero, then the quota group
           99  +** is disabled and will be deleted when the last database connection using
          100  +** the quota group is closed.
          101  +**
          102  +** Calling this routine on a zPattern that does not exist and with a
          103  +** zero iLimit is a no-op.
          104  +**
          105  +** A quota group must exist with a non-zero iLimit prior to opening
          106  +** database connections if those connections are to participate in the
          107  +** quota group.  Creating a quota group does not affect database connections
          108  +** that are already open.
          109  +**
          110  +** The patterns that define the various quota groups should be distinct.
          111  +** If the same filename matches more than one quota group pattern, then
          112  +** the behavior of this package is undefined.
          113  +*/
          114  +int sqlite3_quota_set(
          115  +  const char *zPattern,           /* The filename pattern */
          116  +  sqlite3_int64 iLimit,           /* New quota to set for this quota group */
          117  +  void (*xCallback)(              /* Callback invoked when going over quota */
          118  +     const char *zFilename,         /* Name of file whose size increases */
          119  +     sqlite3_int64 *piLimit,        /* IN/OUT: The current limit */
          120  +     sqlite3_int64 iSize,           /* Total size of all files in the group */
          121  +     void *pArg                     /* Client data */
          122  +  ),
          123  +  void *pArg,                     /* client data passed thru to callback */
          124  +  void (*xDestroy)(void*)         /* Optional destructor for pArg */
          125  +);
          126  +
          127  +/*
          128  +** Bring the named file under quota management, assuming its name matches
          129  +** the glob pattern of some quota group.  Or if it is already under
          130  +** management, update its size.  If zFilename does not match the glob
          131  +** pattern of any quota group, this routine is a no-op.
          132  +*/
          133  +int sqlite3_quota_file(const char *zFilename);
          134  +
          135  +/*
          136  +** The following object serves the same role as FILE in the standard C
          137  +** library.  It represents an open connection to a file on disk for I/O.
          138  +**
          139  +** A single quota_FILE should not be used by two or more threads at the
          140  +** same time.  Multiple threads can be using different quota_FILE objects
          141  +** simultaneously, but not the same quota_FILE object.
          142  +*/
          143  +typedef struct quota_FILE quota_FILE;
          144  +
          145  +/*
          146  +** Create a new quota_FILE object used to read and/or write to the
          147  +** file zFilename.  The zMode parameter is as with standard library zMode.
          148  +*/
          149  +quota_FILE *sqlite3_quota_fopen(const char *zFilename, const char *zMode);
          150  +
          151  +/*
          152  +** Perform I/O against a quota_FILE object.  When doing writes, the
          153  +** quota mechanism may result in a short write, in order to prevent
          154  +** the sum of sizes of all files from going over quota.
          155  +*/
          156  +size_t sqlite3_quota_fread(void*, size_t, size_t, quota_FILE*);
          157  +size_t sqlite3_quota_fwrite(void*, size_t, size_t, quota_FILE*);
          158  +
          159  +/*
          160  +** Flush all written content held in memory buffers out to disk.
          161  +** This is the equivalent of fflush() in the standard library.
          162  +**
          163  +** If the hardSync parameter is true (non-zero) then this routine
          164  +** also forces OS buffers to disk - the equivalent of fsync().
          165  +**
          166  +** This routine return zero on success and non-zero if something goes
          167  +** wrong.
          168  +*/
          169  +int sqlite3_quota_fflush(quota_FILE*, int hardSync);
          170  +
          171  +/*
          172  +** Close a quota_FILE object and free all associated resources.  The
          173  +** file remains under quota management.
          174  +*/
          175  +int sqlite3_quota_fclose(quota_FILE*);
          176  +
          177  +/*
          178  +** Move the read/write pointer for a quota_FILE object.  Or tell the
          179  +** current location of the read/write pointer.
          180  +*/
          181  +int sqlite3_quota_fseek(quota_FILE*, long, int);
          182  +void sqlite3_quota_rewind(quota_FILE*);
          183  +long sqlite3_quota_ftell(quota_FILE*);
          184  +
          185  +/*
          186  +** Delete a file from the disk, if that file is under quota management.
          187  +** Adjust quotas accordingly.
          188  +**
          189  +** If zFilename is the name of a directory that matches one of the
          190  +** quota glob patterns, then all files under quota management that
          191  +** are contained within that directory are deleted.
          192  +**
          193  +** A standard SQLite result code is returned (SQLITE_OK, SQLITE_NOMEM, etc.)
          194  +** When deleting a directory of files, if the deletion of any one
          195  +** file fails (for example due to an I/O error), then this routine
          196  +** returns immediately, with the error code, and does not try to 
          197  +** delete any of the other files in the specified directory.
          198  +**
          199  +** All files are removed from quota management and deleted from disk.
          200  +** However, no attempt is made to remove empty directories.
          201  +**
          202  +** This routine is a no-op for files that are not under quota management.
          203  +*/
          204  +int sqlite3_quota_remove(const char *zFilename);
          205  +
          206  +#ifdef __cplusplus
          207  +}  /* end of the 'extern "C"' block */
          208  +#endif
          209  +#endif /* _QUOTA_H_ */

Added test/quota-glob.test.

            1  +# 2011 December 1
            2  +#
            3  +# The author disclaims copyright to this source code.  In place of
            4  +# a legal notice, here is a blessing:
            5  +#
            6  +#    May you do good and not evil.
            7  +#    May you find forgiveness for yourself and forgive others.
            8  +#    May you share freely, never taking more than you give.
            9  +#
           10  +#***********************************************************************
           11  +#
           12  +# Tests for the glob-style string compare operator embedded in the
           13  +# quota shim.
           14  +#
           15  +
           16  +set testdir [file dirname $argv0]
           17  +source $testdir/tester.tcl
           18  +
           19  +catch { unset testnum }
           20  +catch { unset pattern }
           21  +catch { unset text }
           22  +catch { unset ans }
           23  +
           24  +foreach {testnum pattern text ans} {
           25  +   1  abcdefg   abcdefg   1
           26  +   2  abcdefG   abcdefg   0
           27  +   3  abcdef    abcdefg   0
           28  +   4  abcdefgh  abcdefg   0
           29  +   5  abcdef?   abcdefg   1
           30  +   6  abcdef?   abcdef    0
           31  +   7  abcdef?   abcdefgh  0
           32  +   8  abcdefg   abcdef?   0
           33  +   9  abcdef?   abcdef?   1
           34  +  10  abc/def   abc/def   1
           35  +  11  abc//def  abc/def   0
           36  +  12  */abc/*   x/abc/y   1
           37  +  13  */abc/*   /abc/     1
           38  +  16  */abc/*   x///a/ab/abc   0
           39  +  17  */abc/*   x//a/ab/abc/   1
           40  +  16  */abc/*   x///a/ab/abc   0
           41  +  17  */abc/*   x//a/ab/abc/   1
           42  +  18  **/abc/** x//a/ab/abc/   1
           43  +  19  *?/abc/*? x//a/ab/abc/y  1
           44  +  20  ?*/abc/?* x//a/ab/abc/y  1
           45  +  21  {abc[cde]efg}   abcbefg  0
           46  +  22  {abc[cde]efg}   abccefg  1
           47  +  23  {abc[cde]efg}   abcdefg  1
           48  +  24  {abc[cde]efg}   abceefg  1
           49  +  25  {abc[cde]efg}   abcfefg  0
           50  +  26  {abc[^cde]efg}  abcbefg  1
           51  +  27  {abc[^cde]efg}  abccefg  0
           52  +  28  {abc[^cde]efg}  abcdefg  0
           53  +  29  {abc[^cde]efg}  abceefg  0
           54  +  30  {abc[^cde]efg}  abcfefg  1
           55  +  31  {abc[c-e]efg}   abcbefg  0
           56  +  32  {abc[c-e]efg}   abccefg  1
           57  +  33  {abc[c-e]efg}   abcdefg  1
           58  +  34  {abc[c-e]efg}   abceefg  1
           59  +  35  {abc[c-e]efg}   abcfefg  0
           60  +  36  {abc[^c-e]efg}  abcbefg  1
           61  +  37  {abc[^c-e]efg}  abccefg  0
           62  +  38  {abc[^c-e]efg}  abcdefg  0
           63  +  39  {abc[^c-e]efg}  abceefg  0
           64  +  40  {abc[^c-e]efg}  abcfefg  1
           65  +  41  {abc[c-e]efg}   abc-efg  0
           66  +  42  {abc[-ce]efg}   abc-efg  1
           67  +  43  {abc[ce-]efg}   abc-efg  1
           68  +  44  {abc[][*?]efg}  {abc]efg} 1
           69  +  45  {abc[][*?]efg}  {abc*efg} 1
           70  +  46  {abc[][*?]efg}  {abc?efg} 1
           71  +  47  {abc[][*?]efg}  {abc[efg} 1
           72  +  48  {abc[^][*?]efg} {abc]efg} 0
           73  +  49  {abc[^][*?]efg} {abc*efg} 0
           74  +  50  {abc[^][*?]efg} {abc?efg} 0
           75  +  51  {abc[^][*?]efg} {abc[efg} 0
           76  +  52  {abc[^][*?]efg} {abcdefg} 1
           77  +  53  {*[xyz]efg}     {abcxefg} 1
           78  +  54  {*[xyz]efg}     {abcwefg} 0
           79  +} {
           80  +  do_test quota-glob-$testnum.1 {
           81  +    sqlite3_quota_glob $::pattern $::text
           82  +  } $::ans
           83  +  do_test quota-glob-$testnum.2 {
           84  +    sqlite3_quota_glob $::pattern [string map {/ \\} $::text]
           85  +  } $::ans
           86  +}
           87  +finish_test

Changes to test/quota.test.

    44     44   #   quota-2.4.*: Try to shutdown the quota system before closing the db
    45     45   #                file. Check that this fails and the quota system still works
    46     46   #                afterwards. Then close the database and successfully shut
    47     47   #                down the quota system.
    48     48   #   
    49     49   sqlite3_quota_initialize "" 1
    50     50   
           51  +unset -nocomplain quota_request_ok
    51     52   proc quota_check {filename limitvar size} {
    52     53     upvar $limitvar limit
    53     54   
    54     55     lappend ::quota [set limit] $size
    55     56     if {[info exists ::quota_request_ok]} { set limit $size }
    56     57   }
    57     58   

Added test/quota2.test.

            1  +# 2011 December 1
            2  +#
            3  +# The author disclaims copyright to this source code.  In place of
            4  +# a legal notice, here is a blessing:
            5  +#
            6  +#    May you do good and not evil.
            7  +#    May you find forgiveness for yourself and forgive others.
            8  +#    May you share freely, never taking more than you give.
            9  +#
           10  +#***********************************************************************
           11  +#
           12  +
           13  +set testdir [file dirname $argv0]
           14  +source $testdir/tester.tcl
           15  +source $testdir/malloc_common.tcl
           16  +
           17  +db close
           18  +sqlite3_quota_initialize "" 1
           19  +
           20  +foreach dir {quota2a/x1 quota2a/x2 quota2a quota2b quota2c} {
           21  +  file delete -force $dir
           22  +}
           23  +foreach dir {quota2a quota2a/x1 quota2a/x2 quota2b quota2c} {
           24  +  file mkdir $dir
           25  +}
           26  +
           27  +# The standard_path procedure converts a pathname into a standard format
           28  +# that is the same across platforms.
           29  +#
           30  +unset -nocomplain ::quota_pwd ::quota_mapping
           31  +set ::quota_pwd [string map {\\ /} [pwd]]
           32  +set ::quota_mapping [list $::quota_pwd PWD]
           33  +proc standard_path {x} {
           34  +  set x [string map {\\ /} $x]
           35  +  return [string map $::quota_mapping $x]
           36  +}
           37  +
           38  +# The quota_check procedure is a callback from the quota handler.
           39  +# It has three arguments which are (1) the full pathname of the file
           40  +# that has gone over quota, (2) the quota limit, (3) the requested
           41  +# new quota size to cover the last write.  These three values are
           42  +# appended to the global variable $::quota.  The filename is processed
           43  +# to convert every \ character into / and to change the name of the
           44  +# working directory to PWD.  
           45  +#
           46  +# The quota is increased to the request if the ::quota_request_ok 
           47  +# global variable is true.
           48  +#
           49  +set ::quota {}
           50  +set ::quota_request_ok 0
           51  +
           52  +proc quota_check {filename limitvar size} {
           53  +  upvar $limitvar limit
           54  +  lappend ::quota [standard_path $filename] [set limit] $size
           55  +  if {$::quota_request_ok} {set limit $size}
           56  +}
           57  +
           58  +sqlite3_quota_set */quota2a/* 4000 quota_check
           59  +sqlite3_quota_set */quota2b/* 5000 quota_check
           60  +
           61  +unset -nocomplain bigtext
           62  +for {set i 1} {$i<=1000} {incr i} {
           63  +  if {$i%10==0} {
           64  +    append bigtext [format "%06d\n" $i]
           65  +  } else {
           66  +    append bigtext [format "%06d " $i]
           67  +  }
           68  +}
           69  +
           70  +catch { unset h1 }
           71  +catch { unset x }
           72  +do_test quota2-1.1 {
           73  +  set ::h1 [sqlite3_quota_fopen quota2a/xyz.txt w+b]
           74  +  sqlite3_quota_fwrite $::h1 1 7000 $bigtext
           75  +} {4000}
           76  +do_test quota2-1.2 {
           77  +  set ::quota
           78  +} {PWD/quota2a/xyz.txt 4000 7000}
           79  +do_test quota2-1.3 {
           80  +  sqlite3_quota_rewind $::h1
           81  +  set ::x [sqlite3_quota_fread $::h1 1001 7]
           82  +  string length $::x
           83  +} {3003}
           84  +do_test quota2-1.4 {
           85  +  string match $::x [string range $::bigtext 0 3002]
           86  +} {1}
           87  +do_test quota2-1.5 {
           88  +  sqlite3_quota_fseek $::h1 0 SEEK_END
           89  +  sqlite3_quota_ftell $::h1
           90  +} {4000}
           91  +do_test quota2-1.6 {
           92  +  sqlite3_quota_fseek $::h1 -100 SEEK_END
           93  +  sqlite3_quota_ftell $::h1
           94  +} {3900}
           95  +do_test quota2-1.7 {
           96  +  sqlite3_quota_fseek $::h1 -100 SEEK_CUR
           97  +  sqlite3_quota_ftell $::h1
           98  +} {3800}
           99  +do_test quota2-1.8 {
          100  +  sqlite3_quota_fseek $::h1 50 SEEK_CUR
          101  +  sqlite3_quota_ftell $::h1
          102  +} {3850}
          103  +do_test quota2-1.9 {
          104  +  sqlite3_quota_fseek $::h1 50 SEEK_SET
          105  +  sqlite3_quota_ftell $::h1
          106  +} {50}
          107  +do_test quota2-1.10 {
          108  +  sqlite3_quota_rewind $::h1
          109  +  sqlite3_quota_ftell $::h1
          110  +} {0}
          111  +do_test quota2-1.11 {
          112  +  standard_path [sqlite3_quota_dump]
          113  +} {{*/quota2b/* 5000 0} {*/quota2a/* 4000 4000 {PWD/quota2a/xyz.txt 4000 1 0}}}
          114  +do_test quota2-1.12 {
          115  +  sqlite3_quota_fclose $::h1
          116  +  standard_path [sqlite3_quota_dump]
          117  +} {{*/quota2b/* 5000 0} {*/quota2a/* 4000 4000 {PWD/quota2a/xyz.txt 4000 0 0}}}
          118  +do_test quota2-1.13 {
          119  +  sqlite3_quota_remove quota2a/xyz.txt
          120  +  standard_path [sqlite3_quota_dump]
          121  +} {{*/quota2b/* 5000 0} {*/quota2a/* 4000 0}}
          122  +
          123  +
          124  +set quota {}
          125  +do_test quota2-2.1 {
          126  +  set ::h1 [sqlite3_quota_fopen quota2c/xyz.txt w+b]
          127  +  sqlite3_quota_fwrite $::h1 1 7000 $bigtext
          128  +} {7000}
          129  +do_test quota2-2.2 {
          130  +  set ::quota
          131  +} {}
          132  +do_test quota2-2.3 {
          133  +  sqlite3_quota_rewind $::h1
          134  +  set ::x [sqlite3_quota_fread $::h1 1001 7]
          135  +  string length $::x
          136  +} {6006}
          137  +do_test quota2-2.4 {
          138  +  string match $::x [string range $::bigtext 0 6005]
          139  +} {1}
          140  +do_test quota2-2.5 {
          141  +  sqlite3_quota_fseek $::h1 0 SEEK_END
          142  +  sqlite3_quota_ftell $::h1
          143  +} {7000}
          144  +do_test quota2-2.6 {
          145  +  sqlite3_quota_fseek $::h1 -100 SEEK_END
          146  +  sqlite3_quota_ftell $::h1
          147  +} {6900}
          148  +do_test quota2-2.7 {
          149  +  sqlite3_quota_fseek $::h1 -100 SEEK_CUR
          150  +  sqlite3_quota_ftell $::h1
          151  +} {6800}
          152  +do_test quota2-2.8 {
          153  +  sqlite3_quota_fseek $::h1 50 SEEK_CUR
          154  +  sqlite3_quota_ftell $::h1
          155  +} {6850}
          156  +do_test quota2-2.9 {
          157  +  sqlite3_quota_fseek $::h1 50 SEEK_SET
          158  +  sqlite3_quota_ftell $::h1
          159  +} {50}
          160  +do_test quota2-2.10 {
          161  +  sqlite3_quota_rewind $::h1
          162  +  sqlite3_quota_ftell $::h1
          163  +} {0}
          164  +do_test quota2-2.11 {
          165  +  standard_path [sqlite3_quota_dump]
          166  +} {{*/quota2b/* 5000 0} {*/quota2a/* 4000 0}}
          167  +do_test quota2-2.12 {
          168  +  sqlite3_quota_fclose $::h1
          169  +  standard_path [sqlite3_quota_dump]
          170  +} {{*/quota2b/* 5000 0} {*/quota2a/* 4000 0}}
          171  +
          172  +do_test quota2-3.1 {
          173  +  sqlite3_quota_set */quota2b/* 0 quota_check
          174  +  set ::h1 [sqlite3_quota_fopen quota2a/x1/a.txt a]
          175  +  sqlite3_quota_fwrite $::h1 10 10 $bigtext
          176  +} {10}
          177  +do_test quota2-3.2 {
          178  +  standard_path [sqlite3_quota_dump]
          179  +} {{*/quota2a/* 4000 100 {PWD/quota2a/x1/a.txt 100 1 0}}}
          180  +do_test quota2-3.3a {
          181  +  sqlite3_quota_fflush $::h1 0
          182  +  standard_path [sqlite3_quota_dump]
          183  +} {{*/quota2a/* 4000 100 {PWD/quota2a/x1/a.txt 100 1 0}}}
          184  +do_test quota2-3.3b {
          185  +  sqlite3_quota_fflush $::h1 1
          186  +  standard_path [sqlite3_quota_dump]
          187  +} {{*/quota2a/* 4000 100 {PWD/quota2a/x1/a.txt 100 1 0}}}
          188  +do_test quota2-3.3c {
          189  +  sqlite3_quota_fflush $::h1
          190  +  standard_path [sqlite3_quota_dump]
          191  +} {{*/quota2a/* 4000 100 {PWD/quota2a/x1/a.txt 100 1 0}}}
          192  +do_test quota2-3.4 {
          193  +  sqlite3_quota_fclose $::h1
          194  +  standard_path [sqlite3_quota_dump]
          195  +} {{*/quota2a/* 4000 100 {PWD/quota2a/x1/a.txt 100 0 0}}}
          196  +do_test quota2-3.5 {
          197  +  set ::h2 [sqlite3_quota_fopen quota2a/x2/b.txt a]
          198  +  sqlite3_quota_fwrite $::h2 10 20 $bigtext
          199  +  standard_path [sqlite3_quota_dump]
          200  +} {{*/quota2a/* 4000 300 {PWD/quota2a/x2/b.txt 200 1 0} {PWD/quota2a/x1/a.txt 100 0 0}}}
          201  +do_test quota2-3.6 {
          202  +  set ::h3 [sqlite3_quota_fopen quota2a/x1/c.txt a]
          203  +  sqlite3_quota_fwrite $::h3 10 50 $bigtext
          204  +  standard_path [sqlite3_quota_dump]
          205  +} {{*/quota2a/* 4000 800 {PWD/quota2a/x1/c.txt 500 1 0} {PWD/quota2a/x2/b.txt 200 1 0} {PWD/quota2a/x1/a.txt 100 0 0}}}
          206  +do_test quota2-3.7 {
          207  +  file exists quota2a/x1/a.txt
          208  +} {1}
          209  +do_test quota2-3.8 {
          210  +  file exists quota2a/x2/b.txt
          211  +} {1}
          212  +do_test quota2-3.9 {
          213  +  file exists quota2a/x1/c.txt
          214  +} {1}
          215  +do_test quota2-3.10 {
          216  +  sqlite3_quota_remove quota2a/x1
          217  +  standard_path [sqlite3_quota_dump]
          218  +} {{*/quota2a/* 4000 700 {PWD/quota2a/x1/c.txt 500 1 1} {PWD/quota2a/x2/b.txt 200 1 0}}}
          219  +do_test quota2-3.11 {
          220  +  sqlite3_quota_fclose $::h2
          221  +  sqlite3_quota_fclose $::h3
          222  +  standard_path [sqlite3_quota_dump]
          223  +} {{*/quota2a/* 4000 200 {PWD/quota2a/x2/b.txt 200 0 0}}}
          224  +do_test quota2-3.12 {
          225  +  file exists quota2a/x1/a.txt
          226  +} {0}
          227  +do_test quota2-3.13 {
          228  +  file exists quota2a/x2/b.txt
          229  +} {1}
          230  +do_test quota2-3.14 {
          231  +  file exists quota2a/x1/c.txt
          232  +} {0}
          233  +
          234  +catch { sqlite3_quota_shutdown }
          235  +catch { unset quota_request_ok }
          236  +finish_test