/ Check-in [322bd15f]
Login

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

Overview
Comment:Add stdio support to the quota VFS.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 322bd15f97143d39b3a88d5f6cf7afb454e0666e
User & Date: drh 2011-12-15 17:44:33
Context
2011-12-16
13:42
Merge the nx-devkit changes into trunk. check-in: cf3bccc2 user: drh tags: trunk
13:24
Experimental fix for [a1fa75cbdd]. Closed-Leaf check-in: 6492af76 user: dan tags: experimental
2011-12-15
17:44
Add stdio support to the quota VFS. check-in: 322bd15f 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: e85cfe9a user: drh tags: quota-stdio
2011-12-14
18:33
Merge the nx-devkit changes into trunk. This includes the new SQLITE_FCNTL_VFSNAME file-control. check-in: da118e02 user: drh tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

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
................................................................................
   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    718     int rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
          719  +#if defined(SQLITE_FCNTL_VFSNAME)
   593    720     if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){
   594    721       *(char**)pArg = sqlite3_mprintf("quota/%z", *(char**)pArg);
   595    722     }
          723  +#endif
   596    724     return rc;
   597    725   }
   598    726   
   599    727   /* Pass xSectorSize requests through to the original VFS unchanged.
   600    728   */
   601    729   static int quotaSectorSize(sqlite3_file *pConn){
   602    730     sqlite3_file *pSubOpen = quotaSubOpen(pConn);
................................................................................
   805    933   */
   806    934   int sqlite3_quota_file(const char *zFilename){
   807    935     char *zFull;
   808    936     sqlite3_file *fd;
   809    937     int rc;
   810    938     int outFlags = 0;
   811    939     sqlite3_int64 iSize;
   812         -  fd = sqlite3_malloc(gQuota.sThisVfs.szOsFile + gQuota.sThisVfs.mxPathname+1);
          940  +  fd = (sqlite3_file*)sqlite3_malloc(gQuota.sThisVfs.szOsFile +
          941  +                                     gQuota.sThisVfs.mxPathname+1);
   813    942     if( fd==0 ) return SQLITE_NOMEM;
   814    943     zFull = gQuota.sThisVfs.szOsFile + (char*)fd;
   815    944     rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
   816    945                                         gQuota.sThisVfs.mxPathname+1, zFull);
   817    946     if( rc==SQLITE_OK ){
   818    947       rc = quotaOpen(&gQuota.sThisVfs, zFull, fd, 
   819    948                      SQLITE_OPEN_READONLY | SQLITE_OPEN_MAIN_DB, &outFlags);
................................................................................
   823    952       fd->pMethods->xClose(fd);
   824    953     }else if( rc==SQLITE_CANTOPEN ){
   825    954       quotaGroup *pGroup;
   826    955       quotaFile *pFile;
   827    956       quotaEnter();
   828    957       pGroup = quotaGroupFind(zFull);
   829    958       if( pGroup ){
   830         -      pFile = quotaFindFile(pGroup, zFull);
          959  +      pFile = quotaFindFile(pGroup, zFull, 0);
   831    960         if( pFile ) quotaRemoveFile(pFile);
   832    961       }
   833    962       quotaLeave();
   834    963     }
   835    964     sqlite3_free(fd);
   836    965     return rc;
   837    966   }
   838    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  +}
   839   1182     
   840   1183   /***************************** Test Code ***********************************/
   841   1184   #ifdef SQLITE_TEST
   842   1185   #include <tcl.h>
   843   1186   
   844   1187   /*
   845   1188   ** Argument passed to a TCL quota-over-limit callback.
................................................................................
  1060   1403       Tcl_ListObjAppendElement(interp, pGroupTerm,
  1061   1404             Tcl_NewStringObj(pGroup->zPattern, -1));
  1062   1405       Tcl_ListObjAppendElement(interp, pGroupTerm,
  1063   1406             Tcl_NewWideIntObj(pGroup->iLimit));
  1064   1407       Tcl_ListObjAppendElement(interp, pGroupTerm,
  1065   1408             Tcl_NewWideIntObj(pGroup->iSize));
  1066   1409       for(pFile=pGroup->pFiles; pFile; pFile=pFile->pNext){
         1410  +      int i;
         1411  +      char zTemp[1000];
  1067   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] = '/'; }
  1068   1415         Tcl_ListObjAppendElement(interp, pFileTerm,
  1069         -            Tcl_NewStringObj(pFile->zFilename, -1));
         1416  +            Tcl_NewStringObj(zTemp, -1));
  1070   1417         Tcl_ListObjAppendElement(interp, pFileTerm,
  1071   1418               Tcl_NewWideIntObj(pFile->iSize));
  1072   1419         Tcl_ListObjAppendElement(interp, pFileTerm,
  1073   1420               Tcl_NewWideIntObj(pFile->nRef));
  1074   1421         Tcl_ListObjAppendElement(interp, pFileTerm,
  1075   1422               Tcl_NewWideIntObj(pFile->deleteOnClose));
  1076   1423         Tcl_ListObjAppendElement(interp, pGroupTerm, pFileTerm);
................................................................................
  1077   1424       }
  1078   1425       Tcl_ListObjAppendElement(interp, pResult, pGroupTerm);
  1079   1426     }
  1080   1427     quotaLeave();
  1081   1428     Tcl_SetObjResult(interp, pResult);
  1082   1429     return TCL_OK;
  1083   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  +}
  1084   1697   
  1085   1698   /*
  1086   1699   ** This routine registers the custom TCL commands defined in this
  1087   1700   ** module.  This should be the only procedure visible from outside
  1088   1701   ** of this module.
  1089   1702   */
  1090   1703   int Sqlitequota_Init(Tcl_Interp *interp){
  1091   1704     static struct {
  1092   1705        char *zName;
  1093   1706        Tcl_ObjCmdProc *xProc;
  1094   1707     } aCmd[] = {
  1095   1708       { "sqlite3_quota_initialize", test_quota_initialize },
  1096         -    { "sqlite3_quota_shutdown", test_quota_shutdown },
  1097         -    { "sqlite3_quota_set", test_quota_set },
  1098         -    { "sqlite3_quota_file", test_quota_file },
  1099         -    { "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 },
  1100   1723     };
  1101   1724     int i;
  1102   1725   
  1103   1726     for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
  1104   1727       Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
  1105   1728     }
  1106   1729   
  1107   1730     return TCL_OK;
  1108   1731   }
  1109   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.

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

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