diff --git a/db/sqlite3/README.MOZILLA b/db/sqlite3/README.MOZILLA index eec58505db6..5c99b3bf646 100644 --- a/db/sqlite3/README.MOZILLA +++ b/db/sqlite3/README.MOZILLA @@ -9,8 +9,8 @@ Makefile.in there) that we use to build. To move to a new version: -Copy the sqlite3.h and sqlite3.c files from the amalgamation of sqlite. Also, -copy test_quota.h and test_quota.c from the full source package. +Copy the sqlite3.h and sqlite3.c files from the amalgamation of sqlite. +(We no longer use test_quota.h and test_quota.c) Be sure to update SQLITE_VERSION accordingly in $(topsrcdir)/configure.in as well as the version number at the top of this file. diff --git a/db/sqlite3/src/sqlite.def b/db/sqlite3/src/sqlite.def index 4c777e839cd..5eeb52257ae 100644 --- a/db/sqlite3/src/sqlite.def +++ b/db/sqlite3/src/sqlite.def @@ -132,6 +132,7 @@ EXPORTS sqlite3_transfer_bindings sqlite3_unlock_notify sqlite3_update_hook + sqlite3_uri_parameter sqlite3_user_data sqlite3_value_blob sqlite3_value_bytes diff --git a/db/sqlite3/src/test_quota.c b/db/sqlite3/src/test_quota.c deleted file mode 100644 index 2ce46acdc06..00000000000 --- a/db/sqlite3/src/test_quota.c +++ /dev/null @@ -1,2001 +0,0 @@ -/* -** 2010 September 31 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** -** This file contains a VFS "shim" - a layer that sits in between the -** pager and the real VFS. -** -** This particular shim enforces a quota system on files. One or more -** database files are in a "quota group" that is defined by a GLOB -** pattern. A quota is set for the combined size of all files in the -** the group. A quota of zero means "no limit". If the total size -** of all files in the quota group is greater than the limit, then -** write requests that attempt to enlarge a file fail with SQLITE_FULL. -** -** However, before returning SQLITE_FULL, the write requests invoke -** a callback function that is configurable for each quota group. -** This callback has the opportunity to enlarge the quota. If the -** callback does enlarge the quota such that the total size of all -** files within the group is less than the new quota, then the write -** continues as if nothing had happened. -*/ -#include "test_quota.h" -#include -#include - -/* -** For an build without mutexes, no-op the mutex calls. -*/ -#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0 -#define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8) -#define sqlite3_mutex_free(X) -#define sqlite3_mutex_enter(X) -#define sqlite3_mutex_try(X) SQLITE_OK -#define sqlite3_mutex_leave(X) -#define sqlite3_mutex_held(X) ((void)(X),1) -#define sqlite3_mutex_notheld(X) ((void)(X),1) -#endif /* SQLITE_THREADSAFE==0 */ - - -/* -** Figure out if we are dealing with Unix, Windows, or some other -** operating system. After the following block of preprocess macros, -** all of SQLITE_OS_UNIX, SQLITE_OS_WIN, and SQLITE_OS_OTHER -** will defined to either 1 or 0. One of the four will be 1. The other -** three will be 0. -*/ -#if defined(SQLITE_OS_OTHER) -# if SQLITE_OS_OTHER==1 -# undef SQLITE_OS_UNIX -# define SQLITE_OS_UNIX 0 -# undef SQLITE_OS_WIN -# define SQLITE_OS_WIN 0 -# else -# undef SQLITE_OS_OTHER -# endif -#endif -#if !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_OTHER) -# define SQLITE_OS_OTHER 0 -# ifndef SQLITE_OS_WIN -# if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) \ - || defined(__MINGW32__) || defined(__BORLANDC__) -# define SQLITE_OS_WIN 1 -# define SQLITE_OS_UNIX 0 -# else -# define SQLITE_OS_WIN 0 -# define SQLITE_OS_UNIX 1 -# endif -# else -# define SQLITE_OS_UNIX 0 -# endif -#else -# ifndef SQLITE_OS_WIN -# define SQLITE_OS_WIN 0 -# endif -#endif - -#if SQLITE_OS_UNIX -# include -#endif -#if SQLITE_OS_WIN -# include -# include -#endif - - -/************************ Object Definitions ******************************/ - -/* Forward declaration of all object types */ -typedef struct quotaGroup quotaGroup; -typedef struct quotaConn quotaConn; -typedef struct quotaFile quotaFile; - -/* -** A "quota group" is a collection of files whose collective size we want -** to limit. Each quota group is defined by a GLOB pattern. -** -** There is an instance of the following object for each defined quota -** group. This object records the GLOB pattern that defines which files -** belong to the quota group. The object also remembers the size limit -** for the group (the quota) and the callback to be invoked when the -** sum of the sizes of the files within the group goes over the limit. -** -** A quota group must be established (using sqlite3_quota_set(...)) -** prior to opening any of the database connections that access files -** within the quota group. -*/ -struct quotaGroup { - const char *zPattern; /* Filename pattern to be quotaed */ - sqlite3_int64 iLimit; /* Upper bound on total file size */ - sqlite3_int64 iSize; /* Current size of all files */ - void (*xCallback)( /* Callback invoked when going over quota */ - const char *zFilename, /* Name of file whose size increases */ - sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ - sqlite3_int64 iSize, /* Total size of all files in the group */ - void *pArg /* Client data */ - ); - void *pArg; /* Third argument to the xCallback() */ - void (*xDestroy)(void*); /* Optional destructor for pArg */ - quotaGroup *pNext, **ppPrev; /* Doubly linked list of all quota objects */ - quotaFile *pFiles; /* Files within this group */ -}; - -/* -** An instance of this structure represents a single file that is part -** of a quota group. A single file can be opened multiple times. In -** order keep multiple openings of the same file from causing the size -** of the file to count against the quota multiple times, each file -** has a unique instance of this object and multiple open connections -** to the same file each point to a single instance of this object. -*/ -struct quotaFile { - char *zFilename; /* Name of this file */ - quotaGroup *pGroup; /* Quota group to which this file belongs */ - sqlite3_int64 iSize; /* Current size of this file */ - int nRef; /* Number of times this file is open */ - int deleteOnClose; /* True to delete this file when it closes */ - quotaFile *pNext, **ppPrev; /* Linked list of files in the same group */ -}; - -/* -** An instance of the following object represents each open connection -** to a file that participates in quota tracking. This object is a -** subclass of sqlite3_file. The sqlite3_file object for the underlying -** VFS is appended to this structure. -*/ -struct quotaConn { - sqlite3_file base; /* Base class - must be first */ - quotaFile *pFile; /* The underlying file */ - /* The underlying VFS sqlite3_file is appended to this object */ -}; - -/* -** An instance of the following object records the state of an -** open file. This object is opaque to all users - the internal -** structure is only visible to the functions below. -*/ -struct quota_FILE { - FILE *f; /* Open stdio file pointer */ - sqlite3_int64 iOfst; /* Current offset into the file */ - quotaFile *pFile; /* The file record in the quota system */ -#if SQLITE_OS_WIN - char *zMbcsName; /* Full MBCS pathname of the file */ -#endif -}; - - -/************************* Global Variables **********************************/ -/* -** All global variables used by this file are containing within the following -** gQuota structure. -*/ -static struct { - /* The pOrigVfs is the real, original underlying VFS implementation. - ** Most operations pass-through to the real VFS. This value is read-only - ** during operation. It is only modified at start-time and thus does not - ** require a mutex. - */ - sqlite3_vfs *pOrigVfs; - - /* The sThisVfs is the VFS structure used by this shim. It is initialized - ** at start-time and thus does not require a mutex - */ - sqlite3_vfs sThisVfs; - - /* The sIoMethods defines the methods used by sqlite3_file objects - ** associated with this shim. It is initialized at start-time and does - ** not require a mutex. - ** - ** When the underlying VFS is called to open a file, it might return - ** either a version 1 or a version 2 sqlite3_file object. This shim - ** has to create a wrapper sqlite3_file of the same version. Hence - ** there are two I/O method structures, one for version 1 and the other - ** for version 2. - */ - sqlite3_io_methods sIoMethodsV1; - sqlite3_io_methods sIoMethodsV2; - - /* True when this shim as been initialized. - */ - int isInitialized; - - /* For run-time access any of the other global data structures in this - ** shim, the following mutex must be held. - */ - sqlite3_mutex *pMutex; - - /* List of quotaGroup objects. - */ - quotaGroup *pGroup; - -} gQuota; - -/************************* Utility Routines *********************************/ -/* -** Acquire and release the mutex used to serialize access to the -** list of quotaGroups. -*/ -static void quotaEnter(void){ sqlite3_mutex_enter(gQuota.pMutex); } -static void quotaLeave(void){ sqlite3_mutex_leave(gQuota.pMutex); } - -/* Count the number of open files in a quotaGroup -*/ -static int quotaGroupOpenFileCount(quotaGroup *pGroup){ - int N = 0; - quotaFile *pFile = pGroup->pFiles; - while( pFile ){ - if( pFile->nRef ) N++; - pFile = pFile->pNext; - } - return N; -} - -/* Remove a file from a quota group. -*/ -static void quotaRemoveFile(quotaFile *pFile){ - quotaGroup *pGroup = pFile->pGroup; - pGroup->iSize -= pFile->iSize; - *pFile->ppPrev = pFile->pNext; - if( pFile->pNext ) pFile->pNext->ppPrev = pFile->ppPrev; - sqlite3_free(pFile); -} - -/* Remove all files from a quota group. It is always the case that -** all files will be closed when this routine is called. -*/ -static void quotaRemoveAllFiles(quotaGroup *pGroup){ - while( pGroup->pFiles ){ - assert( pGroup->pFiles->nRef==0 ); - quotaRemoveFile(pGroup->pFiles); - } -} - - -/* If the reference count and threshold for a quotaGroup are both -** zero, then destroy the quotaGroup. -*/ -static void quotaGroupDeref(quotaGroup *pGroup){ - if( pGroup->iLimit==0 && quotaGroupOpenFileCount(pGroup)==0 ){ - quotaRemoveAllFiles(pGroup); - *pGroup->ppPrev = pGroup->pNext; - if( pGroup->pNext ) pGroup->pNext->ppPrev = pGroup->ppPrev; - if( pGroup->xDestroy ) pGroup->xDestroy(pGroup->pArg); - sqlite3_free(pGroup); - } -} - -/* -** Return TRUE if string z matches glob pattern zGlob. -** -** Globbing rules: -** -** '*' Matches any sequence of zero or more characters. -** -** '?' Matches exactly one character. -** -** [...] Matches one character from the enclosed list of -** characters. -** -** [^...] Matches one character not in the enclosed list. -** -** / Matches "/" or "\\" -** -*/ -static int quotaStrglob(const char *zGlob, const char *z){ - int c, c2, cx; - int invert; - int seen; - - while( (c = (*(zGlob++)))!=0 ){ - if( c=='*' ){ - while( (c=(*(zGlob++))) == '*' || c=='?' ){ - if( c=='?' && (*(z++))==0 ) return 0; - } - if( c==0 ){ - return 1; - }else if( c=='[' ){ - while( *z && quotaStrglob(zGlob-1,z)==0 ){ - z++; - } - return (*z)!=0; - } - cx = (c=='/') ? '\\' : c; - while( (c2 = (*(z++)))!=0 ){ - while( c2!=c && c2!=cx ){ - c2 = *(z++); - if( c2==0 ) return 0; - } - if( quotaStrglob(zGlob,z) ) return 1; - } - return 0; - }else if( c=='?' ){ - if( (*(z++))==0 ) return 0; - }else if( c=='[' ){ - int prior_c = 0; - seen = 0; - invert = 0; - c = *(z++); - if( c==0 ) return 0; - c2 = *(zGlob++); - if( c2=='^' ){ - invert = 1; - c2 = *(zGlob++); - } - if( c2==']' ){ - if( c==']' ) seen = 1; - c2 = *(zGlob++); - } - while( c2 && c2!=']' ){ - if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){ - c2 = *(zGlob++); - if( c>=prior_c && c<=c2 ) seen = 1; - prior_c = 0; - }else{ - if( c==c2 ){ - seen = 1; - } - prior_c = c2; - } - c2 = *(zGlob++); - } - if( c2==0 || (seen ^ invert)==0 ) return 0; - }else if( c=='/' ){ - if( z[0]!='/' && z[0]!='\\' ) return 0; - z++; - }else{ - if( c!=(*(z++)) ) return 0; - } - } - return *z==0; -} - - -/* Find a quotaGroup given the filename. -** -** Return a pointer to the quotaGroup object. Return NULL if not found. -*/ -static quotaGroup *quotaGroupFind(const char *zFilename){ - quotaGroup *p; - for(p=gQuota.pGroup; p && quotaStrglob(p->zPattern, zFilename)==0; - p=p->pNext){} - return p; -} - -/* Translate an sqlite3_file* that is really a quotaConn* into -** the sqlite3_file* for the underlying original VFS. -*/ -static sqlite3_file *quotaSubOpen(sqlite3_file *pConn){ - quotaConn *p = (quotaConn*)pConn; - return (sqlite3_file*)&p[1]; -} - -/* Find a file in a quota group and return a pointer to that file. -** Return NULL if the file is not in the group. -*/ -static quotaFile *quotaFindFile( - quotaGroup *pGroup, /* Group in which to look for the file */ - const char *zName, /* Full pathname of the file */ - int createFlag /* Try to create the file if not found */ -){ - quotaFile *pFile = pGroup->pFiles; - while( pFile && strcmp(pFile->zFilename, zName)!=0 ){ - pFile = pFile->pNext; - } - if( pFile==0 && createFlag ){ - int nName = (int)(strlen(zName) & 0x3fffffff); - pFile = (quotaFile *)sqlite3_malloc( sizeof(*pFile) + nName + 1 ); - if( pFile ){ - memset(pFile, 0, sizeof(*pFile)); - pFile->zFilename = (char*)&pFile[1]; - memcpy(pFile->zFilename, zName, nName+1); - pFile->pNext = pGroup->pFiles; - if( pGroup->pFiles ) pGroup->pFiles->ppPrev = &pFile->pNext; - pFile->ppPrev = &pGroup->pFiles; - pGroup->pFiles = pFile; - pFile->pGroup = pGroup; - } - } - return pFile; -} -/* -** Translate UTF8 to MBCS for use in fopen() calls. Return a pointer to the -** translated text.. Call quota_mbcs_free() to deallocate any memory -** used to store the returned pointer when done. -*/ -static char *quota_utf8_to_mbcs(const char *zUtf8){ -#if SQLITE_OS_WIN - size_t n; /* Bytes in zUtf8 */ - int nWide; /* number of UTF-16 characters */ - int nMbcs; /* Bytes of MBCS */ - LPWSTR zTmpWide; /* The UTF16 text */ - char *zMbcs; /* The MBCS text */ - int codepage; /* Code page used by fopen() */ - - n = strlen(zUtf8); - nWide = MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, NULL, 0); - if( nWide==0 ) return 0; - zTmpWide = (LPWSTR)sqlite3_malloc( (nWide+1)*sizeof(zTmpWide[0]) ); - if( zTmpWide==0 ) return 0; - MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zTmpWide, nWide); - codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP; - nMbcs = WideCharToMultiByte(codepage, 0, zTmpWide, nWide, 0, 0, 0, 0); - zMbcs = nMbcs ? (char*)sqlite3_malloc( nMbcs+1 ) : 0; - if( zMbcs ){ - WideCharToMultiByte(codepage, 0, zTmpWide, nWide, zMbcs, nMbcs, 0, 0); - } - sqlite3_free(zTmpWide); - return zMbcs; -#else - return (char*)zUtf8; /* No-op on unix */ -#endif -} - -/* -** Deallocate any memory allocated by quota_utf8_to_mbcs(). -*/ -static void quota_mbcs_free(char *zOld){ -#if SQLITE_OS_WIN - sqlite3_free(zOld); -#else - /* No-op on unix */ -#endif -} - -/************************* VFS Method Wrappers *****************************/ -/* -** This is the xOpen method used for the "quota" VFS. -** -** Most of the work is done by the underlying original VFS. This method -** simply links the new file into the appropriate quota group if it is a -** file that needs to be tracked. -*/ -static int quotaOpen( - sqlite3_vfs *pVfs, /* The quota VFS */ - const char *zName, /* Name of file to be opened */ - sqlite3_file *pConn, /* Fill in this file descriptor */ - int flags, /* Flags to control the opening */ - int *pOutFlags /* Flags showing results of opening */ -){ - int rc; /* Result code */ - quotaConn *pQuotaOpen; /* The new quota file descriptor */ - quotaFile *pFile; /* Corresponding quotaFile obj */ - quotaGroup *pGroup; /* The group file belongs to */ - sqlite3_file *pSubOpen; /* Real file descriptor */ - sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs; /* Real VFS */ - - /* If the file is not a main database file or a WAL, then use the - ** normal xOpen method. - */ - if( (flags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_WAL))==0 ){ - return pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags); - } - - /* If the name of the file does not match any quota group, then - ** use the normal xOpen method. - */ - quotaEnter(); - pGroup = quotaGroupFind(zName); - if( pGroup==0 ){ - rc = pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags); - }else{ - /* If we get to this point, it means the file needs to be quota tracked. - */ - pQuotaOpen = (quotaConn*)pConn; - pSubOpen = quotaSubOpen(pConn); - rc = pOrigVfs->xOpen(pOrigVfs, zName, pSubOpen, flags, pOutFlags); - if( rc==SQLITE_OK ){ - pFile = quotaFindFile(pGroup, zName, 1); - if( pFile==0 ){ - quotaLeave(); - pSubOpen->pMethods->xClose(pSubOpen); - return SQLITE_NOMEM; - } - pFile->deleteOnClose = (flags & SQLITE_OPEN_DELETEONCLOSE)!=0; - pFile->nRef++; - pQuotaOpen->pFile = pFile; - if( pSubOpen->pMethods->iVersion==1 ){ - pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV1; - }else{ - pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV2; - } - } - } - quotaLeave(); - return rc; -} - -/* -** This is the xDelete method used for the "quota" VFS. -** -** If the file being deleted is part of the quota group, then reduce -** the size of the quota group accordingly. And remove the file from -** the set of files in the quota group. -*/ -static int quotaDelete( - sqlite3_vfs *pVfs, /* The quota VFS */ - const char *zName, /* Name of file to be deleted */ - int syncDir /* Do a directory sync after deleting */ -){ - int rc; /* Result code */ - quotaFile *pFile; /* Files in the quota */ - quotaGroup *pGroup; /* The group file belongs to */ - sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs; /* Real VFS */ - - /* Do the actual file delete */ - rc = pOrigVfs->xDelete(pOrigVfs, zName, syncDir); - - /* If the file just deleted is a member of a quota group, then remove - ** it from that quota group. - */ - if( rc==SQLITE_OK ){ - quotaEnter(); - pGroup = quotaGroupFind(zName); - if( pGroup ){ - pFile = quotaFindFile(pGroup, zName, 0); - if( pFile ){ - if( pFile->nRef ){ - pFile->deleteOnClose = 1; - }else{ - quotaRemoveFile(pFile); - quotaGroupDeref(pGroup); - } - } - } - quotaLeave(); - } - return rc; -} - - -/************************ I/O Method Wrappers *******************************/ - -/* xClose requests get passed through to the original VFS. But we -** also have to unlink the quotaConn from the quotaFile and quotaGroup. -** The quotaFile and/or quotaGroup are freed if they are no longer in use. -*/ -static int quotaClose(sqlite3_file *pConn){ - quotaConn *p = (quotaConn*)pConn; - quotaFile *pFile = p->pFile; - sqlite3_file *pSubOpen = quotaSubOpen(pConn); - int rc; - rc = pSubOpen->pMethods->xClose(pSubOpen); - quotaEnter(); - pFile->nRef--; - if( pFile->nRef==0 ){ - quotaGroup *pGroup = pFile->pGroup; - if( pFile->deleteOnClose ){ - gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0); - quotaRemoveFile(pFile); - } - quotaGroupDeref(pGroup); - } - quotaLeave(); - return rc; -} - -/* Pass xRead requests directory thru to the original VFS without -** further processing. -*/ -static int quotaRead( - sqlite3_file *pConn, - void *pBuf, - int iAmt, - sqlite3_int64 iOfst -){ - sqlite3_file *pSubOpen = quotaSubOpen(pConn); - return pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst); -} - -/* Check xWrite requests to see if they expand the file. If they do, -** the perform a quota check before passing them through to the -** original VFS. -*/ -static int quotaWrite( - sqlite3_file *pConn, - const void *pBuf, - int iAmt, - sqlite3_int64 iOfst -){ - quotaConn *p = (quotaConn*)pConn; - sqlite3_file *pSubOpen = quotaSubOpen(pConn); - sqlite3_int64 iEnd = iOfst+iAmt; - quotaGroup *pGroup; - quotaFile *pFile = p->pFile; - sqlite3_int64 szNew; - - if( pFile->iSizepGroup; - quotaEnter(); - szNew = pGroup->iSize - pFile->iSize + iEnd; - if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ - if( pGroup->xCallback ){ - pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew, - pGroup->pArg); - } - if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ - quotaLeave(); - return SQLITE_FULL; - } - } - pGroup->iSize = szNew; - pFile->iSize = iEnd; - quotaLeave(); - } - return pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst); -} - -/* Pass xTruncate requests thru to the original VFS. If the -** success, update the file size. -*/ -static int quotaTruncate(sqlite3_file *pConn, sqlite3_int64 size){ - quotaConn *p = (quotaConn*)pConn; - sqlite3_file *pSubOpen = quotaSubOpen(pConn); - int rc = pSubOpen->pMethods->xTruncate(pSubOpen, size); - quotaFile *pFile = p->pFile; - quotaGroup *pGroup; - if( rc==SQLITE_OK ){ - quotaEnter(); - pGroup = pFile->pGroup; - pGroup->iSize -= pFile->iSize; - pFile->iSize = size; - pGroup->iSize += size; - quotaLeave(); - } - return rc; -} - -/* Pass xSync requests through to the original VFS without change -*/ -static int quotaSync(sqlite3_file *pConn, int flags){ - sqlite3_file *pSubOpen = quotaSubOpen(pConn); - return pSubOpen->pMethods->xSync(pSubOpen, flags); -} - -/* Pass xFileSize requests through to the original VFS but then -** update the quotaGroup with the new size before returning. -*/ -static int quotaFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){ - quotaConn *p = (quotaConn*)pConn; - sqlite3_file *pSubOpen = quotaSubOpen(pConn); - quotaFile *pFile = p->pFile; - quotaGroup *pGroup; - sqlite3_int64 sz; - int rc; - - rc = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); - if( rc==SQLITE_OK ){ - quotaEnter(); - pGroup = pFile->pGroup; - pGroup->iSize -= pFile->iSize; - pFile->iSize = sz; - pGroup->iSize += sz; - quotaLeave(); - *pSize = sz; - } - return rc; -} - -/* Pass xLock requests through to the original VFS unchanged. -*/ -static int quotaLock(sqlite3_file *pConn, int lock){ - sqlite3_file *pSubOpen = quotaSubOpen(pConn); - return pSubOpen->pMethods->xLock(pSubOpen, lock); -} - -/* Pass xUnlock requests through to the original VFS unchanged. -*/ -static int quotaUnlock(sqlite3_file *pConn, int lock){ - sqlite3_file *pSubOpen = quotaSubOpen(pConn); - return pSubOpen->pMethods->xUnlock(pSubOpen, lock); -} - -/* Pass xCheckReservedLock requests through to the original VFS unchanged. -*/ -static int quotaCheckReservedLock(sqlite3_file *pConn, int *pResOut){ - sqlite3_file *pSubOpen = quotaSubOpen(pConn); - return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut); -} - -/* Pass xFileControl requests through to the original VFS unchanged. -*/ -static int quotaFileControl(sqlite3_file *pConn, int op, void *pArg){ - sqlite3_file *pSubOpen = quotaSubOpen(pConn); - int rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); -#if defined(SQLITE_FCNTL_VFSNAME) - if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){ - *(char**)pArg = sqlite3_mprintf("quota/%z", *(char**)pArg); - } -#endif - return rc; -} - -/* Pass xSectorSize requests through to the original VFS unchanged. -*/ -static int quotaSectorSize(sqlite3_file *pConn){ - sqlite3_file *pSubOpen = quotaSubOpen(pConn); - return pSubOpen->pMethods->xSectorSize(pSubOpen); -} - -/* Pass xDeviceCharacteristics requests through to the original VFS unchanged. -*/ -static int quotaDeviceCharacteristics(sqlite3_file *pConn){ - sqlite3_file *pSubOpen = quotaSubOpen(pConn); - return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen); -} - -/* Pass xShmMap requests through to the original VFS unchanged. -*/ -static int quotaShmMap( - sqlite3_file *pConn, /* Handle open on database file */ - int iRegion, /* Region to retrieve */ - int szRegion, /* Size of regions */ - int bExtend, /* True to extend file if necessary */ - void volatile **pp /* OUT: Mapped memory */ -){ - sqlite3_file *pSubOpen = quotaSubOpen(pConn); - return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend, pp); -} - -/* Pass xShmLock requests through to the original VFS unchanged. -*/ -static int quotaShmLock( - sqlite3_file *pConn, /* Database file holding the shared memory */ - int ofst, /* First lock to acquire or release */ - int n, /* Number of locks to acquire or release */ - int flags /* What to do with the lock */ -){ - sqlite3_file *pSubOpen = quotaSubOpen(pConn); - return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags); -} - -/* Pass xShmBarrier requests through to the original VFS unchanged. -*/ -static void quotaShmBarrier(sqlite3_file *pConn){ - sqlite3_file *pSubOpen = quotaSubOpen(pConn); - pSubOpen->pMethods->xShmBarrier(pSubOpen); -} - -/* Pass xShmUnmap requests through to the original VFS unchanged. -*/ -static int quotaShmUnmap(sqlite3_file *pConn, int deleteFlag){ - sqlite3_file *pSubOpen = quotaSubOpen(pConn); - return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag); -} - -/************************** Public Interfaces *****************************/ -/* -** Initialize the quota VFS shim. Use the VFS named zOrigVfsName -** as the VFS that does the actual work. Use the default if -** zOrigVfsName==NULL. -** -** The quota VFS shim is named "quota". It will become the default -** VFS if makeDefault is non-zero. -** -** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once -** during start-up. -*/ -int sqlite3_quota_initialize(const char *zOrigVfsName, int makeDefault){ - sqlite3_vfs *pOrigVfs; - if( gQuota.isInitialized ) return SQLITE_MISUSE; - pOrigVfs = sqlite3_vfs_find(zOrigVfsName); - if( pOrigVfs==0 ) return SQLITE_ERROR; - assert( pOrigVfs!=&gQuota.sThisVfs ); - gQuota.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); - if( !gQuota.pMutex ){ - return SQLITE_NOMEM; - } - gQuota.isInitialized = 1; - gQuota.pOrigVfs = pOrigVfs; - gQuota.sThisVfs = *pOrigVfs; - gQuota.sThisVfs.xOpen = quotaOpen; - gQuota.sThisVfs.xDelete = quotaDelete; - gQuota.sThisVfs.szOsFile += sizeof(quotaConn); - gQuota.sThisVfs.zName = "quota"; - gQuota.sIoMethodsV1.iVersion = 1; - gQuota.sIoMethodsV1.xClose = quotaClose; - gQuota.sIoMethodsV1.xRead = quotaRead; - gQuota.sIoMethodsV1.xWrite = quotaWrite; - gQuota.sIoMethodsV1.xTruncate = quotaTruncate; - gQuota.sIoMethodsV1.xSync = quotaSync; - gQuota.sIoMethodsV1.xFileSize = quotaFileSize; - gQuota.sIoMethodsV1.xLock = quotaLock; - gQuota.sIoMethodsV1.xUnlock = quotaUnlock; - gQuota.sIoMethodsV1.xCheckReservedLock = quotaCheckReservedLock; - gQuota.sIoMethodsV1.xFileControl = quotaFileControl; - gQuota.sIoMethodsV1.xSectorSize = quotaSectorSize; - gQuota.sIoMethodsV1.xDeviceCharacteristics = quotaDeviceCharacteristics; - gQuota.sIoMethodsV2 = gQuota.sIoMethodsV1; - gQuota.sIoMethodsV2.iVersion = 2; - gQuota.sIoMethodsV2.xShmMap = quotaShmMap; - gQuota.sIoMethodsV2.xShmLock = quotaShmLock; - gQuota.sIoMethodsV2.xShmBarrier = quotaShmBarrier; - gQuota.sIoMethodsV2.xShmUnmap = quotaShmUnmap; - sqlite3_vfs_register(&gQuota.sThisVfs, makeDefault); - return SQLITE_OK; -} - -/* -** Shutdown the quota system. -** -** All SQLite database connections must be closed before calling this -** routine. -** -** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once while -** shutting down in order to free all remaining quota groups. -*/ -int sqlite3_quota_shutdown(void){ - quotaGroup *pGroup; - if( gQuota.isInitialized==0 ) return SQLITE_MISUSE; - for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){ - if( quotaGroupOpenFileCount(pGroup)>0 ) return SQLITE_MISUSE; - } - while( gQuota.pGroup ){ - pGroup = gQuota.pGroup; - gQuota.pGroup = pGroup->pNext; - pGroup->iLimit = 0; - assert( quotaGroupOpenFileCount(pGroup)==0 ); - quotaGroupDeref(pGroup); - } - gQuota.isInitialized = 0; - sqlite3_mutex_free(gQuota.pMutex); - sqlite3_vfs_unregister(&gQuota.sThisVfs); - memset(&gQuota, 0, sizeof(gQuota)); - return SQLITE_OK; -} - -/* -** Create or destroy a quota group. -** -** The quota group is defined by the zPattern. When calling this routine -** with a zPattern for a quota group that already exists, this routine -** merely updates the iLimit, xCallback, and pArg values for that quota -** group. If zPattern is new, then a new quota group is created. -** -** If the iLimit for a quota group is set to zero, then the quota group -** is disabled and will be deleted when the last database connection using -** the quota group is closed. -** -** Calling this routine on a zPattern that does not exist and with a -** zero iLimit is a no-op. -** -** A quota group must exist with a non-zero iLimit prior to opening -** database connections if those connections are to participate in the -** quota group. Creating a quota group does not affect database connections -** that are already open. -*/ -int sqlite3_quota_set( - const char *zPattern, /* The filename pattern */ - sqlite3_int64 iLimit, /* New quota to set for this quota group */ - void (*xCallback)( /* Callback invoked when going over quota */ - const char *zFilename, /* Name of file whose size increases */ - sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ - sqlite3_int64 iSize, /* Total size of all files in the group */ - void *pArg /* Client data */ - ), - void *pArg, /* client data passed thru to callback */ - void (*xDestroy)(void*) /* Optional destructor for pArg */ -){ - quotaGroup *pGroup; - quotaEnter(); - pGroup = gQuota.pGroup; - while( pGroup && strcmp(pGroup->zPattern, zPattern)!=0 ){ - pGroup = pGroup->pNext; - } - if( pGroup==0 ){ - int nPattern = (int)(strlen(zPattern) & 0x3fffffff); - if( iLimit<=0 ){ - quotaLeave(); - return SQLITE_OK; - } - pGroup = (quotaGroup *)sqlite3_malloc( sizeof(*pGroup) + nPattern + 1 ); - if( pGroup==0 ){ - quotaLeave(); - return SQLITE_NOMEM; - } - memset(pGroup, 0, sizeof(*pGroup)); - pGroup->zPattern = (char*)&pGroup[1]; - memcpy((char *)pGroup->zPattern, zPattern, nPattern+1); - if( gQuota.pGroup ) gQuota.pGroup->ppPrev = &pGroup->pNext; - pGroup->pNext = gQuota.pGroup; - pGroup->ppPrev = &gQuota.pGroup; - gQuota.pGroup = pGroup; - } - pGroup->iLimit = iLimit; - pGroup->xCallback = xCallback; - if( pGroup->xDestroy && pGroup->pArg!=pArg ){ - pGroup->xDestroy(pGroup->pArg); - } - pGroup->pArg = pArg; - pGroup->xDestroy = xDestroy; - quotaGroupDeref(pGroup); - quotaLeave(); - return SQLITE_OK; -} - -/* -** Bring the named file under quota management. Or if it is already under -** management, update its size. -*/ -int sqlite3_quota_file(const char *zFilename){ - char *zFull; - sqlite3_file *fd; - int rc; - int outFlags = 0; - sqlite3_int64 iSize; - int nAlloc = gQuota.sThisVfs.szOsFile + gQuota.sThisVfs.mxPathname+2; - - /* Allocate space for a file-handle and the full path for file zFilename */ - fd = (sqlite3_file *)sqlite3_malloc(nAlloc); - if( fd==0 ){ - rc = SQLITE_NOMEM; - }else{ - zFull = &((char *)fd)[gQuota.sThisVfs.szOsFile]; - rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename, - gQuota.sThisVfs.mxPathname+1, zFull); - } - - if( rc==SQLITE_OK ){ - zFull[strlen(zFull)+1] = '\0'; - rc = quotaOpen(&gQuota.sThisVfs, zFull, fd, - SQLITE_OPEN_READONLY | SQLITE_OPEN_MAIN_DB, &outFlags); - if( rc==SQLITE_OK ){ - fd->pMethods->xFileSize(fd, &iSize); - fd->pMethods->xClose(fd); - }else if( rc==SQLITE_CANTOPEN ){ - quotaGroup *pGroup; - quotaFile *pFile; - quotaEnter(); - pGroup = quotaGroupFind(zFull); - if( pGroup ){ - pFile = quotaFindFile(pGroup, zFull, 0); - if( pFile ) quotaRemoveFile(pFile); - } - quotaLeave(); - } - } - - sqlite3_free(fd); - return rc; -} - -/* -** Open a potentially quotaed file for I/O. -*/ -quota_FILE *sqlite3_quota_fopen(const char *zFilename, const char *zMode){ - quota_FILE *p = 0; - char *zFull = 0; - char *zFullTranslated = 0; - int rc; - quotaGroup *pGroup; - quotaFile *pFile; - - zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1); - if( zFull==0 ) return 0; - rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename, - gQuota.sThisVfs.mxPathname+1, zFull); - if( rc ) goto quota_fopen_error; - p = (quota_FILE*)sqlite3_malloc(sizeof(*p)); - if( p==0 ) goto quota_fopen_error; - memset(p, 0, sizeof(*p)); - zFullTranslated = quota_utf8_to_mbcs(zFull); - if( zFullTranslated==0 ) goto quota_fopen_error; - p->f = fopen(zFullTranslated, zMode); - if( p->f==0 ) goto quota_fopen_error; - quotaEnter(); - pGroup = quotaGroupFind(zFull); - if( pGroup ){ - pFile = quotaFindFile(pGroup, zFull, 1); - if( pFile==0 ){ - quotaLeave(); - goto quota_fopen_error; - } - pFile->nRef++; - p->pFile = pFile; - } - quotaLeave(); - sqlite3_free(zFull); -#if SQLITE_OS_WIN - p->zMbcsName = zFullTranslated; -#endif - return p; - -quota_fopen_error: - quota_mbcs_free(zFullTranslated); - sqlite3_free(zFull); - if( p && p->f ) fclose(p->f); - sqlite3_free(p); - return 0; -} - -/* -** Read content from a quota_FILE -*/ -size_t sqlite3_quota_fread( - void *pBuf, /* Store the content here */ - size_t size, /* Size of each element */ - size_t nmemb, /* Number of elements to read */ - quota_FILE *p /* Read from this quota_FILE object */ -){ - return fread(pBuf, size, nmemb, p->f); -} - -/* -** Write content into a quota_FILE. Invoke the quota callback and block -** the write if we exceed quota. -*/ -size_t sqlite3_quota_fwrite( - const void *pBuf, /* Take content to write from here */ - size_t size, /* Size of each element */ - size_t nmemb, /* Number of elements */ - quota_FILE *p /* Write to this quota_FILE objecct */ -){ - sqlite3_int64 iOfst; - sqlite3_int64 iEnd; - sqlite3_int64 szNew; - quotaFile *pFile; - size_t rc; - - iOfst = ftell(p->f); - iEnd = iOfst + size*nmemb; - pFile = p->pFile; - if( pFile && pFile->iSizepGroup; - quotaEnter(); - szNew = pGroup->iSize - pFile->iSize + iEnd; - if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ - if( pGroup->xCallback ){ - pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew, - pGroup->pArg); - } - if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ - iEnd = pGroup->iLimit - pGroup->iSize + pFile->iSize; - nmemb = (size_t)((iEnd - iOfst)/size); - iEnd = iOfst + size*nmemb; - szNew = pGroup->iSize - pFile->iSize + iEnd; - } - } - pGroup->iSize = szNew; - pFile->iSize = iEnd; - quotaLeave(); - }else{ - pFile = 0; - } - rc = fwrite(pBuf, size, nmemb, p->f); - - /* If the write was incomplete, adjust the file size and group size - ** downward */ - if( rc=0 ? rc : 0; - sqlite3_int64 iNewEnd = iOfst + size*nWritten; - if( iNewEndpGroup->iSize += iNewEnd - pFile->iSize; - pFile->iSize = iNewEnd; - quotaLeave(); - } - return rc; -} - -/* -** Close an open quota_FILE stream. -*/ -int sqlite3_quota_fclose(quota_FILE *p){ - int rc; - quotaFile *pFile; - rc = fclose(p->f); - pFile = p->pFile; - if( pFile ){ - quotaEnter(); - pFile->nRef--; - if( pFile->nRef==0 ){ - quotaGroup *pGroup = pFile->pGroup; - if( pFile->deleteOnClose ){ - gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0); - quotaRemoveFile(pFile); - } - quotaGroupDeref(pGroup); - } - quotaLeave(); - } -#if SQLITE_OS_WIN - quota_mbcs_free(p->zMbcsName); -#endif - sqlite3_free(p); - return rc; -} - -/* -** Flush memory buffers for a quota_FILE to disk. -*/ -int sqlite3_quota_fflush(quota_FILE *p, int doFsync){ - int rc; - rc = fflush(p->f); - if( rc==0 && doFsync ){ -#if SQLITE_OS_UNIX - rc = fsync(fileno(p->f)); -#endif -#if SQLITE_OS_WIN - rc = _commit(_fileno(p->f)); -#endif - } - return rc!=0; -} - -/* -** Seek on a quota_FILE stream. -*/ -int sqlite3_quota_fseek(quota_FILE *p, long offset, int whence){ - return fseek(p->f, offset, whence); -} - -/* -** rewind a quota_FILE stream. -*/ -void sqlite3_quota_rewind(quota_FILE *p){ - rewind(p->f); -} - -/* -** Tell the current location of a quota_FILE stream. -*/ -long sqlite3_quota_ftell(quota_FILE *p){ - return ftell(p->f); -} - -/* -** Test the error indicator for the given file. -*/ -int sqlite3_quota_ferror(quota_FILE *p){ - return ferror(p->f); -} - -/* -** Truncate a file to szNew bytes. -*/ -int sqlite3_quota_ftruncate(quota_FILE *p, sqlite3_int64 szNew){ - quotaFile *pFile = p->pFile; - int rc; - if( (pFile = p->pFile)!=0 && pFile->iSizeiSizepGroup; - quotaEnter(); - pGroup->iSize += szNew - pFile->iSize; - quotaLeave(); - } -#if SQLITE_OS_UNIX - rc = ftruncate(fileno(p->f), szNew); -#endif -#if SQLITE_OS_WIN - rc = _chsize_s(_fileno(p->f), szNew); -#endif - if( pFile && rc==0 ){ - quotaGroup *pGroup = pFile->pGroup; - quotaEnter(); - pGroup->iSize += szNew - pFile->iSize; - pFile->iSize = szNew; - quotaLeave(); - } - return rc; -} - -/* -** Determine the time that the given file was last modified, in -** seconds size 1970. Write the result into *pTime. Return 0 on -** success and non-zero on any kind of error. -*/ -int sqlite3_quota_file_mtime(quota_FILE *p, time_t *pTime){ - int rc; -#if SQLITE_OS_UNIX - struct stat buf; - rc = fstat(fileno(p->f), &buf); -#endif -#if SQLITE_OS_WIN - struct _stati64 buf; - rc = _stati64(p->zMbcsName, &buf); -#endif - if( rc==0 ) *pTime = buf.st_mtime; - return rc; -} - -/* -** Return the true size of the file, as reported by the operating -** system. -*/ -sqlite3_int64 sqlite3_quota_file_truesize(quota_FILE *p){ - int rc; -#if SQLITE_OS_UNIX - struct stat buf; - rc = fstat(fileno(p->f), &buf); -#endif -#if SQLITE_OS_WIN - struct _stati64 buf; - rc = _stati64(p->zMbcsName, &buf); -#endif - return rc==0 ? buf.st_size : -1; -} - -/* -** Return the size of the file, as it is known to the quota subsystem. -*/ -sqlite3_int64 sqlite3_quota_file_size(quota_FILE *p){ - return p->pFile ? p->pFile->iSize : -1; -} - -/* -** Determine the amount of data in bytes available for reading -** in the given file. -*/ -long sqlite3_quota_file_available(quota_FILE *p){ - FILE* f = p->f; - long pos1, pos2; - int rc; - pos1 = ftell(f); - if ( pos1 < 0 ) return -1; - rc = fseek(f, 0, SEEK_END); - if ( rc != 0 ) return -1; - pos2 = ftell(f); - if ( pos2 < 0 ) return -1; - rc = fseek(f, pos1, SEEK_SET); - if ( rc != 0 ) return -1; - return pos2 - pos1; -} - -/* -** Remove a managed file. Update quotas accordingly. -*/ -int sqlite3_quota_remove(const char *zFilename){ - char *zFull; /* Full pathname for zFilename */ - size_t nFull; /* Number of bytes in zFilename */ - int rc; /* Result code */ - quotaGroup *pGroup; /* Group containing zFilename */ - quotaFile *pFile; /* A file in the group */ - quotaFile *pNextFile; /* next file in the group */ - int diff; /* Difference between filenames */ - char c; /* First character past end of pattern */ - - zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1); - if( zFull==0 ) return SQLITE_NOMEM; - rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename, - gQuota.sThisVfs.mxPathname+1, zFull); - if( rc ){ - sqlite3_free(zFull); - return rc; - } - - /* Figure out the length of the full pathname. If the name ends with - ** / (or \ on windows) then remove the trailing /. - */ - nFull = strlen(zFull); - if( nFull>0 && (zFull[nFull-1]=='/' || zFull[nFull-1]=='\\') ){ - nFull--; - zFull[nFull] = 0; - } - - quotaEnter(); - pGroup = quotaGroupFind(zFull); - if( pGroup ){ - for(pFile=pGroup->pFiles; pFile && rc==SQLITE_OK; pFile=pNextFile){ - pNextFile = pFile->pNext; - diff = memcmp(zFull, pFile->zFilename, nFull); - if( diff==0 && ((c = pFile->zFilename[nFull])==0 || c=='/' || c=='\\') ){ - if( pFile->nRef ){ - pFile->deleteOnClose = 1; - }else{ - rc = gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0); - quotaRemoveFile(pFile); - quotaGroupDeref(pGroup); - } - } - } - } - quotaLeave(); - sqlite3_free(zFull); - return rc; -} - -/***************************** Test Code ***********************************/ -#ifdef SQLITE_TEST -#include - -/* -** Argument passed to a TCL quota-over-limit callback. -*/ -typedef struct TclQuotaCallback TclQuotaCallback; -struct TclQuotaCallback { - Tcl_Interp *interp; /* Interpreter in which to run the script */ - Tcl_Obj *pScript; /* Script to be run */ -}; - -extern const char *sqlite3TestErrorName(int); - - -/* -** This is the callback from a quota-over-limit. -*/ -static void tclQuotaCallback( - const char *zFilename, /* Name of file whose size increases */ - sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ - sqlite3_int64 iSize, /* Total size of all files in the group */ - void *pArg /* Client data */ -){ - TclQuotaCallback *p; /* Callback script object */ - Tcl_Obj *pEval; /* Script to evaluate */ - Tcl_Obj *pVarname; /* Name of variable to pass as 2nd arg */ - unsigned int rnd; /* Random part of pVarname */ - int rc; /* Tcl error code */ - - p = (TclQuotaCallback *)pArg; - if( p==0 ) return; - - pVarname = Tcl_NewStringObj("::piLimit_", -1); - Tcl_IncrRefCount(pVarname); - sqlite3_randomness(sizeof(rnd), (void *)&rnd); - Tcl_AppendObjToObj(pVarname, Tcl_NewIntObj((int)(rnd&0x7FFFFFFF))); - Tcl_ObjSetVar2(p->interp, pVarname, 0, Tcl_NewWideIntObj(*piLimit), 0); - - pEval = Tcl_DuplicateObj(p->pScript); - Tcl_IncrRefCount(pEval); - Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zFilename, -1)); - Tcl_ListObjAppendElement(0, pEval, pVarname); - Tcl_ListObjAppendElement(0, pEval, Tcl_NewWideIntObj(iSize)); - rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL); - - if( rc==TCL_OK ){ - Tcl_Obj *pLimit = Tcl_ObjGetVar2(p->interp, pVarname, 0, 0); - rc = Tcl_GetWideIntFromObj(p->interp, pLimit, piLimit); - Tcl_UnsetVar(p->interp, Tcl_GetString(pVarname), 0); - } - - Tcl_DecrRefCount(pEval); - Tcl_DecrRefCount(pVarname); - if( rc!=TCL_OK ) Tcl_BackgroundError(p->interp); -} - -/* -** Destructor for a TCL quota-over-limit callback. -*/ -static void tclCallbackDestructor(void *pObj){ - TclQuotaCallback *p = (TclQuotaCallback*)pObj; - if( p ){ - Tcl_DecrRefCount(p->pScript); - sqlite3_free((char *)p); - } -} - -/* -** tclcmd: sqlite3_quota_initialize NAME MAKEDEFAULT -*/ -static int test_quota_initialize( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - const char *zName; /* Name of new quota VFS */ - int makeDefault; /* True to make the new VFS the default */ - int rc; /* Value returned by quota_initialize() */ - - /* Process arguments */ - if( objc!=3 ){ - Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT"); - return TCL_ERROR; - } - zName = Tcl_GetString(objv[1]); - if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR; - if( zName[0]=='\0' ) zName = 0; - - /* Call sqlite3_quota_initialize() */ - rc = sqlite3_quota_initialize(zName, makeDefault); - Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); - - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_shutdown -*/ -static int test_quota_shutdown( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - int rc; /* Value returned by quota_shutdown() */ - - if( objc!=1 ){ - Tcl_WrongNumArgs(interp, 1, objv, ""); - return TCL_ERROR; - } - - /* Call sqlite3_quota_shutdown() */ - rc = sqlite3_quota_shutdown(); - Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); - - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_set PATTERN LIMIT SCRIPT -*/ -static int test_quota_set( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - const char *zPattern; /* File pattern to configure */ - sqlite3_int64 iLimit; /* Initial quota in bytes */ - Tcl_Obj *pScript; /* Tcl script to invoke to increase quota */ - int rc; /* Value returned by quota_set() */ - TclQuotaCallback *p; /* Callback object */ - int nScript; /* Length of callback script */ - void (*xDestroy)(void*); /* Optional destructor for pArg */ - void (*xCallback)(const char *, sqlite3_int64 *, sqlite3_int64, void *); - - /* Process arguments */ - if( objc!=4 ){ - Tcl_WrongNumArgs(interp, 1, objv, "PATTERN LIMIT SCRIPT"); - return TCL_ERROR; - } - zPattern = Tcl_GetString(objv[1]); - if( Tcl_GetWideIntFromObj(interp, objv[2], &iLimit) ) return TCL_ERROR; - pScript = objv[3]; - Tcl_GetStringFromObj(pScript, &nScript); - - if( nScript>0 ){ - /* Allocate a TclQuotaCallback object */ - p = (TclQuotaCallback *)sqlite3_malloc(sizeof(TclQuotaCallback)); - if( !p ){ - Tcl_SetResult(interp, (char *)"SQLITE_NOMEM", TCL_STATIC); - return TCL_OK; - } - memset(p, 0, sizeof(TclQuotaCallback)); - p->interp = interp; - Tcl_IncrRefCount(pScript); - p->pScript = pScript; - xDestroy = tclCallbackDestructor; - xCallback = tclQuotaCallback; - }else{ - p = 0; - xDestroy = 0; - xCallback = 0; - } - - /* Invoke sqlite3_quota_set() */ - rc = sqlite3_quota_set(zPattern, iLimit, xCallback, (void*)p, xDestroy); - - Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_file FILENAME -*/ -static int test_quota_file( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - const char *zFilename; /* File pattern to configure */ - int rc; /* Value returned by quota_file() */ - - /* Process arguments */ - if( objc!=2 ){ - Tcl_WrongNumArgs(interp, 1, objv, "FILENAME"); - return TCL_ERROR; - } - zFilename = Tcl_GetString(objv[1]); - - /* Invoke sqlite3_quota_file() */ - rc = sqlite3_quota_file(zFilename); - - Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_dump -*/ -static int test_quota_dump( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - Tcl_Obj *pResult; - Tcl_Obj *pGroupTerm; - Tcl_Obj *pFileTerm; - quotaGroup *pGroup; - quotaFile *pFile; - - pResult = Tcl_NewObj(); - quotaEnter(); - for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){ - pGroupTerm = Tcl_NewObj(); - Tcl_ListObjAppendElement(interp, pGroupTerm, - Tcl_NewStringObj(pGroup->zPattern, -1)); - Tcl_ListObjAppendElement(interp, pGroupTerm, - Tcl_NewWideIntObj(pGroup->iLimit)); - Tcl_ListObjAppendElement(interp, pGroupTerm, - Tcl_NewWideIntObj(pGroup->iSize)); - for(pFile=pGroup->pFiles; pFile; pFile=pFile->pNext){ - int i; - char zTemp[1000]; - pFileTerm = Tcl_NewObj(); - sqlite3_snprintf(sizeof(zTemp), zTemp, "%s", pFile->zFilename); - for(i=0; zTemp[i]; i++){ if( zTemp[i]=='\\' ) zTemp[i] = '/'; } - Tcl_ListObjAppendElement(interp, pFileTerm, - Tcl_NewStringObj(zTemp, -1)); - Tcl_ListObjAppendElement(interp, pFileTerm, - Tcl_NewWideIntObj(pFile->iSize)); - Tcl_ListObjAppendElement(interp, pFileTerm, - Tcl_NewWideIntObj(pFile->nRef)); - Tcl_ListObjAppendElement(interp, pFileTerm, - Tcl_NewWideIntObj(pFile->deleteOnClose)); - Tcl_ListObjAppendElement(interp, pGroupTerm, pFileTerm); - } - Tcl_ListObjAppendElement(interp, pResult, pGroupTerm); - } - quotaLeave(); - Tcl_SetObjResult(interp, pResult); - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_fopen FILENAME MODE -*/ -static int test_quota_fopen( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - const char *zFilename; /* File pattern to configure */ - const char *zMode; /* Mode string */ - quota_FILE *p; /* Open string object */ - char zReturn[50]; /* Name of pointer to return */ - - /* Process arguments */ - if( objc!=3 ){ - Tcl_WrongNumArgs(interp, 1, objv, "FILENAME MODE"); - return TCL_ERROR; - } - zFilename = Tcl_GetString(objv[1]); - zMode = Tcl_GetString(objv[2]); - p = sqlite3_quota_fopen(zFilename, zMode); - sqlite3_snprintf(sizeof(zReturn), zReturn, "%p", p); - Tcl_SetResult(interp, zReturn, TCL_VOLATILE); - return TCL_OK; -} - -/* Defined in test1.c */ -extern void *sqlite3TestTextToPtr(const char*); - -/* -** tclcmd: sqlite3_quota_fread HANDLE SIZE NELEM -*/ -static int test_quota_fread( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - quota_FILE *p; - char *zBuf; - int sz; - int nElem; - size_t got; - - if( objc!=4 ){ - Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM"); - return TCL_ERROR; - } - p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); - if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR; - if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR; - zBuf = (char*)sqlite3_malloc( sz*nElem + 1 ); - if( zBuf==0 ){ - Tcl_SetResult(interp, "out of memory", TCL_STATIC); - return TCL_ERROR; - } - got = sqlite3_quota_fread(zBuf, sz, nElem, p); - if( got<0 ) got = 0; - zBuf[got*sz] = 0; - Tcl_SetResult(interp, zBuf, TCL_VOLATILE); - sqlite3_free(zBuf); - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_fwrite HANDLE SIZE NELEM CONTENT -*/ -static int test_quota_fwrite( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - quota_FILE *p; - char *zBuf; - int sz; - int nElem; - size_t got; - - if( objc!=5 ){ - Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM CONTENT"); - return TCL_ERROR; - } - p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); - if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR; - if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR; - zBuf = Tcl_GetString(objv[4]); - got = sqlite3_quota_fwrite(zBuf, sz, nElem, p); - Tcl_SetObjResult(interp, Tcl_NewWideIntObj(got)); - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_fclose HANDLE -*/ -static int test_quota_fclose( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - quota_FILE *p; - int rc; - - if( objc!=2 ){ - Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); - return TCL_ERROR; - } - p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); - rc = sqlite3_quota_fclose(p); - Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_fflush HANDLE ?HARDSYNC? -*/ -static int test_quota_fflush( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - quota_FILE *p; - int rc; - int doSync = 0; - - if( objc!=2 && objc!=3 ){ - Tcl_WrongNumArgs(interp, 1, objv, "HANDLE ?HARDSYNC?"); - return TCL_ERROR; - } - p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); - if( objc==3 ){ - if( Tcl_GetBooleanFromObj(interp, objv[2], &doSync) ) return TCL_ERROR; - } - rc = sqlite3_quota_fflush(p, doSync); - Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_fseek HANDLE OFFSET WHENCE -*/ -static int test_quota_fseek( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - quota_FILE *p; - int ofst; - const char *zWhence; - int whence; - int rc; - - if( objc!=4 ){ - Tcl_WrongNumArgs(interp, 1, objv, "HANDLE OFFSET WHENCE"); - return TCL_ERROR; - } - p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); - if( Tcl_GetIntFromObj(interp, objv[2], &ofst) ) return TCL_ERROR; - zWhence = Tcl_GetString(objv[3]); - if( strcmp(zWhence, "SEEK_SET")==0 ){ - whence = SEEK_SET; - }else if( strcmp(zWhence, "SEEK_CUR")==0 ){ - whence = SEEK_CUR; - }else if( strcmp(zWhence, "SEEK_END")==0 ){ - whence = SEEK_END; - }else{ - Tcl_AppendResult(interp, - "WHENCE should be SEEK_SET, SEEK_CUR, or SEEK_END", (char*)0); - return TCL_ERROR; - } - rc = sqlite3_quota_fseek(p, ofst, whence); - Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_rewind HANDLE -*/ -static int test_quota_rewind( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - quota_FILE *p; - if( objc!=2 ){ - Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); - return TCL_ERROR; - } - p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); - sqlite3_quota_rewind(p); - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_ftell HANDLE -*/ -static int test_quota_ftell( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - quota_FILE *p; - sqlite3_int64 x; - if( objc!=2 ){ - Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); - return TCL_ERROR; - } - p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); - x = sqlite3_quota_ftell(p); - Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_ftruncate HANDLE SIZE -*/ -static int test_quota_ftruncate( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - quota_FILE *p; - sqlite3_int64 x; - Tcl_WideInt w; - int rc; - if( objc!=3 ){ - Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE"); - return TCL_ERROR; - } - p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); - if( Tcl_GetWideIntFromObj(interp, objv[2], &w) ) return TCL_ERROR; - x = (sqlite3_int64)w; - rc = sqlite3_quota_ftruncate(p, x); - Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_file_size HANDLE -*/ -static int test_quota_file_size( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - quota_FILE *p; - sqlite3_int64 x; - if( objc!=2 ){ - Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); - return TCL_ERROR; - } - p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); - x = sqlite3_quota_file_size(p); - Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_file_truesize HANDLE -*/ -static int test_quota_file_truesize( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - quota_FILE *p; - sqlite3_int64 x; - if( objc!=2 ){ - Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); - return TCL_ERROR; - } - p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); - x = sqlite3_quota_file_truesize(p); - Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_file_mtime HANDLE -*/ -static int test_quota_file_mtime( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - quota_FILE *p; - time_t t; - if( objc!=2 ){ - Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); - return TCL_ERROR; - } - p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); - t = 0; - sqlite3_quota_file_mtime(p, &t); - Tcl_SetObjResult(interp, Tcl_NewWideIntObj(t)); - return TCL_OK; -} - - -/* -** tclcmd: sqlite3_quota_remove FILENAME -*/ -static int test_quota_remove( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - const char *zFilename; /* File pattern to configure */ - int rc; - if( objc!=2 ){ - Tcl_WrongNumArgs(interp, 1, objv, "FILENAME"); - return TCL_ERROR; - } - zFilename = Tcl_GetString(objv[1]); - rc = sqlite3_quota_remove(zFilename); - Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_glob PATTERN TEXT -** -** Test the glob pattern matching. Return 1 if TEXT matches PATTERN -** and return 0 if it does not. -*/ -static int test_quota_glob( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - const char *zPattern; /* The glob pattern */ - const char *zText; /* Text to compare agains the pattern */ - int rc; - if( objc!=3 ){ - Tcl_WrongNumArgs(interp, 1, objv, "PATTERN TEXT"); - return TCL_ERROR; - } - zPattern = Tcl_GetString(objv[1]); - zText = Tcl_GetString(objv[2]); - rc = quotaStrglob(zPattern, zText); - Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_file_available HANDLE -** -** Return the number of bytes from the current file point to the end of -** the file. -*/ -static int test_quota_file_available( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - quota_FILE *p; - sqlite3_int64 x; - if( objc!=2 ){ - Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); - return TCL_ERROR; - } - p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); - x = sqlite3_quota_file_available(p); - Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); - return TCL_OK; -} - -/* -** tclcmd: sqlite3_quota_ferror HANDLE -** -** Return true if the file handle is in the error state. -*/ -static int test_quota_ferror( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - quota_FILE *p; - int x; - if( objc!=2 ){ - Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); - return TCL_ERROR; - } - p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); - x = sqlite3_quota_ferror(p); - Tcl_SetObjResult(interp, Tcl_NewIntObj(x)); - return TCL_OK; -} - -/* -** This routine registers the custom TCL commands defined in this -** module. This should be the only procedure visible from outside -** of this module. -*/ -int Sqlitequota_Init(Tcl_Interp *interp){ - static struct { - char *zName; - Tcl_ObjCmdProc *xProc; - } aCmd[] = { - { "sqlite3_quota_initialize", test_quota_initialize }, - { "sqlite3_quota_shutdown", test_quota_shutdown }, - { "sqlite3_quota_set", test_quota_set }, - { "sqlite3_quota_file", test_quota_file }, - { "sqlite3_quota_dump", test_quota_dump }, - { "sqlite3_quota_fopen", test_quota_fopen }, - { "sqlite3_quota_fread", test_quota_fread }, - { "sqlite3_quota_fwrite", test_quota_fwrite }, - { "sqlite3_quota_fclose", test_quota_fclose }, - { "sqlite3_quota_fflush", test_quota_fflush }, - { "sqlite3_quota_fseek", test_quota_fseek }, - { "sqlite3_quota_rewind", test_quota_rewind }, - { "sqlite3_quota_ftell", test_quota_ftell }, - { "sqlite3_quota_ftruncate", test_quota_ftruncate }, - { "sqlite3_quota_file_size", test_quota_file_size }, - { "sqlite3_quota_file_truesize", test_quota_file_truesize }, - { "sqlite3_quota_file_mtime", test_quota_file_mtime }, - { "sqlite3_quota_remove", test_quota_remove }, - { "sqlite3_quota_glob", test_quota_glob }, - { "sqlite3_quota_file_available",test_quota_file_available }, - { "sqlite3_quota_ferror", test_quota_ferror }, - }; - int i; - - for(i=0; i -#include -#include -#if SQLITE_OS_UNIX -# include -#endif -#if SQLITE_OS_WIN -# include -#endif - -/* Make this callable from C++ */ -#ifdef __cplusplus -extern "C" { -#endif - -/* -** Initialize the quota VFS shim. Use the VFS named zOrigVfsName -** as the VFS that does the actual work. Use the default if -** zOrigVfsName==NULL. -** -** The quota VFS shim is named "quota". It will become the default -** VFS if makeDefault is non-zero. -** -** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once -** during start-up. -*/ -int sqlite3_quota_initialize(const char *zOrigVfsName, int makeDefault); - -/* -** Shutdown the quota system. -** -** All SQLite database connections must be closed before calling this -** routine. -** -** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once while -** shutting down in order to free all remaining quota groups. -*/ -int sqlite3_quota_shutdown(void); - -/* -** Create or destroy a quota group. -** -** The quota group is defined by the zPattern. When calling this routine -** with a zPattern for a quota group that already exists, this routine -** merely updates the iLimit, xCallback, and pArg values for that quota -** group. If zPattern is new, then a new quota group is created. -** -** The zPattern is always compared against the full pathname of the file. -** Even if APIs are called with relative pathnames, SQLite converts the -** name to a full pathname before comparing it against zPattern. zPattern -** is a glob pattern with the following matching rules: -** -** '*' Matches any sequence of zero or more characters. -** -** '?' Matches exactly one character. -** -** [...] Matches one character from the enclosed list of -** characters. "]" can be part of the list if it is -** the first character. Within the list "X-Y" matches -** characters X or Y or any character in between the -** two. Ex: "[0-9]" matches any digit. -** -** [^...] Matches one character not in the enclosed list. -** -** / Matches either / or \. This allows glob patterns -** containing / to work on both unix and windows. -** -** Note that, unlike unix shell globbing, the directory separator "/" -** can match a wildcard. So, for example, the pattern "/abc/xyz/" "*" -** matches any files anywhere in the directory hierarchy beneath -** /abc/xyz. -** -** The glob algorithm works on bytes. Multi-byte UTF8 characters are -** matched as if each byte were a separate character. -** -** If the iLimit for a quota group is set to zero, then the quota group -** is disabled and will be deleted when the last database connection using -** the quota group is closed. -** -** Calling this routine on a zPattern that does not exist and with a -** zero iLimit is a no-op. -** -** A quota group must exist with a non-zero iLimit prior to opening -** database connections if those connections are to participate in the -** quota group. Creating a quota group does not affect database connections -** that are already open. -** -** The patterns that define the various quota groups should be distinct. -** If the same filename matches more than one quota group pattern, then -** the behavior of this package is undefined. -*/ -int sqlite3_quota_set( - const char *zPattern, /* The filename pattern */ - sqlite3_int64 iLimit, /* New quota to set for this quota group */ - void (*xCallback)( /* Callback invoked when going over quota */ - const char *zFilename, /* Name of file whose size increases */ - sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ - sqlite3_int64 iSize, /* Total size of all files in the group */ - void *pArg /* Client data */ - ), - void *pArg, /* client data passed thru to callback */ - void (*xDestroy)(void*) /* Optional destructor for pArg */ -); - -/* -** Bring the named file under quota management, assuming its name matches -** the glob pattern of some quota group. Or if it is already under -** management, update its size. If zFilename does not match the glob -** pattern of any quota group, this routine is a no-op. -*/ -int sqlite3_quota_file(const char *zFilename); - -/* -** The following object serves the same role as FILE in the standard C -** library. It represents an open connection to a file on disk for I/O. -** -** A single quota_FILE should not be used by two or more threads at the -** same time. Multiple threads can be using different quota_FILE objects -** simultaneously, but not the same quota_FILE object. -*/ -typedef struct quota_FILE quota_FILE; - -/* -** Create a new quota_FILE object used to read and/or write to the -** file zFilename. The zMode parameter is as with standard library zMode. -*/ -quota_FILE *sqlite3_quota_fopen(const char *zFilename, const char *zMode); - -/* -** Perform I/O against a quota_FILE object. When doing writes, the -** quota mechanism may result in a short write, in order to prevent -** the sum of sizes of all files from going over quota. -*/ -size_t sqlite3_quota_fread(void*, size_t, size_t, quota_FILE*); -size_t sqlite3_quota_fwrite(const void*, size_t, size_t, quota_FILE*); - -/* -** Flush all written content held in memory buffers out to disk. -** This is the equivalent of fflush() in the standard library. -** -** If the hardSync parameter is true (non-zero) then this routine -** also forces OS buffers to disk - the equivalent of fsync(). -** -** This routine return zero on success and non-zero if something goes -** wrong. -*/ -int sqlite3_quota_fflush(quota_FILE*, int hardSync); - -/* -** Close a quota_FILE object and free all associated resources. The -** file remains under quota management. -*/ -int sqlite3_quota_fclose(quota_FILE*); - -/* -** Move the read/write pointer for a quota_FILE object. Or tell the -** current location of the read/write pointer. -*/ -int sqlite3_quota_fseek(quota_FILE*, long, int); -void sqlite3_quota_rewind(quota_FILE*); -long sqlite3_quota_ftell(quota_FILE*); - -/* -** Test the error indicator for the given file. -** -** Return non-zero if the error indicator is set. -*/ -int sqlite3_quota_ferror(quota_FILE*); - -/* -** Truncate a file previously opened by sqlite3_quota_fopen(). Return -** zero on success and non-zero on any kind of failure. -** -** The newSize argument must be less than or equal to the current file size. -** Any attempt to "truncate" a file to a larger size results in -** undefined behavior. -*/ -int sqlite3_quota_ftruncate(quota_FILE*, sqlite3_int64 newSize); - -/* -** Return the last modification time of the opened file, in seconds -** since 1970. -*/ -int sqlite3_quota_file_mtime(quota_FILE*, time_t *pTime); - -/* -** Return the size of the file as it is known to the quota system. -** -** This size might be different from the true size of the file on -** disk if some outside process has modified the file without using the -** quota mechanism, or if calls to sqlite3_quota_fwrite() have occurred -** which have increased the file size, but those writes have not yet been -** forced to disk using sqlite3_quota_fflush(). -** -** Return -1 if the file is not participating in quota management. -*/ -sqlite3_int64 sqlite3_quota_file_size(quota_FILE*); - -/* -** Return the true size of the file. -** -** The true size should be the same as the size of the file as known -** to the quota system, however the sizes might be different if the -** file has been extended or truncated via some outside process or if -** pending writes have not yet been flushed to disk. -** -** Return -1 if the file does not exist or if the size of the file -** cannot be determined for some reason. -*/ -sqlite3_int64 sqlite3_quota_file_truesize(quota_FILE*); - -/* -** Determine the amount of data in bytes available for reading -** in the given file. -** -** Return -1 if the amount cannot be determined for some reason. -*/ -long sqlite3_quota_file_available(quota_FILE*); - -/* -** Delete a file from the disk, if that file is under quota management. -** Adjust quotas accordingly. -** -** If zFilename is the name of a directory that matches one of the -** quota glob patterns, then all files under quota management that -** are contained within that directory are deleted. -** -** A standard SQLite result code is returned (SQLITE_OK, SQLITE_NOMEM, etc.) -** When deleting a directory of files, if the deletion of any one -** file fails (for example due to an I/O error), then this routine -** returns immediately, with the error code, and does not try to -** delete any of the other files in the specified directory. -** -** All files are removed from quota management and deleted from disk. -** However, no attempt is made to remove empty directories. -** -** This routine is a no-op for files that are not under quota management. -*/ -int sqlite3_quota_remove(const char *zFilename); - -#ifdef __cplusplus -} /* end of the 'extern "C"' block */ -#endif -#endif /* _QUOTA_H_ */ diff --git a/dom/Makefile.in b/dom/Makefile.in index 672e0653b7c..47cd2534639 100644 --- a/dom/Makefile.in +++ b/dom/Makefile.in @@ -58,6 +58,7 @@ PARALLEL_DIRS += \ media \ messages \ power \ + quota \ settings \ sms \ mms \ diff --git a/dom/dom-config.mk b/dom/dom-config.mk index d0f46cce17f..1cf57ed945b 100644 --- a/dom/dom-config.mk +++ b/dom/dom-config.mk @@ -8,6 +8,7 @@ DOM_SRCDIRS = \ dom/encoding \ dom/file \ dom/power \ + dom/quota \ dom/media \ dom/network/src \ dom/settings \ diff --git a/dom/file/FileStreamWrappers.cpp b/dom/file/FileStreamWrappers.cpp index 2283266a0f6..c4cf10230c7 100644 --- a/dom/file/FileStreamWrappers.cpp +++ b/dom/file/FileStreamWrappers.cpp @@ -8,7 +8,6 @@ #include "nsIFileStorage.h" #include "nsISeekableStream.h" -#include "nsIStandardFileStream.h" #include "mozilla/Attributes.h" #include "FileHelper.h" @@ -246,16 +245,6 @@ FileOutputStreamWrapper::Close() nsresult rv = NS_OK; if (!mFirstTime) { - // We must flush buffers of the stream on the same thread on which we wrote - // some data. - nsCOMPtr sstream = do_QueryInterface(mFileStream); - if (sstream) { - rv = sstream->FlushBuffers(); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to flush buffers of the stream!"); - } - } - NS_ASSERTION(PR_GetCurrentThread() == mWriteThread, "Unsetting thread locals on wrong thread!"); mFileHelper->mFileStorage->UnsetThreadLocals(); diff --git a/dom/file/LockedFile.cpp b/dom/file/LockedFile.cpp index 0fca7305b5c..926df911a6b 100644 --- a/dom/file/LockedFile.cpp +++ b/dom/file/LockedFile.cpp @@ -953,10 +953,10 @@ FinishHelper::Run() } for (uint32_t index = 0; index < mParallelStreams.Length(); index++) { - nsCOMPtr ostream = + nsCOMPtr stream = do_QueryInterface(mParallelStreams[index]); - if (NS_FAILED(ostream->Close())) { + if (NS_FAILED(stream->Close())) { NS_WARNING("Failed to close stream!"); } @@ -964,9 +964,9 @@ FinishHelper::Run() } if (mStream) { - nsCOMPtr ostream = do_QueryInterface(mStream); + nsCOMPtr stream = do_QueryInterface(mStream); - if (NS_FAILED(ostream->Close())) { + if (NS_FAILED(stream->Close())) { NS_WARNING("Failed to close stream!"); } diff --git a/dom/file/nsIFileStorage.h b/dom/file/nsIFileStorage.h index 92bb6089a4b..e985f0a3ac3 100644 --- a/dom/file/nsIFileStorage.h +++ b/dom/file/nsIFileStorage.h @@ -10,14 +10,17 @@ #include "nsISupports.h" #define NS_FILESTORAGE_IID \ - {0xbba9c2ff, 0x85c9, 0x47c1, \ - { 0xaf, 0xce, 0x0a, 0x7e, 0x6f, 0x21, 0x50, 0x95 } } + {0xa0801944, 0x2f1c, 0x4203, \ + { 0x9c, 0xaa, 0xaa, 0x47, 0xe0, 0x0c, 0x67, 0x92 } } class nsIFileStorage : public nsISupports { public: NS_DECLARE_STATIC_IID_ACCESSOR(NS_FILESTORAGE_IID) + virtual const nsACString& + StorageOrigin() = 0; + virtual nsISupports* StorageId() = 0; @@ -36,20 +39,23 @@ public: NS_DEFINE_STATIC_IID_ACCESSOR(nsIFileStorage, NS_FILESTORAGE_IID) -#define NS_DECL_NSIFILESTORAGE \ - virtual nsISupports* \ - StorageId(); \ - \ - virtual bool \ - IsStorageInvalidated(); \ - \ - virtual bool \ - IsStorageShuttingDown(); \ - \ - virtual void \ - SetThreadLocals(); \ - \ - virtual void \ - UnsetThreadLocals(); +#define NS_DECL_NSIFILESTORAGE \ + virtual const nsACString& \ + StorageOrigin() MOZ_OVERRIDE; \ + \ + virtual nsISupports* \ + StorageId() MOZ_OVERRIDE; \ + \ + virtual bool \ + IsStorageInvalidated() MOZ_OVERRIDE; \ + \ + virtual bool \ + IsStorageShuttingDown() MOZ_OVERRIDE; \ + \ + virtual void \ + SetThreadLocals() MOZ_OVERRIDE; \ + \ + virtual void \ + UnsetThreadLocals() MOZ_OVERRIDE; #endif // nsIFileStorage_h__ diff --git a/dom/indexedDB/FileManager.cpp b/dom/indexedDB/FileManager.cpp index 9db56e881f1..4ed6e9e9314 100644 --- a/dom/indexedDB/FileManager.cpp +++ b/dom/indexedDB/FileManager.cpp @@ -7,8 +7,8 @@ #include "FileManager.h" #include "mozIStorageConnection.h" -#include "mozIStorageServiceQuotaManagement.h" #include "mozIStorageStatement.h" +#include "nsIInputStream.h" #include "nsISimpleEnumerator.h" #include "mozStorageCID.h" @@ -18,6 +18,8 @@ #include "IndexedDatabaseManager.h" #include "OpenDatabaseHelper.h" +#include "IndexedDatabaseInlines.h" + #define JOURNAL_DIRECTORY_NAME "journals" USING_INDEXEDDB_NAMESPACE @@ -262,13 +264,11 @@ FileManager::GetFileForId(nsIFile* aDirectory, int64_t aId) // static nsresult -FileManager::InitDirectory(mozIStorageServiceQuotaManagement* aService, - nsIFile* aDirectory, +FileManager::InitDirectory(nsIFile* aDirectory, nsIFile* aDatabaseFile, - FactoryPrivilege aPrivilege) + const nsACString& aOrigin) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(aService, "Null service!"); NS_ASSERTION(aDirectory, "Null directory!"); NS_ASSERTION(aDatabaseFile, "Null database file!"); @@ -310,8 +310,8 @@ FileManager::InitDirectory(mozIStorageServiceQuotaManagement* aService, if (hasElements) { nsCOMPtr connection; - rv = OpenDatabaseHelper::CreateDatabaseConnection( - NullString(), aDatabaseFile, aDirectory, getter_AddRefs(connection)); + rv = OpenDatabaseHelper::CreateDatabaseConnection(aDatabaseFile, + aDirectory, NullString(), aOrigin, getter_AddRefs(connection)); NS_ENSURE_SUCCESS(rv, rv); mozStorageTransaction transaction(connection, false); @@ -377,12 +377,17 @@ FileManager::InitDirectory(mozIStorageServiceQuotaManagement* aService, } } - if (aPrivilege == Chrome) { - return NS_OK; - } + return NS_OK; +} + +// static +nsresult +FileManager::GetUsage(nsIFile* aDirectory, uint64_t* aUsage) +{ + uint64_t usage = 0; nsCOMPtr entries; - rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + nsresult rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); NS_ENSURE_SUCCESS(rv, rv); bool hasMore; @@ -402,9 +407,13 @@ FileManager::InitDirectory(mozIStorageServiceQuotaManagement* aService, continue; } - rv = aService->UpdateQuotaInformationForFile(file); + int64_t fileSize; + rv = file->GetFileSize(&fileSize); NS_ENSURE_SUCCESS(rv, rv); + + IncrementUsage(&usage, uint64_t(fileSize)); } + *aUsage = usage; return NS_OK; } diff --git a/dom/indexedDB/FileManager.h b/dom/indexedDB/FileManager.h index 2c72d0a8527..370d4a8e70c 100644 --- a/dom/indexedDB/FileManager.h +++ b/dom/indexedDB/FileManager.h @@ -24,10 +24,10 @@ class FileManager friend class FileInfo; public: - FileManager(const nsACString& aOrigin, + FileManager(const nsACString& aOrigin, FactoryPrivilege aPrivilege, const nsAString& aDatabaseName) - : mOrigin(aOrigin), mDatabaseName(aDatabaseName), mLastFileId(0), - mInvalidated(false) + : mOrigin(aOrigin), mPrivilege(aPrivilege), mDatabaseName(aDatabaseName), + mLastFileId(0), mInvalidated(false) { } ~FileManager() @@ -40,6 +40,11 @@ public: return mOrigin; } + const FactoryPrivilege& Privilege() const + { + return mPrivilege; + } + const nsAString& DatabaseName() const { return mDatabaseName; @@ -68,12 +73,15 @@ public: static already_AddRefed GetFileForId(nsIFile* aDirectory, int64_t aId); - static nsresult InitDirectory(mozIStorageServiceQuotaManagement* aService, - nsIFile* aDirectory, nsIFile* aDatabaseFile, - FactoryPrivilege aPrivilege); + static nsresult InitDirectory(nsIFile* aDirectory, + nsIFile* aDatabaseFile, + const nsACString& aOrigin); + + static nsresult GetUsage(nsIFile* aDirectory, uint64_t* aUsage); private: nsCString mOrigin; + FactoryPrivilege mPrivilege; nsString mDatabaseName; nsString mDirectoryPath; diff --git a/dom/indexedDB/FileStream.cpp b/dom/indexedDB/FileStream.cpp deleted file mode 100644 index dddf5d5276a..00000000000 --- a/dom/indexedDB/FileStream.cpp +++ /dev/null @@ -1,321 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "FileStream.h" - -#include "nsIFile.h" - -#include "nsThreadUtils.h" -#include "test_quota.h" - -USING_INDEXEDDB_NAMESPACE - -NS_IMPL_THREADSAFE_ADDREF(FileStream) -NS_IMPL_THREADSAFE_RELEASE(FileStream) - -NS_INTERFACE_MAP_BEGIN(FileStream) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStandardFileStream) - NS_INTERFACE_MAP_ENTRY(nsISeekableStream) - NS_INTERFACE_MAP_ENTRY(nsIInputStream) - NS_INTERFACE_MAP_ENTRY(nsIOutputStream) - NS_INTERFACE_MAP_ENTRY(nsIStandardFileStream) - NS_INTERFACE_MAP_ENTRY(nsIFileMetadata) -NS_INTERFACE_MAP_END - -NS_IMETHODIMP -FileStream::Seek(int32_t aWhence, int64_t aOffset) -{ - // TODO: Add support for 64 bit file sizes, bug 752431 - NS_ENSURE_TRUE(aOffset <= INT32_MAX, NS_ERROR_INVALID_ARG); - - nsresult rv = DoPendingOpen(); - NS_ENSURE_SUCCESS(rv, rv); - - if (!mQuotaFile) { - return NS_BASE_STREAM_CLOSED; - } - - int whence; - switch (aWhence) { - case nsISeekableStream::NS_SEEK_SET: - whence = SEEK_SET; - break; - case nsISeekableStream::NS_SEEK_CUR: - whence = SEEK_CUR; - break; - case nsISeekableStream::NS_SEEK_END: - whence = SEEK_END; - break; - default: - return NS_ERROR_INVALID_ARG; - } - - NS_ASSERTION(!NS_IsMainThread(), "Performing sync IO on the main thread!"); - - int rc = sqlite3_quota_fseek(mQuotaFile, aOffset, whence); - NS_ENSURE_TRUE(rc == 0, NS_BASE_STREAM_OSERROR); - - return NS_OK; -} - -NS_IMETHODIMP -FileStream::Tell(int64_t* aResult) -{ - nsresult rv = DoPendingOpen(); - NS_ENSURE_SUCCESS(rv, rv); - - if (!mQuotaFile) { - return NS_BASE_STREAM_CLOSED; - } - - NS_ASSERTION(!NS_IsMainThread(), "Performing sync IO on the main thread!"); - - long rc = sqlite3_quota_ftell(mQuotaFile); - NS_ENSURE_TRUE(rc >= 0, NS_BASE_STREAM_OSERROR); - - *aResult = rc; - return NS_OK; -} - -NS_IMETHODIMP -FileStream::SetEOF() -{ - int64_t pos; - nsresult rv = Tell(&pos); - NS_ENSURE_SUCCESS(rv, rv); - - NS_ASSERTION(!NS_IsMainThread(), "Performing sync IO on the main thread!"); - - int rc = sqlite3_quota_ftruncate(mQuotaFile, pos); - NS_ENSURE_TRUE(rc == 0, NS_BASE_STREAM_OSERROR); - - return NS_OK; -} - - -NS_IMETHODIMP -FileStream::Close() -{ - CleanUpOpen(); - - if (mQuotaFile) { - NS_ASSERTION(!NS_IsMainThread(), "Performing sync IO on the main thread!"); - - int rc = sqlite3_quota_fclose(mQuotaFile); - mQuotaFile = nullptr; - - NS_ENSURE_TRUE(rc == 0, NS_BASE_STREAM_OSERROR); - } - - return NS_OK; -} - -NS_IMETHODIMP -FileStream::Available(uint64_t* aResult) -{ - nsresult rv = DoPendingOpen(); - NS_ENSURE_SUCCESS(rv, rv); - - if (!mQuotaFile) { - return NS_BASE_STREAM_CLOSED; - } - - NS_ASSERTION(!NS_IsMainThread(), "Performing sync IO on the main thread!"); - - long rc = sqlite3_quota_file_available(mQuotaFile); - NS_ENSURE_TRUE(rc >= 0, NS_BASE_STREAM_OSERROR); - - *aResult = rc; - return NS_OK; -} - -NS_IMETHODIMP -FileStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult) -{ - nsresult rv = DoPendingOpen(); - NS_ENSURE_SUCCESS(rv, rv); - - if (!mQuotaFile) { - return NS_BASE_STREAM_CLOSED; - } - - NS_ASSERTION(!NS_IsMainThread(), "Performing sync IO on the main thread!"); - - size_t bytesRead = sqlite3_quota_fread(aBuf, 1, aCount, mQuotaFile); - if (bytesRead < aCount && sqlite3_quota_ferror(mQuotaFile)) { - return NS_BASE_STREAM_OSERROR; - } - - *aResult = bytesRead; - return NS_OK; -} - -NS_IMETHODIMP -FileStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, - uint32_t aCount, uint32_t* aResult) -{ - NS_NOTREACHED("Don't call me!"); - return NS_ERROR_NOT_IMPLEMENTED; -} - -NS_IMETHODIMP -FileStream::IsNonBlocking(bool *aNonBlocking) -{ - *aNonBlocking = false; - return NS_OK; -} - -NS_IMETHODIMP -FileStream::Write(const char* aBuf, uint32_t aCount, uint32_t *aResult) -{ - nsresult rv = DoPendingOpen(); - NS_ENSURE_SUCCESS(rv, rv); - - if (!mQuotaFile) { - return NS_BASE_STREAM_CLOSED; - } - - NS_ASSERTION(!NS_IsMainThread(), "Performing sync IO on the main thread!"); - - size_t bytesWritten = sqlite3_quota_fwrite(aBuf, 1, aCount, mQuotaFile); - if (bytesWritten < aCount) { - return NS_BASE_STREAM_OSERROR; - } - - *aResult = bytesWritten; - return NS_OK; -} - -NS_IMETHODIMP -FileStream::Flush() -{ - nsresult rv = DoPendingOpen(); - NS_ENSURE_SUCCESS(rv, rv); - - if (!mQuotaFile) { - return NS_BASE_STREAM_CLOSED; - } - - NS_ASSERTION(!NS_IsMainThread(), "Performing sync IO on the main thread!"); - - int rc = sqlite3_quota_fflush(mQuotaFile, 1); - NS_ENSURE_TRUE(rc == 0, NS_BASE_STREAM_OSERROR); - - return NS_OK; -} - -NS_IMETHODIMP -FileStream::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval) -{ - return NS_ERROR_NOT_IMPLEMENTED; -} - -NS_IMETHODIMP -FileStream::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval) -{ - NS_NOTREACHED("Don't call me!"); - return NS_ERROR_NOT_IMPLEMENTED; -} - -NS_IMETHODIMP -FileStream::Init(nsIFile* aFile, const nsAString& aMode, int32_t aFlags) -{ - NS_ASSERTION(!mQuotaFile && !mDeferredOpen, "Already initialized!"); - - nsresult rv = aFile->GetPath(mFilePath); - NS_ENSURE_SUCCESS(rv, rv); - - mMode = aMode; - mFlags = aFlags; - - if (mFlags & nsIStandardFileStream::FLAGS_DEFER_OPEN) { - mDeferredOpen = true; - return NS_OK; - } - - return DoOpen(); -} - -NS_IMETHODIMP -FileStream::GetSize(int64_t* _retval) -{ - nsresult rv = DoPendingOpen(); - NS_ENSURE_SUCCESS(rv, rv); - - if (!mQuotaFile) { - return NS_BASE_STREAM_CLOSED; - } - - NS_ASSERTION(!NS_IsMainThread(), "Performing sync IO on the main thread!"); - - // TODO: Use sqlite3_quota_file_size() here, bug 760783 - int64_t rc = sqlite3_quota_file_truesize(mQuotaFile); - - NS_ASSERTION(rc >= 0, "The file is not under quota management!"); - - *_retval = rc; - return NS_OK; -} - -NS_IMETHODIMP -FileStream::GetLastModified(int64_t* _retval) -{ - nsresult rv = DoPendingOpen(); - NS_ENSURE_SUCCESS(rv, rv); - - if (!mQuotaFile) { - return NS_BASE_STREAM_CLOSED; - } - - NS_ASSERTION(!NS_IsMainThread(), "Performing sync IO on the main thread!"); - - time_t mtime; - int rc = sqlite3_quota_file_mtime(mQuotaFile, &mtime); - NS_ENSURE_TRUE(rc == 0, NS_BASE_STREAM_OSERROR); - - *_retval = mtime * PR_MSEC_PER_SEC; - return NS_OK; -} - -NS_IMETHODIMP -FileStream::FlushBuffers() -{ - nsresult rv = DoPendingOpen(); - NS_ENSURE_SUCCESS(rv, rv); - - if (!mQuotaFile) { - return NS_BASE_STREAM_CLOSED; - } - - NS_ASSERTION(!NS_IsMainThread(), "Performing sync IO on the main thread!"); - - int rc = sqlite3_quota_fflush(mQuotaFile, 0); - NS_ENSURE_TRUE(rc == 0, NS_BASE_STREAM_OSERROR); - - return NS_OK; -} - -nsresult -FileStream::DoOpen() -{ - NS_ASSERTION(!mFilePath.IsEmpty(), "Must have a file path"); - - NS_ASSERTION(!NS_IsMainThread(), "Performing sync IO on the main thread!"); - - quota_FILE* quotaFile = - sqlite3_quota_fopen(NS_ConvertUTF16toUTF8(mFilePath).get(), - NS_ConvertUTF16toUTF8(mMode).get()); - - CleanUpOpen(); - - if (!quotaFile) { - return NS_BASE_STREAM_OSERROR; - } - - mQuotaFile = quotaFile; - - return NS_OK; -} diff --git a/dom/indexedDB/FileStream.h b/dom/indexedDB/FileStream.h deleted file mode 100644 index 09648b117d1..00000000000 --- a/dom/indexedDB/FileStream.h +++ /dev/null @@ -1,140 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef mozilla_dom_indexeddb_filestream_h__ -#define mozilla_dom_indexeddb_filestream_h__ - -#include "IndexedDatabase.h" - -#include "nsIFileStreams.h" -#include "nsIInputStream.h" -#include "nsIOutputStream.h" -#include "nsISeekableStream.h" -#include "nsIStandardFileStream.h" - -class nsIFile; -struct quota_FILE; - -BEGIN_INDEXEDDB_NAMESPACE - -class FileStream : public nsISeekableStream, - public nsIInputStream, - public nsIOutputStream, - public nsIStandardFileStream, - public nsIFileMetadata -{ -public: - FileStream() - : mFlags(0), - mDeferredOpen(false), - mQuotaFile(nullptr) - { } - - virtual ~FileStream() - { - Close(); - } - - NS_DECL_ISUPPORTS - NS_DECL_NSISEEKABLESTREAM - NS_DECL_NSISTANDARDFILESTREAM - NS_DECL_NSIFILEMETADATA - - // nsIInputStream - NS_IMETHOD - Close(); - - NS_IMETHOD - Available(uint64_t* _retval); - - NS_IMETHOD - Read(char* aBuf, uint32_t aCount, uint32_t* _retval); - - NS_IMETHOD - ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, - uint32_t* _retval); - - NS_IMETHOD - IsNonBlocking(bool* _retval); - - // nsIOutputStream - - // Close() already declared - - NS_IMETHOD - Flush(); - - NS_IMETHOD - Write(const char* aBuf, uint32_t aCount, uint32_t* _retval); - - NS_IMETHOD - WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, uint32_t* _retval); - - NS_IMETHOD - WriteSegments(nsReadSegmentFun aReader, void* aClosure, uint32_t aCount, - uint32_t* _retval); - - // IsNonBlocking() already declared - -protected: - /** - * Cleans up data prepared in Init. - */ - void - CleanUpOpen() - { - mFilePath.Truncate(); - mDeferredOpen = false; - } - - /** - * Open the file. This is called either from Init - * or from DoPendingOpen (if FLAGS_DEFER_OPEN is used when initializing this - * stream). The default behavior of DoOpen is to open the file and save the - * file descriptor. - */ - virtual nsresult - DoOpen(); - - /** - * If there is a pending open, do it now. It's important for this to be - * inlined since we do it in almost every stream API call. - */ - nsresult - DoPendingOpen() - { - if (!mDeferredOpen) { - return NS_OK; - } - - return DoOpen(); - } - - /** - * Data we need to do an open. - */ - nsString mFilePath; - nsString mMode; - - /** - * Flags describing our behavior. See the IDL file for possible values. - */ - int32_t mFlags; - - /** - * Whether we have a pending open (see FLAGS_DEFER_OPEN in the IDL file). - */ - bool mDeferredOpen; - - /** - * File descriptor for opened file. - */ - quota_FILE* mQuotaFile; -}; - -END_INDEXEDDB_NAMESPACE - -#endif // mozilla_dom_indexeddb_filestream_h__ diff --git a/dom/indexedDB/IDBDatabase.cpp b/dom/indexedDB/IDBDatabase.cpp index 63500b0e6ea..8842dafe07a 100644 --- a/dom/indexedDB/IDBDatabase.cpp +++ b/dom/indexedDB/IDBDatabase.cpp @@ -779,6 +779,12 @@ IDBDatabase::Close() return NS_OK; } +const nsACString& +IDBDatabase::StorageOrigin() +{ + return Origin(); +} + nsISupports* IDBDatabase::StorageId() { diff --git a/dom/indexedDB/IDBFactory.cpp b/dom/indexedDB/IDBFactory.cpp index 1007df1616d..c1f573e6b2a 100644 --- a/dom/indexedDB/IDBFactory.cpp +++ b/dom/indexedDB/IDBFactory.cpp @@ -252,9 +252,27 @@ IDBFactory::Create(ContentParent* aContentParent, return NS_OK; } +// static +already_AddRefed +IDBFactory::GetDatabaseFileURL(nsIFile* aDatabaseFile, const nsACString& aOrigin) +{ + nsCOMPtr uri; + nsresult rv = NS_NewFileURI(getter_AddRefs(uri), aDatabaseFile); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr fileUrl = do_QueryInterface(uri); + NS_ASSERTION(fileUrl, "This should always succeed!"); + + rv = fileUrl->SetQuery(NS_LITERAL_CSTRING("origin=") + aOrigin); + NS_ENSURE_SUCCESS(rv, nullptr); + + return fileUrl.forget(); +} + // static already_AddRefed -IDBFactory::GetConnection(const nsAString& aDatabaseFilePath) +IDBFactory::GetConnection(const nsAString& aDatabaseFilePath, + const nsACString& aOrigin) { NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(StringEndsWith(aDatabaseFilePath, NS_LITERAL_STRING(".sqlite")), @@ -271,13 +289,15 @@ IDBFactory::GetConnection(const nsAString& aDatabaseFilePath) NS_ENSURE_SUCCESS(rv, nullptr); NS_ENSURE_TRUE(exists, nullptr); - nsCOMPtr ss = + nsCOMPtr dbFileUrl = GetDatabaseFileURL(dbFile, aOrigin); + NS_ENSURE_TRUE(dbFileUrl, nullptr); + + nsCOMPtr ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); NS_ENSURE_TRUE(ss, nullptr); nsCOMPtr connection; - rv = ss->OpenDatabaseWithVFS(dbFile, NS_LITERAL_CSTRING("quota"), - getter_AddRefs(connection)); + rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(connection)); NS_ENSURE_SUCCESS(rv, nullptr); // Turn on foreign key constraints and recursive triggers. diff --git a/dom/indexedDB/IDBFactory.h b/dom/indexedDB/IDBFactory.h index d5461f71a74..49dad424fbd 100644 --- a/dom/indexedDB/IDBFactory.h +++ b/dom/indexedDB/IDBFactory.h @@ -15,6 +15,8 @@ #include "nsCycleCollectionParticipant.h" class nsIAtom; +class nsIFile; +class nsIFileURL; class nsPIDOMWindow; namespace mozilla { @@ -75,8 +77,12 @@ public: static nsresult Create(ContentParent* aContentParent, IDBFactory** aFactory); + static already_AddRefed + GetDatabaseFileURL(nsIFile* aDatabaseFile, const nsACString& aOrigin); + static already_AddRefed - GetConnection(const nsAString& aDatabaseFilePath); + GetConnection(const nsAString& aDatabaseFilePath, + const nsACString& aOrigin); static nsresult LoadDatabaseInformation(mozIStorageConnection* aConnection, diff --git a/dom/indexedDB/IDBFileHandle.cpp b/dom/indexedDB/IDBFileHandle.cpp index e0340ff10e2..f71fd56a917 100644 --- a/dom/indexedDB/IDBFileHandle.cpp +++ b/dom/indexedDB/IDBFileHandle.cpp @@ -6,15 +6,14 @@ #include "IDBFileHandle.h" -#include "nsIStandardFileStream.h" - #include "mozilla/dom/file/File.h" +#include "mozilla/dom/quota/FileStreams.h" #include "nsDOMClassInfoID.h" -#include "FileStream.h" #include "IDBDatabase.h" USING_INDEXEDDB_NAMESPACE +USING_QUOTA_NAMESPACE namespace { @@ -68,22 +67,22 @@ IDBFileHandle::Create(IDBDatabase* aDatabase, already_AddRefed IDBFileHandle::CreateStream(nsIFile* aFile, bool aReadOnly) { - nsRefPtr stream = new FileStream(); + const nsACString& origin = mFileStorage->StorageOrigin(); + + nsCOMPtr result; - nsString streamMode; if (aReadOnly) { - streamMode.AssignLiteral("rb"); + nsRefPtr stream = FileInputStream::Create( + origin, aFile, -1, -1, nsIFileInputStream::DEFER_OPEN); + result = NS_ISUPPORTS_CAST(nsIFileInputStream*, stream); } else { - streamMode.AssignLiteral("r+b"); + nsRefPtr stream = FileStream::Create( + origin, aFile, -1, -1, nsIFileStream::DEFER_OPEN); + result = NS_ISUPPORTS_CAST(nsIFileStream*, stream); } + NS_ENSURE_TRUE(result, nullptr); - nsresult rv = stream->Init(aFile, streamMode, - nsIStandardFileStream::FLAGS_DEFER_OPEN); - NS_ENSURE_SUCCESS(rv, nullptr); - - nsCOMPtr result = - NS_ISUPPORTS_CAST(nsIStandardFileStream*, stream); return result.forget(); } diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index 746d4731837..1f16d2629bf 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -17,6 +17,7 @@ #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/StructuredCloneTags.h" #include "mozilla/dom/ipc/Blob.h" +#include "mozilla/dom/quota/FileStreams.h" #include "mozilla/storage.h" #include "nsContentUtils.h" #include "nsDOMClassInfo.h" @@ -27,10 +28,8 @@ #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" #include "snappy/snappy.h" -#include "test_quota.h" #include "AsyncConnectionHelper.h" -#include "FileStream.h" #include "IDBCursor.h" #include "IDBEvents.h" #include "IDBFileHandle.h" @@ -51,6 +50,7 @@ USING_INDEXEDDB_NAMESPACE using namespace mozilla::dom; using namespace mozilla::dom::indexedDB::ipc; +using mozilla::dom::quota::FileOutputStream; namespace { @@ -2734,9 +2734,9 @@ AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection) nativeFile = fileManager->GetFileForId(directory, id); NS_ENSURE_TRUE(nativeFile, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - nsRefPtr outputStream = new FileStream(); - rv = outputStream->Init(nativeFile, NS_LITERAL_STRING("wb"), 0); - NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + nsRefPtr outputStream = FileOutputStream::Create( + mObjectStore->Transaction()->Database()->Origin(), nativeFile); + NS_ENSURE_TRUE(outputStream, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = CopyData(inputStream, outputStream); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); diff --git a/dom/indexedDB/IDBTransaction.cpp b/dom/indexedDB/IDBTransaction.cpp index fcef7ccb2cd..a5345e2adfb 100644 --- a/dom/indexedDB/IDBTransaction.cpp +++ b/dom/indexedDB/IDBTransaction.cpp @@ -352,7 +352,8 @@ IDBTransaction::GetOrCreateConnection(mozIStorageConnection** aResult) if (!mConnection) { nsCOMPtr connection = - IDBFactory::GetConnection(mDatabase->FilePath()); + IDBFactory::GetConnection(mDatabase->FilePath(), + mDatabase->Origin()); NS_ENSURE_TRUE(connection, NS_ERROR_FAILURE); nsresult rv; diff --git a/dom/indexedDB/IndexedDatabaseInlines.h b/dom/indexedDB/IndexedDatabaseInlines.h index 62e65d6c5a9..f27d60c3479 100644 --- a/dom/indexedDB/IndexedDatabaseInlines.h +++ b/dom/indexedDB/IndexedDatabaseInlines.h @@ -79,4 +79,17 @@ AppendConditionClause(const nsACString& aColumnName, aResult += NS_LITERAL_CSTRING(" :") + aArgName; } +inline void +IncrementUsage(uint64_t* aUsage, uint64_t aDelta) +{ + // Watch for overflow! + if ((UINT64_MAX - *aUsage) < aDelta) { + NS_WARNING("Usage exceeds the maximum!"); + *aUsage = UINT64_MAX; + } + else { + *aUsage += aDelta; + } +} + END_INDEXEDDB_NAMESPACE diff --git a/dom/indexedDB/IndexedDatabaseManager.cpp b/dom/indexedDB/IndexedDatabaseManager.cpp index e4ad647b169..88f09da00bd 100644 --- a/dom/indexedDB/IndexedDatabaseManager.cpp +++ b/dom/indexedDB/IndexedDatabaseManager.cpp @@ -22,6 +22,7 @@ #include "nsITimer.h" #include "mozilla/dom/file/FileService.h" +#include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/dom/TabContext.h" #include "mozilla/LazyIdleThread.h" #include "mozilla/Preferences.h" @@ -36,7 +37,6 @@ #include "nsThreadUtils.h" #include "nsXPCOM.h" #include "nsXPCOMPrivate.h" -#include "test_quota.h" #include "xpcpublic.h" #include "AsyncConnectionHelper.h" @@ -48,6 +48,8 @@ #include "OpenDatabaseHelper.h" #include "TransactionThreadPool.h" +#include "IndexedDatabaseInlines.h" + // The amount of time, in milliseconds, that our IO thread will stay alive // after the last event it processes. #define DEFAULT_THREAD_TIMEOUT_MS 30000 @@ -70,6 +72,7 @@ using namespace mozilla::services; using namespace mozilla::dom; using mozilla::Preferences; using mozilla::dom::file::FileService; +using mozilla::dom::quota::QuotaManager; static NS_DEFINE_CID(kDOMSOF_CID, NS_DOM_SCRIPT_OBJECT_FACTORY_CID); @@ -103,29 +106,6 @@ GetDatabaseBaseFilename(const nsAString& aFilename, return true; } -class QuotaCallback MOZ_FINAL : public mozIStorageQuotaCallback -{ -public: - NS_DECL_ISUPPORTS - - NS_IMETHOD - QuotaExceeded(const nsACString& aFilename, - int64_t aCurrentSizeLimit, - int64_t aCurrentTotalSize, - nsISupports* aUserData, - int64_t* _retval) - { - if (IndexedDatabaseManager::QuotaIsLifted()) { - *_retval = 0; - return NS_OK; - } - - return NS_ERROR_FAILURE; - } -}; - -NS_IMPL_THREADSAFE_ISUPPORTS1(QuotaCallback, mozIStorageQuotaCallback) - // Adds all databases in the hash to the given array. template PLDHashOperator @@ -440,8 +420,8 @@ IndexedDatabaseManager::GetOrCreate() NS_LITERAL_CSTRING("IndexedDB I/O"), LazyIdleThread::ManualShutdown); - // We need one quota callback object to hand to SQLite. - instance->mQuotaCallbackSingleton = new QuotaCallback(); + // Make sure that the quota manager is up. + NS_ENSURE_TRUE(QuotaManager::GetOrCreate(), nullptr); // Make a timer here to avoid potential failures later. We don't actually // initialize the timer until shutdown. @@ -996,37 +976,15 @@ IndexedDatabaseManager::EnsureOriginIsInitialized(const nsACString& aOrigin, return NS_OK; } - // First figure out the filename pattern we'll use. - nsCOMPtr patternFile; - rv = directory->Clone(getter_AddRefs(patternFile)); - NS_ENSURE_SUCCESS(rv, rv); - - rv = patternFile->Append(NS_LITERAL_STRING("*")); - NS_ENSURE_SUCCESS(rv, rv); - - nsString pattern; - rv = patternFile->GetPath(pattern); - NS_ENSURE_SUCCESS(rv, rv); - - // Now tell SQLite to start tracking this pattern for content. - nsCOMPtr ss = - do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); - NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE); - - if (aPrivilege != Chrome) { - rv = ss->SetQuotaForFilenamePattern(NS_ConvertUTF16toUTF8(pattern), - GetIndexedDBQuotaMB() * 1024 * 1024, - mQuotaCallbackSingleton, nullptr); - NS_ENSURE_SUCCESS(rv, rv); - } - // We need to see if there are any files in the directory already. If they // are database files then we need to cleanup stored files (if it's needed) - // and also tell SQLite about all of them. + // and also initialize the quota. nsAutoTArray subdirsToProcess; nsAutoTArray , 20> unknownFiles; + uint64_t usage = 0; + nsTHashtable validSubdirs; validSubdirs.Init(20); @@ -1068,20 +1026,28 @@ IndexedDatabaseManager::EnsureOriginIsInitialized(const nsACString& aOrigin, continue; } - nsCOMPtr fileManagerDirectory; - rv = directory->Clone(getter_AddRefs(fileManagerDirectory)); + nsCOMPtr fmDirectory; + rv = directory->Clone(getter_AddRefs(fmDirectory)); NS_ENSURE_SUCCESS(rv, rv); - rv = fileManagerDirectory->Append(dbBaseFilename); + rv = fmDirectory->Append(dbBaseFilename); NS_ENSURE_SUCCESS(rv, rv); - rv = FileManager::InitDirectory(ss, fileManagerDirectory, file, - aPrivilege); + rv = FileManager::InitDirectory(fmDirectory, file, aOrigin); NS_ENSURE_SUCCESS(rv, rv); if (aPrivilege != Chrome) { - rv = ss->UpdateQuotaInformationForFile(file); + uint64_t fileUsage; + rv = FileManager::GetUsage(fmDirectory, &fileUsage); NS_ENSURE_SUCCESS(rv, rv); + + IncrementUsage(&usage, fileUsage); + + int64_t fileSize; + rv = file->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(rv, rv); + + IncrementUsage(&usage, uint64_t(fileSize)); } validSubdirs.PutEntry(dbBaseFilename); @@ -1117,12 +1083,39 @@ IndexedDatabaseManager::EnsureOriginIsInitialized(const nsACString& aOrigin, } } + if (aPrivilege != Chrome) { + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + quotaManager->InitQuotaForOrigin(aOrigin, GetIndexedDBQuotaMB(), usage); + } + mInitializedOrigins.AppendElement(aOrigin); NS_ADDREF(*aDirectory = directory); return NS_OK; } +void +IndexedDatabaseManager::UninitializeOriginsByPattern( + const nsACString& aPattern) +{ +#ifdef DEBUG + { + bool correctThread; + NS_ASSERTION(NS_SUCCEEDED(mIOThread->IsOnCurrentThread(&correctThread)) && + correctThread, + "Running on the wrong thread!"); + } +#endif + + for (int32_t i = mInitializedOrigins.Length() - 1; i >= 0; i--) { + if (PatternMatchesOrigin(aPattern, mInitializedOrigins[i])) { + mInitializedOrigins.RemoveElementAt(i); + } + } +} + bool IndexedDatabaseManager::QuotaIsLiftedInternal() { @@ -1250,16 +1243,14 @@ IndexedDatabaseManager::GetFileManager(const nsACString& aOrigin, } void -IndexedDatabaseManager::AddFileManager(const nsACString& aOrigin, - const nsAString& aDatabaseName, - FileManager* aFileManager) +IndexedDatabaseManager::AddFileManager(FileManager* aFileManager) { NS_ASSERTION(aFileManager, "Null file manager!"); nsTArray >* array; - if (!mFileManagers.Get(aOrigin, &array)) { + if (!mFileManagers.Get(aFileManager->Origin(), &array)) { array = new nsTArray >(); - mFileManagers.Put(aOrigin, array); + mFileManagers.Put(aFileManager->Origin(), array); } array->AppendElement(aFileManager); @@ -1783,6 +1774,13 @@ OriginClearRunnable::DeleteFiles(IndexedDatabaseManager* aManager) // correctly... NS_ERROR("Failed to remove directory!"); } + + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + quotaManager->RemoveQuotaForPattern(mOriginOrPattern); + + aManager->UninitializeOriginsByPattern(mOriginOrPattern); } } @@ -1880,19 +1878,6 @@ IndexedDatabaseManager::AsyncUsageRunnable::Cancel() } } -inline void -IncrementUsage(uint64_t* aUsage, uint64_t aDelta) -{ - // Watch for overflow! - if ((INT64_MAX - *aUsage) <= aDelta) { - NS_WARNING("Database sizes exceed max we can report!"); - *aUsage = INT64_MAX; - } - else { - *aUsage += aDelta; - } -} - nsresult IndexedDatabaseManager::AsyncUsageRunnable::TakeShortcut() { @@ -2295,25 +2280,22 @@ IndexedDatabaseManager::AsyncDeleteFileRunnable::Run() nsCOMPtr file = mFileManager->GetFileForId(directory, mFileId); NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); - nsString filePath; - nsresult rv = file->GetPath(filePath); - NS_ENSURE_SUCCESS(rv, rv); + nsresult rv; + int64_t fileSize; - int rc = sqlite3_quota_remove(NS_ConvertUTF16toUTF8(filePath).get()); - if (rc != SQLITE_OK) { - NS_WARNING("Failed to delete stored file!"); - return NS_ERROR_FAILURE; + if (mFileManager->Privilege() != Chrome) { + rv = file->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); } - // sqlite3_quota_remove won't actually remove anything if we're not tracking - // the quota here. Manually remove the file if it exists. - bool exists; - rv = file->Exists(&exists); - NS_ENSURE_SUCCESS(rv, rv); + rv = file->Remove(false); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); - if (exists) { - rv = file->Remove(false); - NS_ENSURE_SUCCESS(rv, rv); + if (mFileManager->Privilege() != Chrome) { + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + quotaManager->DecreaseUsageForOrigin(mFileManager->Origin(), fileSize); } directory = mFileManager->GetJournalDirectory(); diff --git a/dom/indexedDB/IndexedDatabaseManager.h b/dom/indexedDB/IndexedDatabaseManager.h index f9fbbf2a00f..1ea5425f447 100644 --- a/dom/indexedDB/IndexedDatabaseManager.h +++ b/dom/indexedDB/IndexedDatabaseManager.h @@ -23,7 +23,6 @@ #define INDEXEDDB_MANAGER_CONTRACTID "@mozilla.org/dom/indexeddb/manager;1" -class mozIStorageQuotaCallback; class nsIAtom; class nsIFile; class nsITimer; @@ -134,6 +133,8 @@ public: FactoryPrivilege aPrivilege, nsIFile** aDirectory); + void UninitializeOriginsByPattern(const nsACString& aPattern); + // Determine if the quota is lifted for the Window the current thread is // using. static inline bool @@ -172,9 +173,7 @@ public: const nsAString& aDatabaseName); void - AddFileManager(const nsACString& aOrigin, - const nsAString& aDatabaseName, - FileManager* aFileManager); + AddFileManager(FileManager* aFileManager); void InvalidateFileManagersForPattern(const nsACString& aPattern); @@ -502,10 +501,6 @@ private: // A timer that gets activated at shutdown to ensure we close all databases. nsCOMPtr mShutdownTimer; - // A single threadsafe instance of our quota callback. Created on the main - // thread during GetOrCreate(). - nsCOMPtr mQuotaCallbackSingleton; - // A list of all successfully initialized origins. This list isn't protected // by any mutex but it is only ever touched on the IO thread. nsTArray mInitializedOrigins; diff --git a/dom/indexedDB/Makefile.in b/dom/indexedDB/Makefile.in index fef085894e3..09d48534a34 100644 --- a/dom/indexedDB/Makefile.in +++ b/dom/indexedDB/Makefile.in @@ -25,7 +25,6 @@ CPPSRCS = \ DatabaseInfo.cpp \ FileInfo.cpp \ FileManager.cpp \ - FileStream.cpp \ IDBCursor.cpp \ IDBDatabase.cpp \ IDBEvents.cpp \ @@ -93,7 +92,6 @@ XPIDLSRCS = \ nsIIDBVersionChangeEvent.idl \ nsIIDBOpenDBRequest.idl \ nsIIndexedDatabaseManager.idl \ - nsIStandardFileStream.idl \ $(NULL) DIRS += ipc diff --git a/dom/indexedDB/OpenDatabaseHelper.cpp b/dom/indexedDB/OpenDatabaseHelper.cpp index e71cad4095f..4cd7f61397f 100644 --- a/dom/indexedDB/OpenDatabaseHelper.cpp +++ b/dom/indexedDB/OpenDatabaseHelper.cpp @@ -8,11 +8,12 @@ #include "nsIFile.h" +#include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/storage.h" #include "nsEscape.h" +#include "nsNetUtil.h" #include "nsThreadUtils.h" #include "snappy/snappy.h" -#include "test_quota.h" #include "nsIBFCacheEntry.h" #include "IDBEvents.h" @@ -21,6 +22,7 @@ using namespace mozilla; USING_INDEXEDDB_NAMESPACE +USING_QUOTA_NAMESPACE namespace { @@ -1632,15 +1634,15 @@ OpenDatabaseHelper::DoDatabaseWork() rv = dbFile->GetPath(mDatabaseFilePath); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - nsCOMPtr fileManagerDirectory; - rv = dbDirectory->Clone(getter_AddRefs(fileManagerDirectory)); + nsCOMPtr fmDirectory; + rv = dbDirectory->Clone(getter_AddRefs(fmDirectory)); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - rv = fileManagerDirectory->Append(filename); + rv = fmDirectory->Append(filename); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsCOMPtr connection; - rv = CreateDatabaseConnection(mName, dbFile, fileManagerDirectory, + rv = CreateDatabaseConnection(dbFile, fmDirectory, mName, mASCIIOrigin, getter_AddRefs(connection)); if (NS_FAILED(rv) && NS_ERROR_GET_MODULE(rv) != NS_ERROR_MODULE_DOM_INDEXEDDB) { @@ -1691,12 +1693,12 @@ OpenDatabaseHelper::DoDatabaseWork() nsRefPtr fileManager = mgr->GetFileManager(mASCIIOrigin, mName); if (!fileManager) { - fileManager = new FileManager(mASCIIOrigin, mName); + fileManager = new FileManager(mASCIIOrigin, mPrivilege, mName); - rv = fileManager->Init(fileManagerDirectory, connection); + rv = fileManager->Init(fmDirectory, connection); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - mgr->AddFileManager(mASCIIOrigin, mName, fileManager); + mgr->AddFileManager(fileManager); } mFileManager = fileManager.forget(); @@ -1707,23 +1709,26 @@ OpenDatabaseHelper::DoDatabaseWork() // static nsresult OpenDatabaseHelper::CreateDatabaseConnection( - const nsAString& aName, nsIFile* aDBFile, - nsIFile* aFileManagerDirectory, + nsIFile* aFMDirectory, + const nsAString& aName, + const nsACString& aOrigin, mozIStorageConnection** aConnection) { NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); - NS_NAMED_LITERAL_CSTRING(quotaVFSName, "quota"); + nsCOMPtr dbFileUrl = + IDBFactory::GetDatabaseFileURL(aDBFile, aOrigin); + NS_ENSURE_TRUE(dbFileUrl, NS_ERROR_FAILURE); - nsCOMPtr ss = + nsCOMPtr ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE); nsCOMPtr connection; - nsresult rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName, - getter_AddRefs(connection)); + nsresult rv = + ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(connection)); if (rv == NS_ERROR_FILE_CORRUPTED) { // If we're just opening the database during origin initialization, then // we don't want to erase any files. The failure here will fail origin @@ -1737,21 +1742,20 @@ OpenDatabaseHelper::CreateDatabaseConnection( NS_ENSURE_SUCCESS(rv, rv); bool exists; - rv = aFileManagerDirectory->Exists(&exists); + rv = aFMDirectory->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); if (exists) { bool isDirectory; - rv = aFileManagerDirectory->IsDirectory(&isDirectory); + rv = aFMDirectory->IsDirectory(&isDirectory); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(isDirectory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - rv = aFileManagerDirectory->Remove(true); + rv = aFMDirectory->Remove(true); NS_ENSURE_SUCCESS(rv, rv); } - rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName, - getter_AddRefs(connection)); + rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(connection)); } NS_ENSURE_SUCCESS(rv, rv); @@ -2347,6 +2351,8 @@ DeleteDatabaseHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { NS_ASSERTION(!aConnection, "How did we get a connection here?"); + const FactoryPrivilege& privilege = mOpenHelper->Privilege(); + IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); NS_ASSERTION(mgr, "This should never fail!"); @@ -2372,59 +2378,57 @@ DeleteDatabaseHelper::DoDatabaseWork(mozIStorageConnection* aConnection) rv = dbFile->Exists(&exists); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - int rc; - if (exists) { - nsString dbFilePath; - rv = dbFile->GetPath(dbFilePath); - NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + int64_t fileSize; - rc = sqlite3_quota_remove(NS_ConvertUTF16toUTF8(dbFilePath).get()); - if (rc != SQLITE_OK) { - NS_WARNING("Failed to delete db file!"); - return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + if (privilege != Chrome) { + rv = dbFile->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } - // sqlite3_quota_remove won't actually remove anything if we're not tracking - // the quota here. Manually remove the file if it exists. - rv = dbFile->Exists(&exists); + rv = dbFile->Remove(false); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - if (exists) { - rv = dbFile->Remove(false); - NS_ENSURE_SUCCESS(rv, rv); + if (privilege != Chrome) { + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + quotaManager->DecreaseUsageForOrigin(mASCIIOrigin, fileSize); } } - nsCOMPtr fileManagerDirectory; - rv = directory->Clone(getter_AddRefs(fileManagerDirectory)); - NS_ENSURE_SUCCESS(rv, rv); - - rv = fileManagerDirectory->Append(filename); + nsCOMPtr fmDirectory; + rv = directory->Clone(getter_AddRefs(fmDirectory)); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - rv = fileManagerDirectory->Exists(&exists); + rv = fmDirectory->Append(filename); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + rv = fmDirectory->Exists(&exists); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (exists) { bool isDirectory; - rv = fileManagerDirectory->IsDirectory(&isDirectory); + rv = fmDirectory->IsDirectory(&isDirectory); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(isDirectory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - nsString fileManagerDirectoryPath; - rv = fileManagerDirectory->GetPath(fileManagerDirectoryPath); - NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + uint64_t usage = 0; - rc = sqlite3_quota_remove( - NS_ConvertUTF16toUTF8(fileManagerDirectoryPath).get()); - if (rc != SQLITE_OK) { - NS_WARNING("Failed to delete file directory!"); - return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + if (privilege != Chrome) { + rv = FileManager::GetUsage(fmDirectory, &usage); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } - rv = fileManagerDirectory->Remove(true); - NS_ENSURE_SUCCESS(rv, rv); + rv = fmDirectory->Remove(true); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + if (privilege != Chrome) { + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + quotaManager->DecreaseUsageForOrigin(mASCIIOrigin, usage); + } } return NS_OK; diff --git a/dom/indexedDB/OpenDatabaseHelper.h b/dom/indexedDB/OpenDatabaseHelper.h index 587301b9eaf..5a3d987c3d0 100644 --- a/dom/indexedDB/OpenDatabaseHelper.h +++ b/dom/indexedDB/OpenDatabaseHelper.h @@ -77,10 +77,16 @@ public: return mDatabase; } + const FactoryPrivilege& Privilege() const + { + return mPrivilege; + } + static - nsresult CreateDatabaseConnection(const nsAString& aName, - nsIFile* aDBFile, - nsIFile* aFileManagerDirectory, + nsresult CreateDatabaseConnection(nsIFile* aDBFile, + nsIFile* aFMDirectory, + const nsAString& aName, + const nsACString& aOrigin, mozIStorageConnection** aConnection); protected: diff --git a/dom/indexedDB/nsIStandardFileStream.idl b/dom/indexedDB/nsIStandardFileStream.idl deleted file mode 100644 index 265c3edb1ee..00000000000 --- a/dom/indexedDB/nsIStandardFileStream.idl +++ /dev/null @@ -1,60 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsISupports.idl" - -interface nsIFile; - -/** - * A stream that allows you to read from a file or stream to a file - * using standard file APIs. - */ -[scriptable, uuid(ebbbb779-92a3-4b2a-b7cf-6efbe904c453)] -interface nsIStandardFileStream : nsISupports -{ - /** - * If this is set, the file will be opened (i.e., a call to - * fopen done) only when we do an actual operation on the stream, - * or more specifically, when one of the following is called: - * - Seek - * - Tell - * - SetEOF - * - Available - * - Read - * - Write - * - Flush - * - GetSize - * - GetLastModified - * - Sync - * - * FLAGS_DEFER_OPEN is useful if we use the stream on a background - * thread, so that the opening and possible |stat|ing of the file - * happens there as well. - * - * @note Using this flag results in the file not being opened - * during the call to Init. This means that any errors that might - * happen when this flag is not set would happen during the - * first read. Also, the file is not locked when Init is called, - * so it might be deleted before we try to read from it. - */ - const long FLAGS_DEFER_OPEN = 1 << 0; - - /** - * @param file file to read from or stream to - * @param mode file open mode (see fopen documentation) - * @param flags flags specifying various behaviors of the class - * (see enumerations in the class) - */ - void init(in nsIFile file, - in AString mode, - in long flags); - - /** - * Flush all written content held in memory buffers out to disk. - * This is the equivalent of fflush() - */ - void flushBuffers(); -}; diff --git a/dom/indexedDB/test/Makefile.in b/dom/indexedDB/test/Makefile.in index 9c79b148a8c..4c9a2011076 100644 --- a/dom/indexedDB/test/Makefile.in +++ b/dom/indexedDB/test/Makefile.in @@ -54,11 +54,13 @@ MOCHITEST_FILES = \ test_file_os_delete.html \ test_file_put_get_object.html \ test_file_put_get_values.html \ + test_file_quota.html \ test_file_replace.html \ test_file_resurrection_delete.html \ test_file_resurrection_transaction_abort.html \ test_file_sharing.html \ test_file_transaction_abort.html \ + test_filehandle_quota.html \ test_filehandle_serialization.html \ test_filehandle_store_snapshot.html \ test_getAll.html \ diff --git a/dom/indexedDB/test/file.js b/dom/indexedDB/test/file.js index 07bd10a3245..3c6194a1327 100644 --- a/dom/indexedDB/test/file.js +++ b/dom/indexedDB/test/file.js @@ -3,6 +3,8 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ +const DEFAULT_QUOTA = 50 * 1024 * 1024; + var bufferCache = []; var utils = SpecialPowers.getDOMWindowUtils(window); @@ -184,25 +186,6 @@ function getUsage(usageHandler) idbManager.getUsageForURI(uri, callback); } -function getUsageSync() -{ - let usage; - - getUsage(function(aUsage, aFileUsage) { - usage = aUsage; - }); - - let comp = SpecialPowers.wrap(Components); - let thread = comp.classes["@mozilla.org/thread-manager;1"] - .getService(comp.interfaces.nsIThreadManager) - .currentThread; - while (!usage) { - thread.processNextEvent(true); - } - - return usage; -} - function scheduleGC() { SpecialPowers.exactGC(window, continueToNextStep); diff --git a/dom/indexedDB/test/test_file_quota.html b/dom/indexedDB/test/test_file_quota.html index b07880d78da..9fbc0c014ea 100644 --- a/dom/indexedDB/test/test_file_quota.html +++ b/dom/indexedDB/test/test_file_quota.html @@ -13,14 +13,12 @@ function testSteps() { const READ_WRITE = IDBTransaction.READ_WRITE; - const DEFAULT_QUOTA_MB = 50; const name = window.location.pathname; const objectStoreName = "Blobs"; - const testData = { key: 0, value: {} }; - const fileData = { key: 1, file: null }; + const fileData = { key: 1, file: getNullFile("random.bin", DEFAULT_QUOTA) }; let request = indexedDB.open(name, 1); request.onerror = errorHandler; @@ -32,21 +30,17 @@ let db = event.target.result; - let objectStore = db.createObjectStore(objectStoreName, { }); - objectStore.add(testData.value, testData.key); - - let size = (DEFAULT_QUOTA_MB + 1) * 1024 * 1024 - getUsageSync(); - fileData.file = getNullFile("random.bin", size); + db.createObjectStore(objectStoreName, { }); event = yield; is(event.type, "success", "Got correct event type"); trans = db.transaction([objectStoreName], READ_WRITE); - objectStore = trans.objectStore(objectStoreName); + let objectStore = trans.objectStore(objectStoreName); request = objectStore.add(fileData.file, fileData.key); - request.addEventListener("error", new ExpectError("UnknownError")); + request.addEventListener("error", new ExpectError("UnknownError", true)); request.onsuccess = unexpectedSuccessHandler; event = yield; diff --git a/dom/indexedDB/test/test_filehandle_quota.html b/dom/indexedDB/test/test_filehandle_quota.html index addaf015abe..0506279b4db 100644 --- a/dom/indexedDB/test/test_filehandle_quota.html +++ b/dom/indexedDB/test/test_filehandle_quota.html @@ -13,7 +13,6 @@ function testSteps() { const READ_WRITE = IDBTransaction.READ_WRITE; - const DEFAULT_QUOTA_MB = 50; const name = window.location.pathname; @@ -39,10 +38,10 @@ let lockedFile = fileHandle.open("readwrite"); - let blob = getNullBlob((50 + 1) * 1024 * 1024 - getUsageSync()); + let blob = getNullBlob(DEFAULT_QUOTA); request = lockedFile.write(blob); - request.addEventListener("error", new ExpectError("UnknownError")); + request.addEventListener("error", new ExpectError("UnknownError", true)); request.onsuccess = unexpectedSuccessHandler; event = yield; diff --git a/dom/quota/FileStreams.cpp b/dom/quota/FileStreams.cpp new file mode 100644 index 00000000000..9de244f7792 --- /dev/null +++ b/dom/quota/FileStreams.cpp @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileStreams.h" + +USING_QUOTA_NAMESPACE + +template +NS_IMETHODIMP +FileQuotaStream::SetEOF() +{ + nsresult rv = FileStreamBase::SetEOF(); + NS_ENSURE_SUCCESS(rv, rv); + + if (mQuotaObject) { + int64_t offset; + nsresult rv = FileStreamBase::Tell(&offset); + NS_ENSURE_SUCCESS(rv, rv); + + mQuotaObject->UpdateSize(offset); + } + + return NS_OK; +} + +template +NS_IMETHODIMP +FileQuotaStream::Close() +{ + nsresult rv = FileStreamBase::Close(); + NS_ENSURE_SUCCESS(rv, rv); + + mQuotaObject = nullptr; + + return NS_OK; +} + +template +nsresult +FileQuotaStream::DoOpen() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + NS_ASSERTION(!mQuotaObject, "Creating quota object more than once?"); + mQuotaObject = quotaManager->GetQuotaObject(mOrigin, + FileStreamBase::mOpenParams.localFile); + + nsresult rv = FileStreamBase::DoOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (mQuotaObject && (FileStreamBase::mOpenParams.ioFlags & PR_TRUNCATE)) { + mQuotaObject->UpdateSize(0); + } + + return NS_OK; +} + +template +NS_IMETHODIMP +FileQuotaStreamWithWrite::Write(const char* aBuf, + uint32_t aCount, + uint32_t* _retval) +{ + nsresult rv; + + if (FileQuotaStreamWithWrite::mQuotaObject) { + int64_t offset; + rv = FileStreamBase::Tell(&offset); + NS_ENSURE_SUCCESS(rv, rv); + + if (!FileQuotaStreamWithWrite:: + mQuotaObject->MaybeAllocateMoreSpace(offset, aCount)) { + return NS_ERROR_FAILURE; + } + } + + rv = FileStreamBase::Write(aBuf, aCount, _retval); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED0(FileInputStream, nsFileInputStream) + +already_AddRefed +FileInputStream::Create(const nsACString& aOrigin, nsIFile* aFile, + int32_t aIOFlags, int32_t aPerm, + int32_t aBehaviorFlags) +{ + nsRefPtr stream = new FileInputStream(aOrigin); + nsresult rv = stream->Init(aFile, aIOFlags, aPerm, aBehaviorFlags); + NS_ENSURE_SUCCESS(rv, nullptr); + return stream.forget(); +} + +NS_IMPL_ISUPPORTS_INHERITED0(FileOutputStream, nsFileOutputStream) + +already_AddRefed +FileOutputStream::Create(const nsACString& aOrigin, nsIFile* aFile, + int32_t aIOFlags, int32_t aPerm, + int32_t aBehaviorFlags) +{ + nsRefPtr stream = new FileOutputStream(aOrigin); + nsresult rv = stream->Init(aFile, aIOFlags, aPerm, aBehaviorFlags); + NS_ENSURE_SUCCESS(rv, nullptr); + return stream.forget(); +} + +NS_IMPL_ISUPPORTS_INHERITED0(FileStream, nsFileStream) + +already_AddRefed +FileStream::Create(const nsACString& aOrigin, nsIFile* aFile, int32_t aIOFlags, + int32_t aPerm, int32_t aBehaviorFlags) +{ + nsRefPtr stream = new FileStream(aOrigin); + nsresult rv = stream->Init(aFile, aIOFlags, aPerm, aBehaviorFlags); + NS_ENSURE_SUCCESS(rv, nullptr); + return stream.forget(); +} diff --git a/dom/quota/FileStreams.h b/dom/quota/FileStreams.h new file mode 100644 index 00000000000..77bfad47121 --- /dev/null +++ b/dom/quota/FileStreams.h @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_quota_filestreams_h__ +#define mozilla_dom_quota_filestreams_h__ + +#include "QuotaCommon.h" + +#include "nsFileStreams.h" + +#include "QuotaManager.h" + +BEGIN_QUOTA_NAMESPACE + +template +class FileQuotaStream : public FileStreamBase +{ +public: + // nsFileStreamBase override + NS_IMETHOD + SetEOF() MOZ_OVERRIDE; + + NS_IMETHOD + Close() MOZ_OVERRIDE; + +protected: + FileQuotaStream(const nsACString& aOrigin) + : mOrigin(aOrigin) + { } + + // nsFileStreamBase override + virtual nsresult + DoOpen() MOZ_OVERRIDE; + + nsCString mOrigin; + nsRefPtr mQuotaObject; +}; + +template +class FileQuotaStreamWithWrite : public FileQuotaStream +{ +public: + // nsFileStreamBase override + NS_IMETHOD + Write(const char* aBuf, uint32_t aCount, uint32_t* _retval) MOZ_OVERRIDE; + +protected: + FileQuotaStreamWithWrite(const nsACString& aOrigin) + : FileQuotaStream(aOrigin) + { } +}; + +class FileInputStream : public FileQuotaStream +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + static already_AddRefed + Create(const nsACString& aOrigin, nsIFile* aFile, int32_t aIOFlags = -1, + int32_t aPerm = -1, int32_t aBehaviorFlags = 0); + +private: + FileInputStream(const nsACString& aOrigin) + : FileQuotaStream(aOrigin) + { } + + virtual ~FileInputStream() { + Close(); + } +}; + +class FileOutputStream : public FileQuotaStreamWithWrite +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + static already_AddRefed + Create(const nsACString& aOrigin, nsIFile* aFile, int32_t aIOFlags = -1, + int32_t aPerm = -1, int32_t aBehaviorFlags = 0); + +private: + FileOutputStream(const nsACString& aOrigin) + : FileQuotaStreamWithWrite(aOrigin) + { } + + virtual ~FileOutputStream() { + Close(); + } +}; + +class FileStream : public FileQuotaStreamWithWrite +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + static already_AddRefed + Create(const nsACString& aOrigin, nsIFile* aFile, int32_t aIOFlags = -1, + int32_t aPerm = -1, int32_t aBehaviorFlags = 0); + +private: + FileStream(const nsACString& aOrigin) + : FileQuotaStreamWithWrite(aOrigin) + { } + + virtual ~FileStream() { + Close(); + } +}; + +END_QUOTA_NAMESPACE + +#endif /* mozilla_dom_quota_filestreams_h__ */ diff --git a/dom/quota/Makefile.in b/dom/quota/Makefile.in new file mode 100644 index 00000000000..49be551e24f --- /dev/null +++ b/dom/quota/Makefile.in @@ -0,0 +1,33 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = dom +LIBRARY_NAME = domquota_s +XPIDL_MODULE = dom_quota +LIBXUL_LIBRARY = 1 +FORCE_STATIC_LIB = 1 + +include $(topsrcdir)/dom/dom-config.mk + +EXPORTS_NAMESPACES = mozilla/dom/quota + +CPPSRCS = \ + FileStreams.cpp \ + QuotaManager.cpp \ + $(NULL) + +EXPORTS_mozilla/dom/quota = \ + FileStreams.h \ + QuotaCommon.h \ + QuotaManager.h \ + $(NULL) + +include $(topsrcdir)/config/rules.mk diff --git a/dom/quota/QuotaCommon.h b/dom/quota/QuotaCommon.h new file mode 100644 index 00000000000..a415d177eb3 --- /dev/null +++ b/dom/quota/QuotaCommon.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_quota_quotacommon_h__ +#define mozilla_dom_quota_quotacommon_h__ + +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsStringGlue.h" +#include "nsTArray.h" + +#define BEGIN_QUOTA_NAMESPACE \ + namespace mozilla { namespace dom { namespace quota { +#define END_QUOTA_NAMESPACE \ + } /* namespace quota */ } /* namespace dom */ } /* namespace mozilla */ +#define USING_QUOTA_NAMESPACE \ + using namespace mozilla::dom::quota; + +#endif // mozilla_dom_quota_quotacommon_h__ diff --git a/dom/quota/QuotaManager.cpp b/dom/quota/QuotaManager.cpp new file mode 100644 index 00000000000..b251606d718 --- /dev/null +++ b/dom/quota/QuotaManager.cpp @@ -0,0 +1,294 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "QuotaManager.h" + +#include "nsIFile.h" + +#include "mozilla/ClearOnShutdown.h" +#include "nsComponentManagerUtils.h" + +#include "mozilla/dom/indexedDB/IndexedDatabaseManager.h" + +USING_QUOTA_NAMESPACE + +namespace { + +nsAutoPtr gInstance; + +PLDHashOperator +RemoveQuotaForPatternCallback(const nsACString& aKey, + nsRefPtr& aValue, + void* aUserArg) +{ + NS_ASSERTION(!aKey.IsEmpty(), "Empty key!"); + NS_ASSERTION(aValue, "Null pointer!"); + NS_ASSERTION(aUserArg, "Null pointer!"); + + const nsACString* pattern = + static_cast(aUserArg); + + if (StringBeginsWith(aKey, *pattern)) { + return PL_DHASH_REMOVE; + } + + return PL_DHASH_NEXT; +} + +} // anonymous namespace + +void +QuotaObject::AddRef() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + if (!quotaManager) { + NS_ERROR("Null quota manager, this shouldn't happen, possible leak!"); + + NS_AtomicIncrementRefcnt(mRefCnt); + + return; + } + + MutexAutoLock lock(quotaManager->mQuotaMutex); + + ++mRefCnt; +} + +void +QuotaObject::Release() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + if (!quotaManager) { + NS_ERROR("Null quota manager, this shouldn't happen, possible leak!"); + + nsrefcnt count = NS_AtomicDecrementRefcnt(mRefCnt); + if (count == 0) { + mRefCnt = 1; + delete this; + } + + return; + } + + { + MutexAutoLock lock(quotaManager->mQuotaMutex); + + --mRefCnt; + + if (mRefCnt > 0) { + return; + } + + if (mOriginInfo) { + mOriginInfo->mQuotaObjects.Remove(mPath); + } + } + + delete this; +} + +void +QuotaObject::UpdateSize(int64_t aSize) +{ + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + MutexAutoLock lock(quotaManager->mQuotaMutex); + + if (mOriginInfo) { + mOriginInfo->mUsage -= mSize; + mSize = aSize; + mOriginInfo->mUsage += mSize; + } +} + +bool +QuotaObject::MaybeAllocateMoreSpace(int64_t aOffset, int32_t aCount) +{ + int64_t end = aOffset + aCount; + + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + MutexAutoLock lock(quotaManager->mQuotaMutex); + + if (mSize >= end || !mOriginInfo) { + return true; + } + + int64_t newUsage = mOriginInfo->mUsage - mSize + end; + if (newUsage > mOriginInfo->mLimit) { + if (!indexedDB::IndexedDatabaseManager::QuotaIsLifted()) { + return false; + } + + nsCString origin = mOriginInfo->mOrigin; + + mOriginInfo->LockedClearOriginInfos(); + NS_ASSERTION(!mOriginInfo, + "Should have cleared in LockedClearOriginInfos!"); + + quotaManager->mOriginInfos.Remove(origin); + + mSize = end; + + return true; + } + + mOriginInfo->mUsage = newUsage; + mSize = end; + + return true; +} + +#ifdef DEBUG +void +OriginInfo::LockedClearOriginInfos() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + quotaManager->mQuotaMutex.AssertCurrentThreadOwns(); + + mQuotaObjects.EnumerateRead(ClearOriginInfoCallback, nullptr); +} +#endif + +// static +PLDHashOperator +OriginInfo::ClearOriginInfoCallback(const nsAString& aKey, + QuotaObject* aValue, + void* aUserArg) +{ + NS_ASSERTION(!aKey.IsEmpty(), "Empty key!"); + NS_ASSERTION(aValue, "Null pointer!"); + + aValue->mOriginInfo = nullptr; + + return PL_DHASH_NEXT; +} + +// static +QuotaManager* +QuotaManager::GetOrCreate() +{ + if (!gInstance) { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + gInstance = new QuotaManager(); + + ClearOnShutdown(&gInstance); + } + + return gInstance; +} + +// static +QuotaManager* +QuotaManager::Get() +{ + // Does not return an owning reference. + return gInstance; +} + +void +QuotaManager::InitQuotaForOrigin(const nsACString& aOrigin, + int64_t aLimit, + int64_t aUsage) +{ + OriginInfo* info = new OriginInfo(aOrigin, aLimit * 1024 * 1024, aUsage); + + MutexAutoLock lock(mQuotaMutex); + + NS_ASSERTION(!mOriginInfos.GetWeak(aOrigin), "Replacing an existing entry!"); + mOriginInfos.Put(aOrigin, info); +} + +void +QuotaManager::DecreaseUsageForOrigin(const nsACString& aOrigin, + int64_t aSize) +{ + MutexAutoLock lock(mQuotaMutex); + + nsRefPtr originInfo; + mOriginInfos.Get(aOrigin, getter_AddRefs(originInfo)); + + if (originInfo) { + originInfo->mUsage -= aSize; + } +} + +void +QuotaManager::RemoveQuotaForPattern(const nsACString& aPattern) +{ + NS_ASSERTION(!aPattern.IsEmpty(), "Empty pattern!"); + + MutexAutoLock lock(mQuotaMutex); + + mOriginInfos.Enumerate(RemoveQuotaForPatternCallback, + const_cast(&aPattern)); +} + +already_AddRefed +QuotaManager::GetQuotaObject(const nsACString& aOrigin, + nsIFile* aFile) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + nsString path; + nsresult rv = aFile->GetPath(path); + NS_ENSURE_SUCCESS(rv, nullptr); + + int64_t fileSize; + + bool exists; + rv = aFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv, nullptr); + + if (exists) { + rv = aFile->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(rv, nullptr); + } + else { + fileSize = 0; + } + + QuotaObject* info = nullptr; + { + MutexAutoLock lock(mQuotaMutex); + + nsRefPtr originInfo; + mOriginInfos.Get(aOrigin, getter_AddRefs(originInfo)); + + if (!originInfo) { + return nullptr; + } + + originInfo->mQuotaObjects.Get(path, &info); + + if (!info) { + info = new QuotaObject(originInfo, path, fileSize); + originInfo->mQuotaObjects.Put(path, info); + } + } + + nsRefPtr result = info; + return result.forget(); +} + +already_AddRefed +QuotaManager::GetQuotaObject(const nsACString& aOrigin, + const nsAString& aPath) +{ + nsresult rv; + nsCOMPtr file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, nullptr); + + rv = file->InitWithPath(aPath); + NS_ENSURE_SUCCESS(rv, nullptr); + + return GetQuotaObject(aOrigin, file); +} diff --git a/dom/quota/QuotaManager.h b/dom/quota/QuotaManager.h new file mode 100644 index 00000000000..e19acdd9be3 --- /dev/null +++ b/dom/quota/QuotaManager.h @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_quota_quotamanager_h__ +#define mozilla_dom_quota_quotamanager_h__ + +#include "QuotaCommon.h" + +#include "mozilla/Mutex.h" +#include "nsDataHashtable.h" +#include "nsRefPtrHashtable.h" +#include "nsThreadUtils.h" + +BEGIN_QUOTA_NAMESPACE + +class OriginInfo; +class QuotaManager; + +class QuotaObject +{ + friend class OriginInfo; + friend class QuotaManager; + +public: + void + AddRef(); + + void + Release(); + + void + UpdateSize(int64_t aSize); + + bool + MaybeAllocateMoreSpace(int64_t aOffset, int32_t aCount); + +private: + QuotaObject(OriginInfo* aOriginInfo, const nsAString& aPath, int64_t aSize) + : mOriginInfo(aOriginInfo), mPath(aPath), mSize(aSize) + { } + + virtual ~QuotaObject() + { } + + nsAutoRefCnt mRefCnt; + + OriginInfo* mOriginInfo; + nsString mPath; + int64_t mSize; +}; + +class OriginInfo +{ + friend class QuotaManager; + friend class QuotaObject; + +public: + OriginInfo(const nsACString& aOrigin, int64_t aLimit, int64_t aUsage) + : mOrigin(aOrigin), mLimit(aLimit), mUsage(aUsage) + { + mQuotaObjects.Init(); + } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OriginInfo) + +private: + void +#ifdef DEBUG + LockedClearOriginInfos(); +#else + LockedClearOriginInfos() + { + mQuotaObjects.EnumerateRead(ClearOriginInfoCallback, nullptr); + } +#endif + + static PLDHashOperator + ClearOriginInfoCallback(const nsAString& aKey, + QuotaObject* aValue, void* aUserArg); + + nsDataHashtable mQuotaObjects; + + nsCString mOrigin; + int64_t mLimit; + int64_t mUsage; +}; + +class QuotaManager +{ + friend class nsAutoPtr; + friend class OriginInfo; + friend class QuotaObject; + +public: + // Returns a non-owning reference. + static QuotaManager* + GetOrCreate(); + + // Returns a non-owning reference. + static QuotaManager* + Get(); + + void + InitQuotaForOrigin(const nsACString& aOrigin, + int64_t aLimit, + int64_t aUsage); + + void + DecreaseUsageForOrigin(const nsACString& aOrigin, + int64_t aSize); + + void + RemoveQuotaForPattern(const nsACString& aPattern); + + already_AddRefed + GetQuotaObject(const nsACString& aOrigin, + nsIFile* aFile); + + already_AddRefed + GetQuotaObject(const nsACString& aOrigin, + const nsAString& aPath); + +private: + QuotaManager() + : mQuotaMutex("QuotaManager.mQuotaMutex") + { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + mOriginInfos.Init(); + } + + virtual ~QuotaManager() + { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + } + + mozilla::Mutex mQuotaMutex; + + nsRefPtrHashtable mOriginInfos; +}; + +END_QUOTA_NAMESPACE + +#endif /* mozilla_dom_quota_quotamanager_h__ */ diff --git a/layout/build/Makefile.in b/layout/build/Makefile.in index e6b32da8839..496b55f2343 100644 --- a/layout/build/Makefile.in +++ b/layout/build/Makefile.in @@ -69,6 +69,7 @@ SHARED_LIBRARY_LIBS = \ $(DEPTH)/dom/encoding/$(LIB_PREFIX)domencoding_s.$(LIB_SUFFIX) \ $(DEPTH)/dom/file/$(LIB_PREFIX)domfile_s.$(LIB_SUFFIX) \ $(DEPTH)/dom/power/$(LIB_PREFIX)dom_power_s.$(LIB_SUFFIX) \ + $(DEPTH)/dom/quota/$(LIB_PREFIX)domquota_s.$(LIB_SUFFIX) \ $(DEPTH)/dom/settings/$(LIB_PREFIX)jsdomsettings_s.$(LIB_SUFFIX) \ $(DEPTH)/dom/permission/$(LIB_PREFIX)jsdompermissionsettings_s.$(LIB_SUFFIX) \ $(DEPTH)/dom/network/src/$(LIB_PREFIX)dom_network_s.$(LIB_SUFFIX) \ diff --git a/netwerk/base/src/Makefile.in b/netwerk/base/src/Makefile.in index 0c0d60ea5a4..e8cef4871c2 100644 --- a/netwerk/base/src/Makefile.in +++ b/netwerk/base/src/Makefile.in @@ -19,6 +19,7 @@ LIBXUL_LIBRARY = 1 EXPORTS = \ nsMIMEInputStream.h \ nsURLHelper.h \ + nsFileStreams.h \ $(NULL) EXPORTS_NAMESPACES = mozilla/net diff --git a/netwerk/base/src/nsFileStreams.cpp b/netwerk/base/src/nsFileStreams.cpp index 2420ffca400..ecc26aa19fa 100644 --- a/netwerk/base/src/nsFileStreams.cpp +++ b/netwerk/base/src/nsFileStreams.cpp @@ -51,7 +51,9 @@ nsFileStreamBase::~nsFileStreamBase() Close(); } -NS_IMPL_THREADSAFE_ISUPPORTS1(nsFileStreamBase, nsISeekableStream) +NS_IMPL_THREADSAFE_ISUPPORTS2(nsFileStreamBase, + nsISeekableStream, + nsIFileMetadata) NS_IMETHODIMP nsFileStreamBase::Seek(int32_t whence, int64_t offset) @@ -124,6 +126,52 @@ nsFileStreamBase::SetEOF() return NS_OK; } +NS_IMETHODIMP +nsFileStreamBase::GetSize(int64_t* _retval) +{ + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mFD) { + return NS_BASE_STREAM_CLOSED; + } + + PRFileInfo64 info; + if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) { + return NS_BASE_STREAM_OSERROR; + } + + *_retval = int64_t(info.size); + + return NS_OK; +} + +NS_IMETHODIMP +nsFileStreamBase::GetLastModified(int64_t* _retval) +{ + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mFD) { + return NS_BASE_STREAM_CLOSED; + } + + PRFileInfo64 info; + if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) { + return NS_BASE_STREAM_OSERROR; + } + + int64_t modTime = int64_t(info.modifyTime); + if (modTime == 0) { + *_retval = 0; + } + else { + *_retval = modTime / int64_t(PR_USEC_PER_MSEC); + } + + return NS_OK; +} + nsresult nsFileStreamBase::Close() { @@ -934,13 +982,12 @@ nsSafeFileOutputStream::Write(const char *buf, uint32_t count, uint32_t *result) //////////////////////////////////////////////////////////////////////////////// // nsFileStream -NS_IMPL_ISUPPORTS_INHERITED4(nsFileStream, +NS_IMPL_ISUPPORTS_INHERITED3(nsFileStream, nsFileStreamBase, nsIInputStream, nsIOutputStream, - nsIFileStream, - nsIFileMetadata) - + nsIFileStream) + NS_IMETHODIMP nsFileStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm, int32_t behaviorFlags) @@ -959,50 +1006,4 @@ nsFileStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm, mBehaviorFlags & nsIFileStream::DEFER_OPEN); } -NS_IMETHODIMP -nsFileStream::GetSize(int64_t* _retval) -{ - nsresult rv = DoPendingOpen(); - NS_ENSURE_SUCCESS(rv, rv); - - if (!mFD) { - return NS_BASE_STREAM_CLOSED; - } - - PRFileInfo64 info; - if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) { - return NS_BASE_STREAM_OSERROR; - } - - *_retval = int64_t(info.size); - - return NS_OK; -} - -NS_IMETHODIMP -nsFileStream::GetLastModified(int64_t* _retval) -{ - nsresult rv = DoPendingOpen(); - NS_ENSURE_SUCCESS(rv, rv); - - if (!mFD) { - return NS_BASE_STREAM_CLOSED; - } - - PRFileInfo64 info; - if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) { - return NS_BASE_STREAM_OSERROR; - } - - int64_t modTime = int64_t(info.modifyTime); - if (modTime == 0) { - *_retval = 0; - } - else { - *_retval = modTime / int64_t(PR_USEC_PER_MSEC); - } - - return NS_OK; -} - //////////////////////////////////////////////////////////////////////////////// diff --git a/netwerk/base/src/nsFileStreams.h b/netwerk/base/src/nsFileStreams.h index 13e5b459e25..1aa6a829e3e 100644 --- a/netwerk/base/src/nsFileStreams.h +++ b/netwerk/base/src/nsFileStreams.h @@ -24,11 +24,13 @@ //////////////////////////////////////////////////////////////////////////////// -class nsFileStreamBase : public nsISeekableStream +class nsFileStreamBase : public nsISeekableStream, + public nsIFileMetadata { public: NS_DECL_ISUPPORTS NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSIFILEMETADATA nsFileStreamBase(); virtual ~nsFileStreamBase(); @@ -124,8 +126,8 @@ public: NS_IMETHOD IsNonBlocking(bool* _retval) { return nsFileStreamBase::IsNonBlocking(_retval); - } - + } + // Overrided from nsFileStreamBase NS_IMETHOD Seek(int32_t aWhence, int64_t aOffset); @@ -260,13 +262,11 @@ protected: class nsFileStream : public nsFileStreamBase, public nsIInputStream, public nsIOutputStream, - public nsIFileStream, - public nsIFileMetadata + public nsIFileStream { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIFILESTREAM - NS_DECL_NSIFILEMETADATA NS_FORWARD_NSIINPUTSTREAM(nsFileStreamBase::) // Can't use NS_FORWARD_NSIOUTPUTSTREAM due to overlapping methods diff --git a/storage/public/Makefile.in b/storage/public/Makefile.in index c485d4e75ba..c05e6f38e5a 100644 --- a/storage/public/Makefile.in +++ b/storage/public/Makefile.in @@ -36,7 +36,6 @@ XPIDLSRCS = \ mozIStorageCompletionCallback.idl \ mozIStorageBaseStatement.idl \ mozIStorageAsyncStatement.idl \ - mozIStorageServiceQuotaManagement.idl \ mozIStorageVacuumParticipant.idl \ $(NULL) # SEE ABOVE NOTE! diff --git a/storage/public/mozIStorageService.idl b/storage/public/mozIStorageService.idl index 3087a115219..483649b0f51 100644 --- a/storage/public/mozIStorageService.idl +++ b/storage/public/mozIStorageService.idl @@ -7,6 +7,7 @@ interface mozIStorageConnection; interface nsIFile; +interface nsIFileURL; /** * The mozIStorageService interface is intended to be implemented by @@ -15,7 +16,7 @@ interface nsIFile; * * This is the only way to open a database connection. */ -[scriptable, uuid(fe8e95cb-b377-4c8d-bccb-d9198c67542b)] +[scriptable, uuid(12bfad34-cca3-40fb-8736-d8bf9db61a27)] interface mozIStorageService : nsISupports { /** * Get a connection to a named special database storage. @@ -106,6 +107,16 @@ interface mozIStorageService : nsISupports { */ mozIStorageConnection openUnsharedDatabase(in nsIFile aDatabaseFile); + /** + * See openDatabase(). Exactly the same only initialized with a file URL. + * Custom parameters can be passed to SQLite and VFS implementations through + * the query part of the URL. + * + * @param aURL + * A nsIFileURL that represents the database that is to be opened. + */ + mozIStorageConnection openDatabaseWithFileURL(in nsIFileURL aFileURL); + /* * Utilities */ diff --git a/storage/public/mozIStorageServiceQuotaManagement.idl b/storage/public/mozIStorageServiceQuotaManagement.idl deleted file mode 100644 index ee5086b9781..00000000000 --- a/storage/public/mozIStorageServiceQuotaManagement.idl +++ /dev/null @@ -1,99 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsISupports.idl" - -interface mozIStorageConnection; -interface nsIFile; - -[scriptable, function, uuid(ae94f0a5-ebdf-48f4-9959-085e13235d8d)] -interface mozIStorageQuotaCallback : nsISupports -{ - /** - * Called when the file size quota for a group of databases is exceeded. - * - * @param aFilename - * The filename of the database that has exceeded the quota. - * - * @param aCurrentSizeLimit - * The current size (in bytes) of the quota. - * - * @param aCurrentTotalSize - * The current size of all databases in the quota group. - * - * @param aUserData - * Any additional data that was provided to the - * setQuotaForFilenamePattern function. - * - * @returns A new quota size. A new quota of 0 will disable the quota callback - * and any quota value less than aCurrentTotalSize will cause the - * database operation to fail with NS_ERROR_FILE_NO_DEVICE_SPACE. - */ - long long quotaExceeded(in ACString aFilename, - in long long aCurrentSizeLimit, - in long long aCurrentTotalSize, - in nsISupports aUserData); -}; - -/** - * This is a temporary interface that should eventually merge with - * mozIStorageService. - */ -[scriptable, uuid(4d81faf5-fe01-428b-99b8-c94cba12fd72)] -interface mozIStorageServiceQuotaManagement : nsISupports -{ - /** - * See mozIStorageService.openDatabase. Exactly the same only with a custom - * SQLite VFS. - */ - mozIStorageConnection openDatabaseWithVFS(in nsIFile aDatabaseFile, - in ACString aVFSName); - - /** - * Set a file size quota for a group of databases matching the given filename - * pattern, optionally specifying a callback when the quota is exceeded. - * - * @param aPattern - * A pattern to match filenames for inclusion in the quota system. May - * contain the following special characters: - * '*' Matches any sequence of zero or more characters. - * '?' Matches exactly one character. - * [...] Matches one character from the enclosed list of characters. - * [^...] Matches one character not in the enclosed list. - * - * @param aSizeLimit - * The size limit (in bytes) for the quota group. - * - * @param aCallback - * A callback that will be used when the quota is exceeded. - * - * @param aUserData - * Additional information to be passed to the callback. - */ - void setQuotaForFilenamePattern(in ACString aPattern, - in long long aSizeLimit, - in mozIStorageQuotaCallback aCallback, - in nsISupports aUserData); - - /** - * Adds, removes, or updates the file size information maintained by the quota - * system for files not opened through openDatabaseWithVFS(). - * - * Use this function when you want files to be included in quota calculations - * that are either a) not SQLite databases, or b) SQLite databases that have - * not been opened. - * - * This function will have no effect on files that do not match an existing - * quota pattern (set previously by setQuotaForFilenamePattern()). - * - * @param aFile - * The file for which quota information should be updated. If the file - * exists then its size information will be added or refreshed. If the - * file does not exist then the file will be removed from tracking - * under the quota system. - */ - void updateQuotaInformationForFile(in nsIFile aFile); -}; diff --git a/storage/public/storage.h b/storage/public/storage.h index 8e571e276f9..08f39f3d32e 100644 --- a/storage/public/storage.h +++ b/storage/public/storage.h @@ -24,7 +24,6 @@ #include "mozIStorageStatementCallback.h" #include "mozIStorageBindingParamsArray.h" #include "mozIStorageBindingParams.h" -#include "mozIStorageServiceQuotaManagement.h" #include "mozIStorageVacuumParticipant.h" #include "mozIStorageCompletionCallback.h" #include "mozIStorageAsyncStatement.h" diff --git a/storage/src/TelemetryVFS.cpp b/storage/src/TelemetryVFS.cpp index 60de5c41e5d..e4fce091f72 100644 --- a/storage/src/TelemetryVFS.cpp +++ b/storage/src/TelemetryVFS.cpp @@ -10,6 +10,7 @@ #include "sqlite3.h" #include "nsThreadUtils.h" #include "mozilla/Util.h" +#include "mozilla/dom/quota/QuotaManager.h" /** * This preference is a workaround to allow users/sysadmins to identify @@ -24,6 +25,7 @@ namespace { using namespace mozilla; +using namespace mozilla::dom::quota; struct Histograms { const char *name; @@ -82,9 +84,17 @@ private: }; struct telemetry_file { - sqlite3_file base; // Base class. Must be first - Histograms *histograms; // histograms pertaining to this file - sqlite3_file pReal[1]; // This contains the vfs that actually does work + // Base class. Must be first + sqlite3_file base; + + // histograms pertaining to this file + Histograms *histograms; + + // quota object for this file + nsRefPtr quotaObject; + + // This contains the vfs that actually does work + sqlite3_file pReal[1]; }; /* @@ -99,6 +109,7 @@ xClose(sqlite3_file *pFile) if( rc==SQLITE_OK ){ delete p->base.pMethods; p->base.pMethods = NULL; + p->quotaObject = nullptr; } return rc; } @@ -126,6 +137,9 @@ int xWrite(sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst) { telemetry_file *p = (telemetry_file *)pFile; + if (p->quotaObject && !p->quotaObject->MaybeAllocateMoreSpace(iOfst, iAmt)) { + return SQLITE_FULL; + } IOThreadAutoTimer ioTimer(p->histograms->writeMS); int rc; rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst); @@ -144,6 +158,9 @@ xTruncate(sqlite3_file *pFile, sqlite_int64 size) int rc; Telemetry::AutoTimer timer; rc = p->pReal->pMethods->xTruncate(p->pReal, size); + if (rc == SQLITE_OK && p->quotaObject) { + p->quotaObject->UpdateSize(size); + } return rc; } @@ -300,6 +317,18 @@ xOpen(sqlite3_vfs* vfs, const char *zName, sqlite3_file* pFile, break; } p->histograms = h; + + const char* origin; + if ((flags & SQLITE_OPEN_URI) && + (origin = sqlite3_uri_parameter(zName, "origin"))) { + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + p->quotaObject = quotaManager->GetQuotaObject(nsDependentCString(origin), + NS_ConvertUTF8toUTF16(zName)); + + } + rc = orig_vfs->xOpen(orig_vfs, zName, p->pReal, flags, pOutFlags); if( rc != SQLITE_OK ) return rc; diff --git a/storage/src/mozStorageConnection.cpp b/storage/src/mozStorageConnection.cpp index 3afd3e1b83b..430824a77fe 100644 --- a/storage/src/mozStorageConnection.cpp +++ b/storage/src/mozStorageConnection.cpp @@ -12,6 +12,7 @@ #include "nsIMemoryReporter.h" #include "nsThreadUtils.h" #include "nsIFile.h" +#include "nsIFileURL.h" #include "mozilla/Telemetry.h" #include "mozilla/Mutex.h" #include "mozilla/CondVar.h" @@ -471,34 +472,83 @@ Connection::getAsyncExecutionTarget() } nsresult -Connection::initialize(nsIFile *aDatabaseFile, - const char* aVFSName) +Connection::initialize() { NS_ASSERTION (!mDBConn, "Initialize called on already opened database!"); SAMPLE_LABEL("storage", "Connection::initialize"); - int srv; - nsresult rv; - - mDatabaseFile = aDatabaseFile; - - if (aDatabaseFile) { - nsAutoString path; - rv = aDatabaseFile->GetPath(path); - NS_ENSURE_SUCCESS(rv, rv); - - srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn, mFlags, - aVFSName); - } - else { - // in memory database requested, sqlite uses a magic file name - srv = ::sqlite3_open_v2(":memory:", &mDBConn, mFlags, aVFSName); - } + // in memory database requested, sqlite uses a magic file name + int srv = ::sqlite3_open_v2(":memory:", &mDBConn, mFlags, NULL); if (srv != SQLITE_OK) { mDBConn = nullptr; return convertResultCode(srv); } + return initializeInternal(nullptr); +} + +nsresult +Connection::initialize(nsIFile *aDatabaseFile) +{ + NS_ASSERTION (aDatabaseFile, "Passed null file!"); + NS_ASSERTION (!mDBConn, "Initialize called on already opened database!"); + SAMPLE_LABEL("storage", "Connection::initialize"); + + mDatabaseFile = aDatabaseFile; + + nsAutoString path; + nsresult rv = aDatabaseFile->GetPath(path); + NS_ENSURE_SUCCESS(rv, rv); + + int srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn, + mFlags, NULL); + if (srv != SQLITE_OK) { + mDBConn = nullptr; + return convertResultCode(srv); + } + + rv = initializeInternal(aDatabaseFile); + NS_ENSURE_SUCCESS(rv, rv); + + mDatabaseFile = aDatabaseFile; + + return NS_OK; +} + +nsresult +Connection::initialize(nsIFileURL *aFileURL) +{ + NS_ASSERTION (aFileURL, "Passed null file URL!"); + NS_ASSERTION (!mDBConn, "Initialize called on already opened database!"); + SAMPLE_LABEL("storage", "Connection::initialize"); + + nsCOMPtr databaseFile; + nsresult rv = aFileURL->GetFile(getter_AddRefs(databaseFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString spec; + rv = aFileURL->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + int srv = ::sqlite3_open_v2(spec.get(), &mDBConn, mFlags, NULL); + if (srv != SQLITE_OK) { + mDBConn = nullptr; + return convertResultCode(srv); + } + + rv = initializeInternal(databaseFile); + NS_ENSURE_SUCCESS(rv, rv); + + mFileURL = aFileURL; + mDatabaseFile = databaseFile; + + return NS_OK; +} + + +nsresult +Connection::initializeInternal(nsIFile* aDatabaseFile) +{ // Properly wrap the database handle's mutex. sharedDBMutex.initWithMutex(sqlite3_db_mutex(mDBConn)); @@ -522,14 +572,14 @@ Connection::initialize(nsIFile *aDatabaseFile, nsAutoCString pageSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size = "); pageSizeQuery.AppendInt(pageSize); - rv = ExecuteSimpleSQL(pageSizeQuery); + nsresult rv = ExecuteSimpleSQL(pageSizeQuery); NS_ENSURE_SUCCESS(rv, rv); // Get the current page_size, since it may differ from the specified value. sqlite3_stmt *stmt; NS_NAMED_LITERAL_CSTRING(pragma_page_size, MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"); - srv = prepareStatement(pragma_page_size, &stmt); + int srv = prepareStatement(pragma_page_size, &stmt); if (srv == SQLITE_OK) { if (SQLITE_ROW == stepStatement(stmt)) { pageSize = ::sqlite3_column_int64(stmt, 0); @@ -962,7 +1012,8 @@ Connection::Clone(bool aReadOnly, nsRefPtr clone = new Connection(mStorageService, flags); NS_ENSURE_TRUE(clone, NS_ERROR_OUT_OF_MEMORY); - nsresult rv = clone->initialize(mDatabaseFile); + nsresult rv = mFileURL ? clone->initialize(mFileURL) + : clone->initialize(mDatabaseFile); NS_ENSURE_SUCCESS(rv, rv); // Copy over pragmas from the original connection. diff --git a/storage/src/mozStorageConnection.h b/storage/src/mozStorageConnection.h index b71f5dbb6e0..97f5cf8aa93 100644 --- a/storage/src/mozStorageConnection.h +++ b/storage/src/mozStorageConnection.h @@ -25,6 +25,7 @@ struct PRLock; class nsIFile; +class nsIFileURL; class nsIEventTarget; class nsIThread; @@ -62,19 +63,28 @@ public: */ Connection(Service *aService, int aFlags); + /** + * Creates the connection to an in-memory database. + */ + nsresult initialize(); + /** * Creates the connection to the database. * * @param aDatabaseFile * The nsIFile of the location of the database to open, or create if it - * does not exist. Passing in nullptr here creates an in-memory - * database. - * @param aVFSName - * The VFS that SQLite will use when opening this database. NULL means - * "default". + * does not exist. */ - nsresult initialize(nsIFile *aDatabaseFile, - const char* aVFSName = NULL); + nsresult initialize(nsIFile *aDatabaseFile); + + /** + * Creates the connection to the database. + * + * @param aFileURL + * The nsIFileURL of the location of the database to open, or create if it + * does not exist. + */ + nsresult initialize(nsIFileURL *aFileURL); // fetch the native handle sqlite3 *GetNativeConnection() { return mDBConn; } @@ -155,6 +165,8 @@ public: private: ~Connection(); + nsresult initializeInternal(nsIFile *aDatabaseFile); + /** * Sets the database into a closed state so no further actions can be * performed. @@ -206,6 +218,7 @@ private: int progressHandler(); sqlite3 *mDBConn; + nsCOMPtr mFileURL; nsCOMPtr mDatabaseFile; /** diff --git a/storage/src/mozStorageService.cpp b/storage/src/mozStorageService.cpp index 00661d6d409..862a7da46fc 100644 --- a/storage/src/mozStorageService.cpp +++ b/storage/src/mozStorageService.cpp @@ -24,8 +24,6 @@ #include "mozilla/Preferences.h" #include "sqlite3.h" -#include "test_quota.h" -#include "test_quota.c" #ifdef SQLITE_OS_WIN // "windows.h" was included and it can #define lots of things we care about... @@ -35,61 +33,6 @@ #include "nsIPromptService.h" #include "nsIMemoryReporter.h" -namespace { - -class QuotaCallbackData -{ -public: - QuotaCallbackData(mozIStorageQuotaCallback *aCallback, - nsISupports *aUserData) - : callback(aCallback), userData(aUserData) - { - MOZ_COUNT_CTOR(QuotaCallbackData); - } - - ~QuotaCallbackData() - { - MOZ_COUNT_DTOR(QuotaCallbackData); - } - - static void Callback(const char *zFilename, - sqlite3_int64 *piLimit, - sqlite3_int64 iSize, - void *pArg) - { - NS_ASSERTION(zFilename && strlen(zFilename), "Null or empty filename!"); - NS_ASSERTION(piLimit, "Null pointer!"); - - QuotaCallbackData *data = static_cast(pArg); - if (!data) { - // No callback specified, return immediately. - return; - } - - NS_ASSERTION(data->callback, "Should never have a null callback!"); - - nsDependentCString filename(zFilename); - - int64_t newLimit; - if (NS_SUCCEEDED(data->callback->QuotaExceeded(filename, *piLimit, - iSize, data->userData, - &newLimit))) { - *piLimit = newLimit; - } - } - - static void Destroy(void *aUserData) - { - delete static_cast(aUserData); - } - -private: - nsCOMPtr callback; - nsCOMPtr userData; -}; - -} // anonymous namespace - //////////////////////////////////////////////////////////////////////////////// //// Defines @@ -345,11 +288,10 @@ private: //////////////////////////////////////////////////////////////////////////////// //// Service -NS_IMPL_THREADSAFE_ISUPPORTS3( +NS_IMPL_THREADSAFE_ISUPPORTS2( Service, mozIStorageService, - nsIObserver, - mozIStorageServiceQuotaManagement + nsIObserver ) Service *Service::gService = nullptr; @@ -438,10 +380,6 @@ Service::~Service() // Shutdown the sqlite3 API. Warn if shutdown did not turn out okay, but // there is nothing actionable we can do in that case. - rc = ::sqlite3_quota_shutdown(); - if (rc != SQLITE_OK) - NS_WARNING("sqlite3 did not shutdown cleanly."); - rc = ::sqlite3_shutdown(); if (rc != SQLITE_OK) NS_WARNING("sqlite3 did not shutdown cleanly."); @@ -636,9 +574,6 @@ Service::initialize() } else { NS_WARNING("Failed to register telemetry VFS"); } - rc = ::sqlite3_quota_initialize("telemetry-vfs", 0); - if (rc != SQLITE_OK) - return convertResultCode(rc); // Set the default value for the toolkit.storage.synchronous pref. It will be // updated with the user preference on the main thread. @@ -739,28 +674,24 @@ Service::OpenSpecialDatabase(const char *aStorageKey, // connection to use a memory DB. } else if (::strcmp(aStorageKey, "profile") == 0) { - rv = NS_GetSpecialDirectory(NS_APP_STORAGE_50_FILE, getter_AddRefs(storageFile)); NS_ENSURE_SUCCESS(rv, rv); - nsString filename; - storageFile->GetPath(filename); - nsCString filename8 = NS_ConvertUTF16toUTF8(filename.get()); // fall through to DB initialization } else { return NS_ERROR_INVALID_ARG; } - Connection *msc = new Connection(this, SQLITE_OPEN_READWRITE); - NS_ENSURE_TRUE(msc, NS_ERROR_OUT_OF_MEMORY); + nsRefPtr msc = new Connection(this, SQLITE_OPEN_READWRITE); - rv = msc->initialize(storageFile); + rv = storageFile ? msc->initialize(storageFile) : msc->initialize(); NS_ENSURE_SUCCESS(rv, rv); - NS_ADDREF(*_connection = msc); + msc.forget(_connection); return NS_OK; + } NS_IMETHODIMP @@ -774,12 +705,11 @@ Service::OpenDatabase(nsIFile *aDatabaseFile, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | SQLITE_OPEN_CREATE; nsRefPtr msc = new Connection(this, flags); - NS_ENSURE_TRUE(msc, NS_ERROR_OUT_OF_MEMORY); nsresult rv = msc->initialize(aDatabaseFile); NS_ENSURE_SUCCESS(rv, rv); - NS_ADDREF(*_connection = msc); + msc.forget(_connection); return NS_OK; } @@ -794,12 +724,30 @@ Service::OpenUnsharedDatabase(nsIFile *aDatabaseFile, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE | SQLITE_OPEN_CREATE; nsRefPtr msc = new Connection(this, flags); - NS_ENSURE_TRUE(msc, NS_ERROR_OUT_OF_MEMORY); nsresult rv = msc->initialize(aDatabaseFile); NS_ENSURE_SUCCESS(rv, rv); - NS_ADDREF(*_connection = msc); + msc.forget(_connection); + return NS_OK; +} + +NS_IMETHODIMP +Service::OpenDatabaseWithFileURL(nsIFileURL *aFileURL, + mozIStorageConnection **_connection) +{ + NS_ENSURE_ARG(aFileURL); + + // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility + // reasons. + int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | + SQLITE_OPEN_CREATE | SQLITE_OPEN_URI; + nsRefPtr msc = new Connection(this, flags); + + nsresult rv = msc->initialize(aFileURL); + NS_ENSURE_SUCCESS(rv, rv); + + msc.forget(_connection); return NS_OK; } @@ -885,67 +833,5 @@ Service::Observe(nsISupports *, const char *aTopic, const PRUnichar *) return NS_OK; } -//////////////////////////////////////////////////////////////////////////////// -//// mozIStorageServiceQuotaManagement - -NS_IMETHODIMP -Service::OpenDatabaseWithVFS(nsIFile *aDatabaseFile, - const nsACString &aVFSName, - mozIStorageConnection **_connection) -{ - NS_ENSURE_ARG(aDatabaseFile); - - // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility - // reasons. - int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | - SQLITE_OPEN_CREATE; - nsRefPtr msc = new Connection(this, flags); - NS_ENSURE_TRUE(msc, NS_ERROR_OUT_OF_MEMORY); - - nsresult rv = msc->initialize(aDatabaseFile, - PromiseFlatCString(aVFSName).get()); - NS_ENSURE_SUCCESS(rv, rv); - - NS_ADDREF(*_connection = msc); - return NS_OK; -} - -NS_IMETHODIMP -Service::SetQuotaForFilenamePattern(const nsACString &aPattern, - int64_t aSizeLimit, - mozIStorageQuotaCallback *aCallback, - nsISupports *aUserData) -{ - NS_ENSURE_FALSE(aPattern.IsEmpty(), NS_ERROR_INVALID_ARG); - - nsAutoPtr data; - if (aSizeLimit && aCallback) { - data = new QuotaCallbackData(aCallback, aUserData); - } - - int rc = ::sqlite3_quota_set(PromiseFlatCString(aPattern).get(), - aSizeLimit, QuotaCallbackData::Callback, - data, QuotaCallbackData::Destroy); - NS_ENSURE_TRUE(rc == SQLITE_OK, convertResultCode(rc)); - - data.forget(); - return NS_OK; -} - -NS_IMETHODIMP -Service::UpdateQuotaInformationForFile(nsIFile *aFile) -{ - NS_ENSURE_ARG_POINTER(aFile); - - nsString path; - nsresult rv = aFile->GetPath(path); - NS_ENSURE_SUCCESS(rv, rv); - - int rc = ::sqlite3_quota_file(NS_ConvertUTF16toUTF8(path).get()); - NS_ENSURE_TRUE(rc == SQLITE_OK, convertResultCode(rc)); - - return NS_OK; -} - } // namespace storage } // namespace mozilla diff --git a/storage/src/mozStorageService.h b/storage/src/mozStorageService.h index 21c1ff892ca..3f5a546695e 100644 --- a/storage/src/mozStorageService.h +++ b/storage/src/mozStorageService.h @@ -15,7 +15,6 @@ #include "mozilla/Mutex.h" #include "mozIStorageService.h" -#include "mozIStorageServiceQuotaManagement.h" class nsIMemoryReporter; class nsIMemoryMultiReporter; @@ -28,7 +27,6 @@ namespace storage { class Connection; class Service : public mozIStorageService , public nsIObserver - , public mozIStorageServiceQuotaManagement { public: /** @@ -58,7 +56,6 @@ public: NS_DECL_ISUPPORTS NS_DECL_MOZISTORAGESERVICE NS_DECL_NSIOBSERVER - NS_DECL_MOZISTORAGESERVICEQUOTAMANAGEMENT /** * Obtains an already AddRefed pointer to XPConnect. This is used by diff --git a/toolkit/toolkit-makefiles.sh b/toolkit/toolkit-makefiles.sh index 6a7d71412e9..8f1bbe0f7f5 100644 --- a/toolkit/toolkit-makefiles.sh +++ b/toolkit/toolkit-makefiles.sh @@ -68,6 +68,7 @@ MAKEFILES_dom=" dom/plugins/base/Makefile dom/plugins/ipc/Makefile dom/power/Makefile + dom/quota/Makefile dom/settings/Makefile dom/sms/Makefile dom/sms/interfaces/Makefile