Merge the read-only shared memory branch into trunk. After this merge, an

unprivileged process can open WAL-mode databases owned by another user as
long as a database connection with write permission exists on the database
file and if the readonly_shm=1 URI query parameter is supplied.

FossilOrigin-Name: 19084a6641f77a62110b04ea50e298fe132ea784
This commit is contained in:
drh 2011-06-02 13:04:33 +00:00
commit c9afea68ff
8 changed files with 231 additions and 34 deletions

View File

@ -1,5 +1,5 @@
C Avoid\sunnecessary\sduplication\sof\sSQL\sparameter\snames.
D 2011-06-01T19:16:06.364
C Merge\sthe\sread-only\sshared\smemory\sbranch\sinto\strunk.\s\sAfter\sthis\smerge,\san\nunprivileged\sprocess\scan\sopen\sWAL-mode\sdatabases\sowned\sby\sanother\suser\sas\nlong\sas\sa\sdatabase\sconnection\swith\swrite\spermission\sexists\son\sthe\sdatabase\nfile\sand\sif\sthe\sreadonly_shm=1\sURI\squery\sparameter\sis\ssupplied.
D 2011-06-02T13:04:33.467
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in 11dcc00a8d0e5202def00e81732784fb0cc4fe1d
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@ -163,7 +163,7 @@ F src/os.c 22ac61d06e72a0dac900400147333b07b13d8e1d
F src/os.h 9dbed8c2b9c1f2f2ebabc09e49829d4777c26bf9
F src/os_common.h a8f95b81eca8a1ab8593d23e94f8a35f35d4078f
F src/os_os2.c 4a75888ba3dfc820ad5e8177025972d74d7f2440
F src/os_unix.c 6d4a58d81ad4b782406519f3790202f330e89bb7
F src/os_unix.c fd4e9588ff0ce09720721ce739ab2682202875ae
F src/os_win.c 218b899469e570d46eb8147c2383075f7c026230
F src/pager.c 120550e7ef01dafaa2cbb4a0528c0d87c8f12b41
F src/pager.h 3f8c783de1d4706b40b1ac15b64f5f896bcc78d1
@ -179,14 +179,14 @@ F src/resolve.c 1c0f32b64f8e3f555fe1f732f9d6f501a7f05706
F src/rowset.c 69afa95a97c524ba6faf3805e717b5b7ae85a697
F src/select.c d9d440809025a58547e39f4f268c2a296bfb56ff
F src/shell.c decd04236a7ef26be5ef46d4ea963044bfad9a48
F src/sqlite.h.in 91c63a69eeddbd62182ec00dbfee390016972bdb
F src/sqlite.h.in 2f51e4f58b2b4626fcbd9938580e730cb5fb4985
F src/sqlite3ext.h c90bd5507099f62043832d73f6425d8d5c5da754
F src/sqliteInt.h 6e58c558c57c8f44011736d5fa5295eb3130f9de
F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
F src/status.c 7ac64842c86cec2fc1a1d0e5c16d3beb8ad332bf
F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
F src/tclsqlite.c 501c9a200fd998a268be475be5858febc90b725b
F src/test1.c 9f61b9d23938bc5dd165ec7b95a91c30ce6e66db
F src/test1.c efca486a25fb894988e7a82e84579a4e57388a02
F src/test2.c 80d323d11e909cf0eb1b6fbb4ac22276483bcf31
F src/test3.c 124ff9735fb6bb7d41de180d6bac90e7b1509432
F src/test4.c d1e5a5e904d4b444cf572391fdcb017638e36ff7
@ -245,7 +245,7 @@ F src/vdbeblob.c f024f0bf420f36b070143c32b15cc7287341ffd3
F src/vdbemem.c 0498796b6ffbe45e32960d6a1f5adfb6e419883b
F src/vdbetrace.c 5d0dc3d5fd54878cc8d6d28eb41deb8d5885b114
F src/vtab.c 9ba8c7fdb7d39260c033a402f6032d3e7bc5d336
F src/wal.c ab1d8c5abf904fd6396f90499cbd8c54b8d6961b
F src/wal.c fd63d07233203dd3bd29cbe1ae5c8bb2c34e08fc
F src/wal.h 66b40bd91bc29a5be1c88ddd1f5ade8f3f48728a
F src/walker.c 3112bb3afe1d85dc52317cb1d752055e9a781f8f
F src/where.c 55403ce19c506be6a321c7f129aff693d6103db5
@ -548,7 +548,7 @@ F test/lock4.test c82268c031d39345d05efa672f80b025481b3ae5
F test/lock5.test b2abb5e711bc59b0eae00f6c97a36ec9f458fada
F test/lock6.test ad5b387a3a8096afd3c68a55b9535056431b0cf5
F test/lock7.test 64006c84c1c616657e237c7ad6532b765611cf64
F test/lock_common.tcl d279887a0ab16cdb6d935c1203e64113c5a000e9
F test/lock_common.tcl 0c270b121d40959fa2f3add382200c27045b3d95
F test/lookaside.test 93f07bac140c5bb1d49f3892d2684decafdc7af2
F test/main.test 9d7bbfcc1b52c88ba7b2ba6554068ecf9939f252
F test/make-where7.tcl 05c16b5d4f5d6512881dfec560cb793915932ef9
@ -886,6 +886,7 @@ F test/walfault.test 58fce626359c9376fe35101b5c0f2df8040aa839
F test/walhook.test ed00a40ba7255da22d6b66433ab61fab16a63483
F test/walmode.test 22ddccd073c817ac9ead62b88ac446e8dedc7d2c
F test/walnoshm.test a074428046408f4eb5c6a00e09df8cc97ff93317
F test/walro.test 1f15853383a976ff8bbec78dd44bc15c4e237392
F test/walshared.test 6dda2293880c300baf5d791c307f653094585761
F test/walslow.test d21625e2e99e11c032ce949e8a94661576548933
F test/walthread.test a25a393c068a2b42b44333fa3fdaae9072f1617c
@ -938,7 +939,7 @@ F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
F tool/split-sqlite3c.tcl d9be87f1c340285a3e081eb19b4a247981ed290c
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
P b3aaf715b60b8a338cc6c92dad1ead4a3f7146a3
R 018490a374b5cf414074a5a4bbd3de70
P e704e8690ae35decc9769a45cf8d519ccad8b79d 1f930d7e04cd4a5ff3d91a0e9f1b62114f1cebd2
R fdd310892b7cea51b352170f4a152593
U drh
Z dcf10ad87997753baf45956f9011da61
Z c21bc600f09ce2bc60c48c66dd9bd252

View File

@ -1 +1 @@
e704e8690ae35decc9769a45cf8d519ccad8b79d
19084a6641f77a62110b04ea50e298fe132ea784

View File

@ -3537,7 +3537,8 @@ struct unixShmNode {
char *zFilename; /* Name of the mmapped file */
int h; /* Open file descriptor */
int szRegion; /* Size of shared-memory regions */
int nRegion; /* Size of array apRegion */
u16 nRegion; /* Size of array apRegion */
u8 isReadonly; /* True if read-only */
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 */
@ -3784,8 +3785,17 @@ static int unixOpenSharedMemory(unixFile *pDbFd){
pShmNode->h = robust_open(zShmFilename, O_RDWR|O_CREAT,
(sStat.st_mode & 0777));
if( pShmNode->h<0 ){
rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zShmFilename);
goto shm_open_err;
const char *zRO;
zRO = sqlite3_uri_parameter(pDbFd->zPath, "readonly_shm");
if( zRO && sqlite3GetBoolean(zRO) ){
pShmNode->h = robust_open(zShmFilename, O_RDONLY,
(sStat.st_mode & 0777));
pShmNode->isReadonly = 1;
}
if( pShmNode->h<0 ){
rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zShmFilename);
goto shm_open_err;
}
}
/* Check to see if another process is holding the dead-man switch.
@ -3924,7 +3934,8 @@ static int unixShmMap(
while(pShmNode->nRegion<=iRegion){
void *pMem;
if( pShmNode->h>=0 ){
pMem = mmap(0, szRegion, PROT_READ|PROT_WRITE,
pMem = mmap(0, szRegion,
pShmNode->isReadonly ? PROT_READ : PROT_READ|PROT_WRITE,
MAP_SHARED, pShmNode->h, pShmNode->nRegion*szRegion
);
if( pMem==MAP_FAILED ){
@ -3950,6 +3961,7 @@ shmpage_out:
}else{
*pp = 0;
}
if( pShmNode->isReadonly && rc==SQLITE_OK ) rc = SQLITE_READONLY;
sqlite3_mutex_leave(pShmNode->mutex);
return rc;
}

View File

@ -454,6 +454,8 @@ int sqlite3_exec(
#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

View File

@ -164,6 +164,8 @@ const char *sqlite3TestErrorName(int rc){
zName = "SQLITE_IOERR_CHECKRESERVEDLOCK"; break;
case SQLITE_IOERR_LOCK: zName = "SQLITE_IOERR_LOCK"; break;
case SQLITE_CORRUPT_VTAB: zName = "SQLITE_CORRUPT_VTAB"; break;
case SQLITE_READONLY_RECOVERY: zName = "SQLITE_READONLY_RECOVERY"; break;
case SQLITE_READONLY_CANTLOCK: zName = "SQLITE_READONLY_CANTLOCK"; break;
default: zName = "SQLITE_Unknown"; break;
}
return zName;

View File

@ -420,7 +420,7 @@ struct Wal {
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 readOnly; /* WAL_RDWR, WAL_RDONLY, or WAL_SHM_RDONLY */
WalIndexHdr hdr; /* Wal-index header for current transaction */
const char *zWalName; /* Name of WAL file */
u32 nCkpt; /* Checkpoint sequence counter in the wal-header */
@ -436,6 +436,13 @@ struct Wal {
#define WAL_EXCLUSIVE_MODE 1
#define WAL_HEAPMEMORY_MODE 2
/*
** Possible values for WAL.readOnly
*/
#define WAL_RDWR 0 /* Normal read/write connection */
#define WAL_RDONLY 1 /* The WAL file is readonly */
#define WAL_SHM_RDONLY 2 /* The SHM file is readonly */
/*
** Each page of the wal-index mapping contains a hash-table made up of
** an array of HASHTABLE_NSLOT elements of the following type.
@ -529,6 +536,10 @@ static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){
rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ,
pWal->writeLock, (void volatile **)&pWal->apWiData[iPage]
);
if( rc==SQLITE_READONLY ){
pWal->readOnly |= WAL_SHM_RDONLY;
rc = SQLITE_OK;
}
}
}
@ -1276,7 +1287,7 @@ int sqlite3WalOpen(
flags = (SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_WAL);
rc = sqlite3OsOpen(pVfs, zWalName, pRet->pWalFd, flags, &flags);
if( rc==SQLITE_OK && flags&SQLITE_OPEN_READONLY ){
pRet->readOnly = 1;
pRet->readOnly = WAL_RDONLY;
}
if( rc!=SQLITE_OK ){
@ -1917,21 +1928,28 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){
** with a writer. So get a WRITE lock 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;
if( badHdr ){
if( pWal->readOnly & WAL_SHM_RDONLY ){
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);
}
pWal->writeLock = 0;
walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
}
/* If the header is read successfully, check the version number to make
@ -2118,7 +2136,9 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
}
/* There was once an "if" here. The extra "{" is to preserve indentation. */
{
if( mxReadMark < pWal->hdr.mxFrame || mxI==0 ){
if( (pWal->readOnly & WAL_SHM_RDONLY)==0
&& (mxReadMark<pWal->hdr.mxFrame || mxI==0)
){
for(i=1; i<WAL_NREADER; i++){
rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1);
if( rc==SQLITE_OK ){
@ -2133,7 +2153,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
}
if( mxI==0 ){
assert( rc==SQLITE_BUSY );
return WAL_RETRY;
return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK;
}
rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
@ -2775,6 +2795,7 @@ int sqlite3WalCheckpoint(
assert( pWal->ckptLock==0 );
assert( pWal->writeLock==0 );
if( pWal->readOnly ) return SQLITE_READONLY;
WALTRACE(("WAL%p: checkpoint begins\n", pWal));
rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1);
if( rc ){

View File

@ -55,8 +55,8 @@ proc do_multiclient_test {varname script} {
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 }

159
test/walro.test Normal file
View File

@ -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