Index: src/attach.c
==================================================================
--- src/attach.c
+++ src/attach.c
@@ -74,10 +74,12 @@
char *zErr = 0;
unsigned int flags;
Db *aNew;
char *zErrDyn = 0;
sqlite3_vfs *pVfs;
+ const char *zVfs = db->pVfs->zName; /* Name of default (main) VFS */
+ int btflags = 0;
UNUSED_PARAMETER(NotUsed);
zFile = (const char *)sqlite3_value_text(argv[0]);
zName = (const char *)sqlite3_value_text(argv[1]);
@@ -127,20 +129,20 @@
/* Open the database file. If the btree is successfully opened, use
** it to obtain the database schema. At this point the schema may
** or may not be initialised.
*/
flags = db->openFlags;
- rc = sqlite3ParseUri(db->pVfs->zName, zFile, &flags, &pVfs, &zPath, &zErr);
+ rc = sqlite3ParseUri(zVfs, zFile, &flags, &btflags, &pVfs, &zPath, &zErr);
if( rc!=SQLITE_OK ){
if( rc==SQLITE_NOMEM ) db->mallocFailed = 1;
sqlite3_result_error(context, zErr, -1);
sqlite3_free(zErr);
return;
}
assert( pVfs );
flags |= SQLITE_OPEN_MAIN_DB;
- rc = sqlite3BtreeOpen(pVfs, zPath, db, &aNew->pBt, 0, flags);
+ rc = sqlite3BtreeOpen(pVfs, zPath, db, &aNew->pBt, btflags, flags);
sqlite3_free( zPath );
db->nDb++;
if( rc==SQLITE_CONSTRAINT ){
rc = SQLITE_ERROR;
zErrDyn = sqlite3MPrintf(db, "database is already attached");
Index: src/btree.h
==================================================================
--- src/btree.h
+++ src/btree.h
@@ -59,10 +59,11 @@
#define BTREE_OMIT_JOURNAL 1 /* Do not create or use a rollback journal */
#define BTREE_NO_READLOCK 2 /* Omit readlocks on readonly files */
#define BTREE_MEMORY 4 /* This is an in-memory DB */
#define BTREE_SINGLE 8 /* The file contains at most 1 b-tree */
#define BTREE_UNORDERED 16 /* Use of a hash implementation is OK */
+#define BTREE_READONLYSHM 32 /* Read-only SHM access is acceptable */
int sqlite3BtreeClose(Btree*);
int sqlite3BtreeSetCacheSize(Btree*,int);
int sqlite3BtreeSetSafetyLevel(Btree*,int,int,int);
int sqlite3BtreeSyncDisabled(Btree*);
Index: src/main.c
==================================================================
--- src/main.c
+++ src/main.c
@@ -1813,10 +1813,15 @@
** query parameter. The second argument contains the URI (or non-URI filename)
** itself. When this function is called the *pFlags variable should contain
** the default flags to open the database handle with. The value stored in
** *pFlags may be updated before returning if the URI filename contains
** "cache=xxx" or "mode=xxx" query parameters.
+**
+** The third argument, pBtflags, points to an integer containing the flags
+** that will be passed as the 5th argument to sqlite3BtreeOpen (BTREE_XXX
+** flags). This value will be edited if the URI filename contains a
+** "readonly_shm=1" or "readonly_shm=0" query parameter.
**
** If successful, SQLITE_OK is returned. In this case *ppVfs is set to point to
** the VFS that should be used to open the database file. *pzFile is set to
** point to a buffer containing the name of the file to open. It is the
** responsibility of the caller to eventually call sqlite3_free() to release
@@ -1829,10 +1834,11 @@
*/
int sqlite3ParseUri(
const char *zDefaultVfs, /* VFS to use if no "vfs=xxx" query option */
const char *zUri, /* Nul-terminated URI to parse */
unsigned int *pFlags, /* IN/OUT: SQLITE_OPEN_XXX flags */
+ int *pBtflags, /* IN/OUT: BTREE_XXX flags */
sqlite3_vfs **ppVfs, /* OUT: VFS to use */
char **pzFile, /* OUT: Filename component of URI */
char **pzErrMsg /* OUT: Error message (if rc!=SQLITE_OK) */
){
int rc = SQLITE_OK;
@@ -1944,10 +1950,16 @@
char *zVal = &zOpt[nOpt+1];
int nVal = sqlite3Strlen30(zVal);
if( nOpt==3 && memcmp("vfs", zOpt, 3)==0 ){
zVfs = zVal;
+ }else if( nOpt==12 && memcmp("readonly_shm", zOpt, 12)==0 ){
+ if( sqlite3Atoi(zVal) ){
+ *pBtflags |= BTREE_READONLYSHM;
+ }else{
+ *pBtflags &= ~BTREE_READONLYSHM;
+ }
}else{
struct OpenMode {
const char *z;
int mode;
} *aMode = 0;
@@ -2047,10 +2059,11 @@
sqlite3 *db; /* Store allocated handle here */
int rc; /* Return code */
int isThreadsafe; /* True for threadsafe connections */
char *zOpen = 0; /* Filename argument to pass to BtreeOpen() */
char *zErrMsg = 0; /* Error message from sqlite3ParseUri() */
+ int btflags = 0; /* Mask of BTREE_XXX flags */
*ppDb = 0;
#ifndef SQLITE_OMIT_AUTOINIT
rc = sqlite3_initialize();
if( rc ) return rc;
@@ -2175,20 +2188,21 @@
createCollation(db, "NOCASE", SQLITE_UTF8, SQLITE_COLL_NOCASE, 0,
nocaseCollatingFunc, 0);
/* Parse the filename/URI argument. */
db->openFlags = flags;
- rc = sqlite3ParseUri(zVfs, zFilename, &flags, &db->pVfs, &zOpen, &zErrMsg);
+ rc = sqlite3ParseUri(
+ zVfs, zFilename, &flags, &btflags, &db->pVfs, &zOpen, &zErrMsg);
if( rc!=SQLITE_OK ){
if( rc==SQLITE_NOMEM ) db->mallocFailed = 1;
sqlite3Error(db, rc, zErrMsg ? "%s" : 0, zErrMsg);
sqlite3_free(zErrMsg);
goto opendb_out;
}
/* Open the backend database driver */
- rc = sqlite3BtreeOpen(db->pVfs, zOpen, db, &db->aDb[0].pBt, 0,
+ rc = sqlite3BtreeOpen(db->pVfs, zOpen, db, &db->aDb[0].pBt, btflags,
flags | SQLITE_OPEN_MAIN_DB);
if( rc!=SQLITE_OK ){
if( rc==SQLITE_IOERR_NOMEM ){
rc = SQLITE_NOMEM;
}
Index: src/os_unix.c
==================================================================
--- src/os_unix.c
+++ src/os_unix.c
@@ -210,10 +210,11 @@
int lastErrno; /* The unix errno from last I/O error */
void *lockingContext; /* Locking style specific state */
UnixUnusedFd *pUnused; /* Pre-allocated UnixUnusedFd */
const char *zPath; /* Name of the file */
unixShm *pShm; /* Shared memory segment information */
+ int readOnlyShm; /* True to open shared-memory read-only */
int szChunk; /* Configured by FCNTL_CHUNK_SIZE */
#if SQLITE_ENABLE_LOCKING_STYLE
int openFlags; /* The flags specified at open() */
#endif
#if SQLITE_ENABLE_LOCKING_STYLE || defined(__APPLE__)
@@ -3471,10 +3472,14 @@
return SQLITE_OK;
}
case SQLITE_FCNTL_SIZE_HINT: {
return fcntlSizeHint((unixFile *)id, *(i64 *)pArg);
}
+ case SQLITE_FCNTL_READONLY_SHM: {
+ ((unixFile*)id)->readOnlyShm = (pArg!=0);
+ return SQLITE_OK;
+ }
#ifndef NDEBUG
/* The pager calls this method to signal that it has done
** a rollback and that the database is therefore unchanged and
** it hence it is OK for the transaction change counter to be
** unchanged.
@@ -3773,10 +3778,11 @@
int szRegion; /* Size of shared-memory regions */
int nRegion; /* Size of array apRegion */
char **apRegion; /* Array of mapped shared-memory regions */
int nRef; /* Number of unixShm objects pointing to this */
unixShm *pFirst; /* All unixShm objects pointing to this */
+ u8 readOnly; /* True if this is a read-only mapping */
#ifdef SQLITE_DEBUG
u8 exclMask; /* Mask of exclusive locks held */
u8 sharedMask; /* Mask of shared locks held */
u8 nextShmId; /* Next available unixShm.id value */
#endif
@@ -4030,31 +4036,50 @@
rc = SQLITE_NOMEM;
goto shm_open_err;
}
if( pInode->bProcessLock==0 ){
- pShmNode->h = robust_open(zShmFilename, O_RDWR|O_CREAT,
- (sStat.st_mode & 0777));
+ int flags = (pDbFd->readOnlyShm ? O_RDONLY : O_RDWR|O_CREAT);
+ pShmNode->h = robust_open(zShmFilename, flags, (sStat.st_mode & 0777));
if( pShmNode->h<0 ){
rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zShmFilename);
goto shm_open_err;
}
+ pShmNode->readOnly = pDbFd->readOnlyShm;
/* Check to see if another process is holding the dead-man switch.
- ** If not, truncate the file to zero length.
- */
- rc = SQLITE_OK;
- if( unixShmSystemLock(pShmNode, F_WRLCK, UNIX_SHM_DMS, 1)==SQLITE_OK ){
- if( robust_ftruncate(pShmNode->h, 0) ){
- rc = unixLogError(SQLITE_IOERR_SHMOPEN, "ftruncate", zShmFilename);
- }
- }
- if( rc==SQLITE_OK ){
- rc = unixShmSystemLock(pShmNode, F_RDLCK, UNIX_SHM_DMS, 1);
- }
- if( rc ) goto shm_open_err;
- }
+ ** If not, zero the first few bytes of the shared-memory file to make
+ ** sure it is not mistaken for valid by code in wal.c. Except, if this
+ ** is a read-only connection to the shared-memory then it is not possible
+ ** to check check if another process is holding a read-lock on the DMS
+ ** byte, as we cannot attempt a write-lock via a read-only file
+ ** descriptor. So in this case, we just assume the shared-memory
+ ** contents are Ok and proceed. */
+ if( pShmNode->readOnly==0 ){
+ rc = SQLITE_OK;
+ if( unixShmSystemLock(pShmNode, F_WRLCK, UNIX_SHM_DMS, 1)==SQLITE_OK ){
+ if( pDbFd->readOnlyShm ){
+ rc = SQLITE_IOERR_SHMOPEN;
+ }else if( 4!=osWrite(pShmNode->h, "\00\00\00\00", 4) ){
+ rc = unixLogError(SQLITE_IOERR_SHMOPEN, "ftruncate", zShmFilename);
+ }
+ }
+ if( rc==SQLITE_OK ){
+ rc = unixShmSystemLock(pShmNode, F_RDLCK, UNIX_SHM_DMS, 1);
+ }
+ if( rc ) goto shm_open_err;
+ }
+ }
+ }
+
+ /* If the unixShmNode is read-only, but SQLITE_FCNTL_READONLY_SHM has not
+ ** been set for file-descriptor pDbFd, return an error. The wal.c module
+ ** will then call this function again with SQLITE_FCNTL_READONLY_SHM set.
+ */
+ else if( pShmNode->readOnly && !pDbFd->readOnlyShm ){
+ rc = SQLITE_IOERR_SHMOPEN;
+ goto shm_open_err;
}
/* Make the new connection a child of the unixShmNode */
p->pShmNode = pShmNode;
#ifdef SQLITE_DEBUG
@@ -4173,11 +4198,11 @@
}
pShmNode->apRegion = apNew;
while(pShmNode->nRegion<=iRegion){
void *pMem;
if( pShmNode->h>=0 ){
- pMem = mmap(0, szRegion, PROT_READ|PROT_WRITE,
+ pMem = mmap(0, szRegion, PROT_READ|(!pShmNode->readOnly?PROT_WRITE:0),
MAP_SHARED, pShmNode->h, pShmNode->nRegion*szRegion
);
if( pMem==MAP_FAILED ){
rc = unixLogError(SQLITE_IOERR_SHMMAP, "mmap", pShmNode->zFilename);
goto shmpage_out;
Index: src/pager.c
==================================================================
--- src/pager.c
+++ src/pager.c
@@ -618,10 +618,11 @@
u8 ckptSyncFlags; /* SYNC_NORMAL or SYNC_FULL for checkpoint */
u8 syncFlags; /* SYNC_NORMAL or SYNC_FULL otherwise */
u8 tempFile; /* zFilename is a temporary file */
u8 readOnly; /* True for a read-only database */
u8 memDb; /* True to inhibit all file I/O */
+ u8 readOnlyShm; /* True if read-only shm access is Ok */
/**************************************************************************
** The following block contains those class members that change during
** routine opertion. Class members not in this block are either fixed
** when the pager is first created or else only change when there is a
@@ -3015,22 +3016,23 @@
** other writers or checkpointers.
*/
static int pagerBeginReadTransaction(Pager *pPager){
int rc; /* Return code */
int changed = 0; /* True if cache must be reset */
+ Wal *pWal = pPager->pWal;
assert( pagerUseWal(pPager) );
assert( pPager->eState==PAGER_OPEN || pPager->eState==PAGER_READER );
/* sqlite3WalEndReadTransaction() was not called for the previous
** transaction in locking_mode=EXCLUSIVE. So call it now. If we
** are in locking_mode=NORMAL and EndRead() was previously called,
** the duplicate call is harmless.
*/
- sqlite3WalEndReadTransaction(pPager->pWal);
+ sqlite3WalEndReadTransaction(pWal);
- rc = sqlite3WalBeginReadTransaction(pPager->pWal, &changed);
+ rc = sqlite3WalBeginReadTransaction(pWal, pPager->readOnlyShm, &changed);
if( rc!=SQLITE_OK || changed ){
pager_reset(pPager);
}
return rc;
@@ -4414,10 +4416,11 @@
#endif
sqlite3_free(zPathname);
}
pPager->pVfs = pVfs;
pPager->vfsFlags = vfsFlags;
+ pPager->readOnlyShm = (flags & PAGER_READONLYSHM)!=0;
/* Open the pager file.
*/
if( zFilename && zFilename[0] ){
int fout = 0; /* VFS flags returned by xOpen() */
Index: src/pager.h
==================================================================
--- src/pager.h
+++ src/pager.h
@@ -58,10 +58,11 @@
** NOTE: These values must match the corresponding BTREE_ values in btree.h.
*/
#define PAGER_OMIT_JOURNAL 0x0001 /* Do not use a rollback journal */
#define PAGER_NO_READLOCK 0x0002 /* Omit readlocks on readonly files */
#define PAGER_MEMORY 0x0004 /* In-memory database */
+#define PAGER_READONLYSHM 0x0020 /* Read-only SHM access is acceptable */
/*
** Valid values for the second argument to sqlite3PagerLockingMode().
*/
#define PAGER_LOCKINGMODE_QUERY -1
Index: src/sqlite.h.in
==================================================================
--- src/sqlite.h.in
+++ src/sqlite.h.in
@@ -453,10 +453,13 @@
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
#define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8))
#define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8))
+#define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8))
+#define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2<<8))
+
/*
** CAPI3REF: Flags For File Open Operations
**
** These bit values are intended for use in the
** 3rd parameter to the [sqlite3_open_v2()] interface and
@@ -732,19 +735,41 @@
** when [PRAGMA synchronous | PRAGMA synchronous=OFF] is set, but most
** VFSes do not need this signal and should silently ignore this opcode.
** Applications should not call [sqlite3_file_control()] with this
** opcode as doing so may disrupt the operation of the specialized VFSes
** that do require it.
+**
+** The [SQLITE_FCNTL_READONLY_SHM] may be generated internally by SQLite if
+** the "readonly_shm=1" URI option is specified when the database is opened.
+** The fourth argument passed to the VFS xFileControl methods is a pointer
+** to a variable of type "int" containing the value 1 or 0. If the variable
+** contains the value 1, then this indicates to the VFS that a read-only
+** mapping of the shared-memory region is acceptable. If it is set to 0, then
+** this indicates that a read-write mapping is required (as normal). If
+** a read-only mapping is returned, then the VFS may also return read-only
+** mappings for any subsequent requests via the same file-descriptor -
+** regardless of the value most recently configured using
+** SQLITE_FCNTL_READONLY_SHM.
+**
+** In practice, if "readonly_shm=1" is specified and the first attempt to
+** map a shared-memory region fails, then this file-control is invoked with
+** the argument variable set to 1 and a second attempt to map the shared-memory
+** region is made. If this mapping succeeds, then the connection continues
+** with the read-only mapping. Otherwise, if it fails, SQLITE_CANTOPEN is
+** returned to the caller. Whether or not the second (read-only) mapping
+** attempt succeeds, the file-control is invoked again with the argument
+** variable set to 0.
*/
#define SQLITE_FCNTL_LOCKSTATE 1
#define SQLITE_GET_LOCKPROXYFILE 2
#define SQLITE_SET_LOCKPROXYFILE 3
#define SQLITE_LAST_ERRNO 4
#define SQLITE_FCNTL_SIZE_HINT 5
#define SQLITE_FCNTL_CHUNK_SIZE 6
#define SQLITE_FCNTL_FILE_POINTER 7
#define SQLITE_FCNTL_SYNC_OMITTED 8
+#define SQLITE_FCNTL_READONLY_SHM 9
/*
** CAPI3REF: Mutex Handle
**
@@ -2454,11 +2479,11 @@
** is a drive specification (e.g. "C:").
**
** [[core URI query parameters]]
** The query component of a URI may contain parameters that are interpreted
** either by SQLite itself, or by a [VFS | custom VFS implementation].
-** SQLite interprets the following three query parameters:
+** SQLite interprets the following four query parameters:
**
**
** - vfs: ^The "vfs" parameter may be used to specify the name of
** a VFS object that provides the operating system interface that should
** be used to access the database file on disk. ^If this option is set to
@@ -2486,10 +2511,26 @@
** sqlite3_open_v2(). ^Setting the cache parameter to "private" is
** equivalent to setting the SQLITE_OPEN_PRIVATECACHE bit.
** ^If sqlite3_open_v2() is used and the "cache" parameter is present in
** a URI filename, its value overrides any behaviour requested by setting
** SQLITE_OPEN_PRIVATECACHE or SQLITE_OPEN_SHAREDCACHE flag.
+**
+**
- readonly_shm: ^The readonly_shm parameter may be set to
+** either "1" or "0". Setting it to "1" indicates that if the database
+** is in WAL mode and read-write access to the associated shared
+** memory region cannot be obtained, then an attempt should be made to open
+** the shared-memory in read-only mode instead. If there exist one or
+** more other database clients that have read-write connections to the
+** database shared-memory, then a read-only shared-memory connection works
+** fine. However, if there exist no clients with read-write connections
+** to the shared-memory and the most recent such crashed or was interrupted
+** by a power failure, then it is possible for a database client using a
+** read-only connection to return incorrect data, incorrectly report
+** database corruption, or return an SQLITE_READONLY error. Or if the
+** most recent read-write connection shut down cleanly, it may not be
+** possible to open the shared-memory in read-only mode at all, and SQLite
+** will return SQLITE_CANTOPEN.
**
**
** ^Specifying an unknown parameter in the query component of a URI is not an
** error. Future versions of SQLite might understand additional query
** parameters. See "[query parameters with special meaning to SQLite]" for
Index: src/sqliteInt.h
==================================================================
--- src/sqliteInt.h
+++ src/sqliteInt.h
@@ -2671,11 +2671,11 @@
void sqlite3AddCheckConstraint(Parse*, Expr*);
void sqlite3AddColumnType(Parse*,Token*);
void sqlite3AddDefaultValue(Parse*,ExprSpan*);
void sqlite3AddCollateType(Parse*, Token*);
void sqlite3EndTable(Parse*,Token*,Token*,Select*);
-int sqlite3ParseUri(const char*,const char*,unsigned int*,
+int sqlite3ParseUri(const char*,const char*,unsigned int*,int*,
sqlite3_vfs**,char**,char **);
Bitvec *sqlite3BitvecCreate(u32);
int sqlite3BitvecTest(Bitvec*, u32);
int sqlite3BitvecSet(Bitvec*, u32);
Index: src/test1.c
==================================================================
--- src/test1.c
+++ src/test1.c
@@ -161,12 +161,13 @@
case SQLITE_IOERR_NOMEM: zName = "SQLITE_IOERR_NOMEM"; break;
case SQLITE_IOERR_ACCESS: zName = "SQLITE_IOERR_ACCESS"; break;
case SQLITE_IOERR_CHECKRESERVEDLOCK:
zName = "SQLITE_IOERR_CHECKRESERVEDLOCK"; break;
case SQLITE_IOERR_LOCK: zName = "SQLITE_IOERR_LOCK"; break;
+ case SQLITE_READONLY_RECOVERY: zName = "SQLITE_READONLY_RECOVERY"; break;
+ case SQLITE_READONLY_CANTLOCK: zName = "SQLITE_READONLY_CANTLOCK"; break;
case SQLITE_CORRUPT_VTAB: zName = "SQLITE_CORRUPT_VTAB"; break;
- zName = "SQLITE_IOERR_CHECKRESERVEDLOCK"; break;
default: zName = "SQLITE_Unknown"; break;
}
return zName;
}
#define t1ErrorName sqlite3TestErrorName
Index: src/wal.c
==================================================================
--- src/wal.c
+++ src/wal.c
@@ -404,10 +404,18 @@
)
/*
** An open write-ahead log file is represented by an instance of the
** following object.
+**
+** The readOnlyShm variable is normally set to 0. If it is set to 1, then
+** the connection to shared-memory is read-only. This means it cannot
+** be written at all (even when read-locking the database). If it is set
+** to 2, then the shared-memory region is not yet open, but a read-only
+** connection is acceptable. In this case when the shared-memory is opened
+** (see function walIndexPage()), readOnlyShm is set to either 0 or 1 as
+** appropriate.
*/
struct Wal {
sqlite3_vfs *pVfs; /* The VFS used to create pDbFd */
sqlite3_file *pDbFd; /* File handle for the database file */
sqlite3_file *pWalFd; /* File handle for WAL file */
@@ -419,10 +427,11 @@
i16 readLock; /* Which read lock is being held. -1 for none */
u8 exclusiveMode; /* Non-zero if connection is in exclusive mode */
u8 writeLock; /* True if in a write transaction */
u8 ckptLock; /* True if holding a checkpoint lock */
u8 readOnly; /* True if the WAL file is open read-only */
+ u8 readOnlyShm; /* True if the SHM file is open read-only */
WalIndexHdr hdr; /* Wal-index header for current transaction */
const char *zWalName; /* Name of WAL file */
u32 nCkpt; /* Checkpoint sequence counter in the wal-header */
#ifdef SQLITE_DEBUG
u8 lockError; /* True if a locking error has occurred */
@@ -527,15 +536,27 @@
if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM;
}else{
rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ,
pWal->writeLock, (void volatile **)&pWal->apWiData[iPage]
);
+ if( rc==SQLITE_CANTOPEN && pWal->readOnlyShm>1 ){
+ assert( iPage==0 );
+ sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_READONLY_SHM, (void*)1);
+ rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ,
+ pWal->writeLock, (void volatile **)&pWal->apWiData[iPage]
+ );
+ if( rc==SQLITE_OK ){
+ pWal->readOnly = pWal->readOnlyShm = 1;
+ }
+ sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_READONLY_SHM, (void*)0);
+ }
}
}
-
*ppPage = pWal->apWiData[iPage];
+
assert( iPage==0 || *ppPage || rc!=SQLITE_OK );
+ if( pWal->readOnlyShm>1 ) pWal->readOnlyShm = 0;
return rc;
}
/*
** Return a pointer to the WalCkptInfo structure in the wal-index.
@@ -771,19 +792,21 @@
SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED);
WALTRACE(("WAL%p: release SHARED-%s\n", pWal, walLockName(lockIdx)));
}
static int walLockExclusive(Wal *pWal, int lockIdx, int n){
int rc;
+ assert( pWal->readOnlyShm==0 );
if( pWal->exclusiveMode ) return SQLITE_OK;
rc = sqlite3OsShmLock(pWal->pDbFd, lockIdx, n,
SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE);
WALTRACE(("WAL%p: acquire EXCLUSIVE-%s cnt=%d %s\n", pWal,
walLockName(lockIdx), n, rc ? "failed" : "ok"));
VVA_ONLY( pWal->lockError = (u8)(rc!=SQLITE_OK && rc!=SQLITE_BUSY); )
return rc;
}
static void walUnlockExclusive(Wal *pWal, int lockIdx, int n){
+ assert( pWal->readOnlyShm==0 );
if( pWal->exclusiveMode ) return;
(void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, n,
SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE);
WALTRACE(("WAL%p: release EXCLUSIVE-%s cnt=%d\n", pWal,
walLockName(lockIdx), n));
@@ -1055,10 +1078,11 @@
*/
assert( pWal->ckptLock==1 || pWal->ckptLock==0 );
assert( WAL_ALL_BUT_WRITE==WAL_WRITE_LOCK+1 );
assert( WAL_CKPT_LOCK==WAL_ALL_BUT_WRITE );
assert( pWal->writeLock );
+ assert( pWal->readOnlyShm==0 );
iLock = WAL_ALL_BUT_WRITE + pWal->ckptLock;
nLock = SQLITE_SHM_NLOCK - iLock;
rc = walLockExclusive(pWal, iLock, nLock);
if( rc ){
return rc;
@@ -1903,37 +1927,45 @@
rc = walIndexPage(pWal, 0, &page0);
if( rc!=SQLITE_OK ){
return rc;
};
assert( page0 || pWal->writeLock==0 );
+ assert( pWal->readOnlyShm==0 || pWal->readOnlyShm==1 );
/* If the first page of the wal-index has been mapped, try to read the
** wal-index header immediately, without holding any lock. This usually
** works, but may fail if the wal-index header is corrupt or currently
** being modified by another thread or process.
*/
badHdr = (page0 ? walIndexTryHdr(pWal, pChanged) : 1);
/* If the first attempt failed, it might have been due to a race
- ** with a writer. So get a WRITE lock and try again.
+ ** with a writer. So lock the WAL_WRITE_LOCK byte and try again.
*/
assert( badHdr==0 || pWal->writeLock==0 );
- if( badHdr && SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1)) ){
- pWal->writeLock = 1;
- if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){
- badHdr = walIndexTryHdr(pWal, pChanged);
- if( badHdr ){
- /* If the wal-index header is still malformed even while holding
- ** a WRITE lock, it can only mean that the header is corrupted and
- ** needs to be reconstructed. So run recovery to do exactly that.
- */
- rc = walIndexRecover(pWal);
- *pChanged = 1;
- }
- }
- pWal->writeLock = 0;
- walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
+ if( badHdr ){
+ if( pWal->readOnlyShm ){
+ if( SQLITE_OK==(rc = walLockShared(pWal, WAL_WRITE_LOCK)) ){
+ walUnlockShared(pWal, WAL_WRITE_LOCK);
+ rc = SQLITE_READONLY_RECOVERY;
+ }
+ }else if( SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1)) ){
+ pWal->writeLock = 1;
+ if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){
+ badHdr = walIndexTryHdr(pWal, pChanged);
+ if( badHdr ){
+ /* If the wal-index header is still malformed even while holding
+ ** a WRITE lock, it can only mean that the header is corrupted and
+ ** needs to be reconstructed. So run recovery to do exactly that.
+ */
+ rc = walIndexRecover(pWal);
+ *pChanged = 1;
+ }
+ }
+ pWal->writeLock = 0;
+ walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
+ }
}
/* If the header is read successfully, check the version number to make
** sure the wal-index was not constructed with some future format that
** this version of SQLite cannot understand.
@@ -2116,11 +2148,11 @@
mxI = i;
}
}
/* There was once an "if" here. The extra "{" is to preserve indentation. */
{
- if( mxReadMark < pWal->hdr.mxFrame || mxI==0 ){
+ if( pWal->readOnlyShm==0 && (mxReadMark < pWal->hdr.mxFrame || mxI==0) ){
for(i=1; iaReadMark[i] = pWal->hdr.mxFrame;
mxI = i;
@@ -2131,11 +2163,12 @@
}
}
}
if( mxI==0 ){
assert( rc==SQLITE_BUSY );
- return WAL_RETRY;
+ assert( rc==SQLITE_BUSY || (pWal->readOnlyShm && rc==SQLITE_OK) );
+ return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK;
}
rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
if( rc ){
return rc==SQLITE_BUSY ? WAL_RETRY : rc;
@@ -2186,17 +2219,22 @@
** If the database contents have changes since the previous read
** transaction, then *pChanged is set to 1 before returning. The
** Pager layer will use this to know that is cache is stale and
** needs to be flushed.
*/
-int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
+int sqlite3WalBeginReadTransaction(Wal *pWal, int readOnlyShm, int *pChanged){
int rc; /* Return code */
int cnt = 0; /* Number of TryBeginRead attempts */
+ if( pWal->nWiData==0 || pWal->apWiData[0]==0 ){
+ assert( readOnlyShm==0 || readOnlyShm==1 );
+ pWal->readOnlyShm = readOnlyShm*2;
+ }
do{
rc = walTryBeginRead(pWal, pChanged, 0, ++cnt);
}while( rc==WAL_RETRY );
+ assert( rc || pWal->readOnlyShm==0 || (readOnlyShm && pWal->readOnlyShm==1) );
testcase( (rc&0xff)==SQLITE_BUSY );
testcase( (rc&0xff)==SQLITE_IOERR );
testcase( rc==SQLITE_PROTOCOL );
testcase( rc==SQLITE_OK );
return rc;
@@ -2367,10 +2405,11 @@
assert( pWal->readLock>=0 );
if( pWal->readOnly ){
return SQLITE_READONLY;
}
+ assert( pWal->readOnlyShm==0 );
/* Only one writer allowed at a time. Get the write lock. Return
** SQLITE_BUSY if unable.
*/
rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1);
@@ -2772,10 +2811,14 @@
assert( pWal->ckptLock==0 );
assert( pWal->writeLock==0 );
WALTRACE(("WAL%p: checkpoint begins\n", pWal));
+ if( pWal->readOnlyShm ){
+ return SQLITE_READONLY;
+ }
+
rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1);
if( rc ){
/* Usually this is SQLITE_BUSY meaning that another thread or process
** is already running a checkpoint, or maybe a recovery. But it might
** also be SQLITE_IOERR. */
Index: src/wal.h
==================================================================
--- src/wal.h
+++ src/wal.h
@@ -21,11 +21,11 @@
#ifdef SQLITE_OMIT_WAL
# define sqlite3WalOpen(x,y,z) 0
# define sqlite3WalLimit(x,y)
# define sqlite3WalClose(w,x,y,z) 0
-# define sqlite3WalBeginReadTransaction(y,z) 0
+# define sqlite3WalBeginReadTransaction(x,y,z) 0
# define sqlite3WalEndReadTransaction(z)
# define sqlite3WalRead(v,w,x,y,z) 0
# define sqlite3WalDbsize(y) 0
# define sqlite3WalBeginWriteTransaction(y) 0
# define sqlite3WalEndWriteTransaction(x) 0
@@ -58,11 +58,11 @@
** at an instant in time. sqlite3WalOpenSnapshot gets a read lock and
** preserves the current state even if the other threads or processes
** write to or checkpoint the WAL. sqlite3WalCloseSnapshot() closes the
** transaction and releases the lock.
*/
-int sqlite3WalBeginReadTransaction(Wal *pWal, int *);
+int sqlite3WalBeginReadTransaction(Wal *pWal, int, int *);
void sqlite3WalEndReadTransaction(Wal *pWal);
/* Read a page from the write-ahead log, if it is present. */
int sqlite3WalRead(Wal *pWal, Pgno pgno, int *pInWal, int nOut, u8 *pOut);
Index: test/lock_common.tcl
==================================================================
--- test/lock_common.tcl
+++ test/lock_common.tcl
@@ -53,12 +53,12 @@
proc csql3 {sql} { list [catch { sql3 $sql } msg] $msg }
uplevel set $varname $tn
uplevel $script
- code2 { db2 close }
- code3 { db3 close }
+ catch { code2 { db2 close } }
+ catch { code3 { db3 close } }
catch { close $::code2_chan }
catch { close $::code3_chan }
catch { db close }
}
}
ADDED test/walro.test
Index: test/walro.test
==================================================================
--- /dev/null
+++ test/walro.test
@@ -0,0 +1,159 @@
+# 2011 May 09
+#
+# 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 tests for using WAL databases in read-only mode.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+source $testdir/lock_common.tcl
+set ::testprefix walro
+
+
+do_multiclient_test tn {
+ # These tests are only going to work on unix.
+ #
+ if {$tcl_platform(platform) != "unix"} continue
+
+ # Do not run tests with the connections in the same process.
+ #
+ if {$tn==2} continue
+
+ # Close all connections and delete the database.
+ #
+ code1 { db close }
+ code2 { db2 close }
+ code3 { db3 close }
+ forcedelete test.db
+ forcedelete walro
+
+ foreach c {code1 code2 code3} {
+ $c {
+ sqlite3_shutdown
+ sqlite3_config_uri 1
+ }
+ }
+
+ file mkdir walro
+
+ do_test 1.1.1 {
+ code2 { sqlite3 db2 test.db }
+ sql2 {
+ PRAGMA journal_mode = WAL;
+ CREATE TABLE t1(x, y);
+ INSERT INTO t1 VALUES('a', 'b');
+ }
+ file exists test.db-shm
+ } {1}
+
+ do_test 1.1.2 {
+ file attributes test.db-shm -permissions r--r--r--
+ code1 { sqlite3 db file:test.db?readonly_shm=1 }
+ } {}
+
+ do_test 1.1.3 { sql1 "SELECT * FROM t1" } {a b}
+ do_test 1.1.4 { sql2 "INSERT INTO t1 VALUES('c', 'd')" } {}
+ do_test 1.1.5 { sql1 "SELECT * FROM t1" } {a b c d}
+
+ # Check that the read-only connection cannot write or checkpoint the db.
+ #
+ do_test 1.1.6 {
+ csql1 "INSERT INTO t1 VALUES('e', 'f')"
+ } {1 {attempt to write a readonly database}}
+ do_test 1.1.7 {
+ csql1 "PRAGMA wal_checkpoint"
+ } {1 {attempt to write a readonly database}}
+
+ do_test 1.1.9 { sql2 "INSERT INTO t1 VALUES('e', 'f')" } {}
+ do_test 1.1.10 { sql1 "SELECT * FROM t1" } {a b c d e f}
+
+ do_test 1.1.11 {
+ sql2 {
+ INSERT INTO t1 VALUES('g', 'h');
+ PRAGMA wal_checkpoint;
+ }
+ set {} {}
+ } {}
+ do_test 1.1.12 { sql1 "SELECT * FROM t1" } {a b c d e f g h}
+ do_test 1.1.13 { sql2 "INSERT INTO t1 VALUES('i', 'j')" } {}
+
+ do_test 1.2.1 {
+ code2 { db2 close }
+ code1 { db close }
+ list [file exists test.db-wal] [file exists test.db-shm]
+ } {1 1}
+ do_test 1.2.2 {
+ code1 { sqlite3 db file:test.db?readonly_shm=1 }
+ sql1 { SELECT * FROM t1 }
+ } {a b c d e f g h i j}
+
+ do_test 1.2.3 {
+ code1 { db close }
+ file attributes test.db-shm -permissions rw-r--r--
+ hexio_write test.db-shm 0 01020304
+ file attributes test.db-shm -permissions r--r--r--
+ code1 { sqlite3 db file:test.db?readonly_shm=1 }
+ csql1 { SELECT * FROM t1 }
+ } {1 {attempt to write a readonly database}}
+ do_test 1.2.4 {
+ code1 { sqlite3_extended_errcode db }
+ } {SQLITE_READONLY_RECOVERY}
+
+ do_test 1.2.5 {
+ file attributes test.db-shm -permissions rw-r--r--
+ code2 { sqlite3 db2 test.db }
+ sql2 "SELECT * FROM t1"
+ } {a b c d e f g h i j}
+ file attributes test.db-shm -permissions r--r--r--
+ do_test 1.2.6 { sql1 "SELECT * FROM t1" } {a b c d e f g h i j}
+
+ do_test 1.2.7 {
+ sql2 {
+ PRAGMA wal_checkpoint;
+ INSERT INTO t1 VALUES('k', 'l');
+ }
+ set {} {}
+ } {}
+ do_test 1.2.8 { sql1 "SELECT * FROM t1" } {a b c d e f g h i j k l}
+
+ # Now check that if the readonly_shm option is not supplied, or if it
+ # is set to zero, it is not possible to connect to the database without
+ # read-write access to the shm.
+ do_test 1.3.1 {
+ code1 { db close }
+ code1 { sqlite3 db test.db }
+ csql1 { SELECT * FROM t1 }
+ } {1 {unable to open database file}}
+
+ # Also test that if the -shm file can be opened for read/write access,
+ # it is, even if readonly_shm=1 is present in the URI.
+ do_test 1.3.2.1 {
+ code1 { db close }
+ code2 { db2 close }
+ file exists test.db-shm
+ } {0}
+ do_test 1.3.2.2 {
+ code1 { sqlite3 db file:test.db?readonly_shm=1 }
+ sql1 { SELECT * FROM t1 }
+ } {a b c d e f g h i j k l}
+ do_test 1.3.2.3 {
+ code1 { db close }
+ close [open test.db-shm w]
+ file attributes test.db-shm -permissions r--r--r--
+ code1 { sqlite3 db file:test.db?readonly_shm=1 }
+ csql1 { SELECT * FROM t1 }
+ } {1 {attempt to write a readonly database}}
+ do_test 1.3.2.4 {
+ code1 { sqlite3_extended_errcode db }
+ } {SQLITE_READONLY_RECOVERY}
+}
+
+finish_test