diff --git a/manifest b/manifest index 9e6489e647..f2e67cf931 100644 --- a/manifest +++ b/manifest @@ -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 diff --git a/manifest.uuid b/manifest.uuid index de373954a3..2a33332676 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e704e8690ae35decc9769a45cf8d519ccad8b79d \ No newline at end of file +19084a6641f77a62110b04ea50e298fe132ea784 \ No newline at end of file diff --git a/src/os_unix.c b/src/os_unix.c index c768dcfa76..5860ae71a9 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -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; } diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 445a247a19..593e9148fe 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -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 diff --git a/src/test1.c b/src/test1.c index 7d31c6d02a..4d5f49fd1d 100644 --- a/src/test1.c +++ b/src/test1.c @@ -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; diff --git a/src/wal.c b/src/wal.c index d90fb05d8b..9f9e1115fa 100644 --- a/src/wal.c +++ b/src/wal.c @@ -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 + && (mxReadMarkhdr.mxFrame || mxI==0) + ){ for(i=1; ickptLock==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 ){ diff --git a/test/lock_common.tcl b/test/lock_common.tcl index 21f584264a..bc1eb86bdc 100644 --- a/test/lock_common.tcl +++ b/test/lock_common.tcl @@ -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 } diff --git a/test/walro.test b/test/walro.test new file mode 100644 index 0000000000..cf044b75f2 --- /dev/null +++ b/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