From: dan Date: Tue, 29 Oct 2024 19:34:58 +0000 (+0000) Subject: Allow read-only connections to ignore hot journals created by "PRAGMA journal_mode... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2e7fcc41620f5a5923c88d739a794c71f387f1d6;p=thirdparty%2Fsqlite.git Allow read-only connections to ignore hot journals created by "PRAGMA journal_mode = wal". FossilOrigin-Name: d003480db7443025cfd2227096fd929454e9243d5f393f296b74f9bdad15d396 --- diff --git a/manifest b/manifest index 3c1add3939..2d6564bbfa 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\stypo\sin\sLICENSE.md -D 2024-10-29T14:22:12.268 +C Allow\sread-only\sconnections\sto\signore\shot\sjournals\screated\sby\s"PRAGMA\sjournal_mode\s=\swal". +D 2024-10-29T19:34:58.292 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md c5b4009dca54d127d2d6033c22fd9cc34f53bedb6ef12c7cbaa468381c74ab28 @@ -763,7 +763,7 @@ F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d87210 F src/os_unix.c 0ad4e0885294b3a0e135a18533590ec9ad91ffe82f6a08e55b40babd51772928 F src/os_win.c 69fa1aaff68270423c85cff4327ba17ef99a1eb017e1a2bfb97416d9b8398b05 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c 9656ad4e8331efb8a4f94f7a0c6440b98caea073950a367ea0c728a53b8e62c9 +F src/pager.c 170558eb359dd374159141c1f8325480d2d3181439738e7fbb938462c5c9c2c4 F src/pager.h 4b1140d691860de0be1347474c51fee07d5420bd7f802d38cbab8ea4ab9f538a F src/parse.y a7a8d42eeff01d267444ddb476029b0b1726fb70ae3d77984140f17ad02e2d61 F src/pcache.c 588cc3c5ccaaadde689ed35ce5c5c891a1f7b1f4d1f56f6cf0143b74d8ee6484 @@ -1477,7 +1477,7 @@ F test/mutex1.test 42cb5e244c3a77bb0ef2b967e06fa5e7ba7d32d90a9b20bed98f6f5ede185 F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660 F test/nan.test 73ea63ab43668313e2f8cc9ef9e9a966672c7934f3ce76926fbe991235d07d91 F test/nockpt.test 8c43b25af63b0bd620cf1b003529e37b6f1dc53bd22690e96a1bd73f78dde53a -F test/nolock.test f196cf8b8fbea4e2ca345140a2b3f3b0da45c76e +F test/nolock.test 284c72c2b752cf3ffbfd224dfa868bd524e624d3ab5c5ef181fc49ad9df0b444 F test/normalize.test f23b6c5926c59548635fcf39678ac613e726121e073dd902a3062fbb83903b72 F test/notify1.test 669b2b743618efdc18ca4b02f45423d5d2304abf F test/notify2.test 2ecabaa1305083856b7c39cf32816b612740c161 @@ -1550,6 +1550,7 @@ F test/randexpr1.test eda062a97e60f9c38ae8d806b03b0ddf23d796df F test/rbu.test 168573d353cd0fd10196b87b0caa322c144ef736 F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8 F test/readonly.test 69a7ccec846cad2e000b3539d56360d02f327061dc5e41f7f9a3e01f19719952 +F test/readonly2.test df7b572df01a0f773bb7b6e64bd8c98a55cb3099b4ffd054a44319c5f7e87f14 F test/recover.test a163a156ea9f2beea63fa83c4dcd8dea6e57b8a569fc647155e3d2754eaac1b5 F test/regexp1.test 8f2a8bc1569666e29a4cee6c1a666cd224eb6d50e2470d1dc1df995170f3e0f1 F test/regexp2.test 55ed41da802b0e284ac7e2fe944be3948f93ff25abbca0361a609acfed1368b5 @@ -2198,8 +2199,11 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh 49a486c5069de041aedcbde4de178293e0463ae9918ecad7539eedf0ec77a139 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 1d24a29c6ef05185950ba5c45f2a60a92f12a8e5c57026b599f716c9f2f6cf84 -R 39666de7648cff00f94d62d8ecd9d6d5 -U drh -Z 98e5f326f4e37c32b0833374f38b52df +P decc60034849c232a05c8eb93ff0c6a5d6a48336d960771ed096d89633a9d0e2 +R b458c57cdc073a8cbc08997978169801 +T *branch * readonly-ignore-wal-jrnl +T *sym-readonly-ignore-wal-jrnl * +T -sym-trunk * +U dan +Z 5e2548d07e7a74989e8031dcc6402807 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 7b7b320a05..e9fe55edc5 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -decc60034849c232a05c8eb93ff0c6a5d6a48336d960771ed096d89633a9d0e2 +d003480db7443025cfd2227096fd929454e9243d5f393f296b74f9bdad15d396 diff --git a/src/pager.c b/src/pager.c index 4f616f0c7f..095d4fb424 100644 --- a/src/pager.c +++ b/src/pager.c @@ -5081,6 +5081,95 @@ sqlite3_file *sqlite3_database_file_object(const char *zName){ } +/* +** This is used by a read-only database connection in cases where there +** is a hot journal which cannot be rolled back, but the hot journal was +** created by an aborted "PRAGMA journal_mode = wal" statement. In this +** case we can proceed, but must set bytes 18 and 19 of page 1 of the +** db file to indicate that this is not a wal mode database. +*/ +static int getPageOneNoWal( + Pager *pPager, /* The pager open on the database file */ + Pgno pgno, /* Page number to fetch */ + DbPage **ppPage, /* Write a pointer to the page here */ + int flags /* PAGER_GET_XXX flags */ +){ + int rc = SQLITE_OK; + + assert( pgno==1 ); + rc = getPageNormal(pPager, pgno, ppPage, flags); + if( rc==SQLITE_OK ){ + u8 *aPg = (u8*)(*ppPage)->pData; + aPg[18] = 1; + aPg[19] = 1; + } + setGetterMethod(pPager); + return rc; +} + +/* +** This function is called after a potentially hot journal has been opened +** to inspect its contents and determine whether or not it really is a +** hot journal. The journal is not a hot journal in two cases: +** +** * The first byte of the journal is 0x00, or, +** +** * The connection is opened read-only, the journal consist of page 1 +** of the db only, and the contents of page 1 differs from the contents +** of the db only in bytes 18 and 19 (the wal mode setting). +** +** If the journal is not hot, then output variable (*pbHot) is set to 0 +** before this function returns. Otherwise, if the journal is hot, (*pbHot) +** is set to 1. +*/ +static int checkHotJournal(Pager *pPager, int *pbHot){ + char aHdr[28]; + int rc = SQLITE_OK; + + *pbHot = 1; + rc = sqlite3OsRead(pPager->jfd, (void *)aHdr, sizeof(aHdr), 0); + if( rc==SQLITE_OK ){ + if( aHdr[0]==0 ){ + *pbHot = 0; + }else if( pPager->readOnly ){ + u32 nPg = sqlite3Get4byte(&aHdr[8]); + u32 off = sqlite3Get4byte(&aHdr[20]); + u32 pgsz = sqlite3Get4byte(&aHdr[24]); + + if( nPg==0xFFFFFFFF ){ + i64 sz = 0; + rc = sqlite3OsFileSize(pPager->jfd, &sz); + nPg = ((sz-off) / pgsz); + } + if( nPg==1 ){ + u8 *a1 = 0; + a1 = sqlite3_malloc(pgsz*2); + if( a1==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + u8 *a2 = &a1[pgsz]; + rc = sqlite3OsRead(pPager->jfd, a1, pgsz, off+4); + if( rc==SQLITE_OK ){ + rc = sqlite3OsRead(pPager->fd, a2, pgsz, 0); + memcpy(&a2[18], &a1[18], 2); + memcpy(&a2[24], &a1[24], 4); + memcpy(&a2[92], &a1[92], 8); + } + if( rc==SQLITE_OK && memcmp(a1, a2, pgsz)==0 ){ + *pbHot = 0; + pPager->xGet = getPageOneNoWal; + } + sqlite3_free(a1); + } + } + } + } + if( rc==SQLITE_IOERR_SHORT_READ ){ + rc = SQLITE_OK; + } + return rc; +} + /* ** This function is called after transitioning from PAGER_UNLOCK to ** PAGER_SHARED state. It tests if there is a hot journal present in @@ -5175,15 +5264,10 @@ static int hasHotJournal(Pager *pPager, int *pExists){ rc = sqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, f, &f); } if( rc==SQLITE_OK ){ - u8 first = 0; - rc = sqlite3OsRead(pPager->jfd, (void *)&first, 1, 0); - if( rc==SQLITE_IOERR_SHORT_READ ){ - rc = SQLITE_OK; - } + rc = checkHotJournal(pPager, pExists); if( !jrnlOpen ){ sqlite3OsClose(pPager->jfd); } - *pExists = (first!=0); }else if( rc==SQLITE_CANTOPEN ){ /* If we cannot open the rollback journal file in order to see if ** it has a zero header, that might be due to an I/O error, or @@ -5249,6 +5333,7 @@ int sqlite3PagerSharedLock(Pager *pPager){ assert( !MEMDB ); assert( pPager->tempFile==0 || pPager->eLock==EXCLUSIVE_LOCK ); + assert( pPager->xGet!=getPageOneNoWal ); rc = pager_wait_on_lock(pPager, SHARED_LOCK); if( rc!=SQLITE_OK ){ @@ -5434,6 +5519,8 @@ int sqlite3PagerSharedLock(Pager *pPager){ assert( !MEMDB ); pager_unlock(pPager); assert( pPager->eState==PAGER_OPEN ); + testcase( pPager->xGet==getPageOneNoWal ); + setGetterMethod(pPager); }else{ pPager->eState = PAGER_READER; pPager->hasHeldSharedLock = 1; @@ -5701,7 +5788,6 @@ static int getPageError( return pPager->errCode; } - /* Dispatch all page fetch requests to the appropriate getter method. */ int sqlite3PagerGet( diff --git a/test/nolock.test b/test/nolock.test index e732dcf13b..8426165dcb 100644 --- a/test/nolock.test +++ b/test/nolock.test @@ -16,6 +16,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl +set testprefix nolock unset -nocomplain tvfs_calls proc tvfs_reset {} { @@ -217,4 +218,31 @@ if {[permutation]!="inmemory_journal"} { } {1 {unable to open database file}} } +#------------------------------------------------------------------------- +reset_db +do_execsql_test 5.0 { + CREATE TABLE t1(x, y); + CREATE TABLE t2(x, y); + INSERT INTO t1 VALUES(1, 2), (3, 4), (5, 6); + INSERT INTO t2 VALUES(1, 2), (3, 4), (5, 6); + PRAGMA cache_size = 0; + BEGIN; + INSERT INTO t1 VALUES(7, 8); + INSERT INTO t2 VALUES(7, 8); +} + +do_test 5.1 { + forcecopy test.db test.db2 + forcecopy test.db-journal test.db2-journal + sqlite3 db2 file:test.db2?immutable=0 -uri 1 -readonly 1 + + list [catch { + db2 eval { + SELECT * FROM t1; + SELECT * FROM t2; + } + } msg] $msg +} {1 {attempt to write a readonly database}} + finish_test + diff --git a/test/readonly2.test b/test/readonly2.test new file mode 100644 index 0000000000..2c2b1aa6d0 --- /dev/null +++ b/test/readonly2.test @@ -0,0 +1,89 @@ +# 2024 October 30 +# +# 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. +# +#*********************************************************************** +# +# Test that readonly database connections may ignore a hot journal +# created by an aborted "PRAGMA journal_mode = wal" statement. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +set ::testprefix readonly2 + +testvfs tvfs -default 1 +tvfs script at_vfs_callback +tvfs filter {xDelete} + +set ::delete_shall_fail 0 +proc at_vfs_callback {method file z args} { + if {$::delete_shall_fail} { + return "SQLITE_IOERR" + } + return "SQLITE_OK" +} + +reset_db +do_execsql_test 1.0 { + BEGIN; + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2), (3, 4), (5, 6); + COMMIT; +} + +set ::delete_shall_fail 1 +do_catchsql_test 1.1 { + PRAGMA journal_mode = wal; +} {1 {disk I/O error}} + +do_test 1.2 { + file exists test.db-journal +} 1 + +do_test 1.3 { + sqlite3 db2 test.db -readonly 1 + db2 eval { + SELECT * FROM t1 + } +} {1 2 3 4 5 6} + +do_test 1.4 { + file exists test.db-journal +} 1 + +set ::delete_shall_fail 0 +do_execsql_test 1.5 { + SELECT * FROM t1 +} {1 2 3 4 5 6} + +set ::delete_shall_fail 1 +do_catchsql_test 1.6 { + PRAGMA user_version = 444; +} {1 {disk I/O error}} + +do_test 1.7 { + file exists test.db-journal +} 1 + +do_test 1.8 { + list [catch { db2 eval { SELECT * FROM t1 } } msg] $msg +} {1 {attempt to write a readonly database}} + +do_test 1.9 { + file exists test.db-journal +} 1 + +set ::delete_shall_fail 0 +do_execsql_test 1.10 { + SELECT * FROM t1 +} {1 2 3 4 5 6} + + +finish_test