diff --git a/manifest b/manifest index afe26f8645..9bdb79b37e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Do\snow\sallow\sthe\sgeometry\sobject\sin\sthe\sright\soperand\sof\sa\sMATCH\soperator\sin\nthe\sRTREE\sextension\sto\sbe\sinpersonated\sby\sa\sBLOB\sliteral. -D 2017-07-27T00:27:57.692 +C Backport\sof\sall\sbatch-atomic-write\schanges\sthrough\scheck-in\n[67bad7fb9b] +D 2017-07-28T02:02:45.191 F Makefile.in 1cc758ce3374a32425e4d130c2fe7b026b20de5b8843243de75f087c0a2661fb F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc 8eeb80162074004e906b53d7340a12a14c471a83743aab975947e95ce061efcc @@ -352,7 +352,7 @@ F src/btreeInt.h a392d353104b4add58b4a59cb185f5d5693dde832c565b77d8d4c343ed98f61 F src/build.c 4026a9c554b233e50c5e9ad46963e676cf54dd2306d952aa1eaa07a1bc9ce14f F src/callback.c 2e76147783386374bf01b227f752c81ec872d730 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e -F src/ctime.c 47d91a25ad8f199a71a5b1b7b169d6dd0d6e98c5719eca801568798743d1161c +F src/ctime.c 9793c9128901afbaa7561c9dcb510c96c6187225680ef644577bd74e2551b3a6 F src/date.c cc42a41c7422389860d40419a5e3bce5eaf6e7835c3ba2677751dc653550a5c7 F src/dbstat.c 19ee7a4e89979d4df8e44cfac7a8f905ec89b77d F src/delete.c 665e705641e5815c3f32d05820d1a5aa630274e568af73f377fdbc614fcf40b4 @@ -375,7 +375,7 @@ F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de F src/mem2.c f1940d9e91948dd6a908fbb9ce3835c36b5d83c3 F src/mem3.c 8768ac94694f31ffaf8b4d0ea5dc08af7010a35a F src/mem5.c 9bf955937b07f8c32541c8a9991f33ce3173d944 -F src/memjournal.c 95752936c11dc6995672d1dd783cd633eea0cc95 +F src/memjournal.c 9b9e579226341e891a4657dc76fe2cec69734abcd23be0e42aa8764d88131f12 F src/msvc.h 4942752b6a253116baaa8de75256c51a459a5e81 F src/mutex.c 8e45800ee78e0cd1f1f3fe8e398853307f4a085c F src/mutex.h 779d588e3b7756ec3ecf7d78cde1d84aba414f85 @@ -387,10 +387,10 @@ F src/os.c add02933b1dce7a39a005b00a2f5364b763e9a24 F src/os.h 8e976e59eb4ca1c0fca6d35ee803e38951cb0343 F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85 F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586 -F src/os_unix.c 30e2c43e4955db990e5b5a81e901f8aa74cc8820 +F src/os_unix.c 9aa19db965d574cbaf74405cd51c58b87fdbfe045b8bbfc01b18bdeeda3c4305 F src/os_win.c 2a6c73eef01c51a048cc4ddccd57f981afbec18a F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c 80893c0860199aebc6efa4f102ab11eebde338b7fdbb0c04d4b04647c2fd62d1 +F src/pager.c 930dace8819cf569331bca24be8fe362d337208bc6e12dc4acb19c596630f1f4 F src/pager.h f2a99646c5533ffe11afa43e9e0bea74054e4efa F src/parse.y 0513387ce02fea97897d8caef82d45f347818593f24f1bdc48e0c530a8af122d F src/pcache.c 62835bed959e2914edd26afadfecce29ece0e870 @@ -405,20 +405,20 @@ F src/resolve.c 3e518b962d932a997fae373366880fc028c75706 F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac F src/select.c c444ca057d48cf1a4eb8f6a58fbc13418a7f83ae94a6bd6f29f7ad6c704a9e4c F src/shell.c a37d96b20b3644d0eb905df5aa7a0fcf9f6e73c15898337230c760a24a8df794 -F src/sqlite.h.in 8dd468837a4f6d76713e3a4cc65bea48095009038593d41040ab46c1b351197f +F src/sqlite.h.in d603987eee5f7a2fa81d4baf5ec70b67b214bd34f7e1819653f5ec3b9c982eee F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 58fd0676d3111d02e62e5a35992a7d3da5d3f88753acc174f2d37b774fbbdd28 -F src/sqliteInt.h aea3aa1b81e0d07d5b1c39b8c5a54a1dc5e4f10136cb63da392aef9eb2a5108b +F src/sqliteInt.h 40785f84fc3d3b25b54f4325d42992f41a73e81de57b354271bd2de0ae377625 F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b F src/status.c a9e66593dfb28a9e746cba7153f84d49c1ddc4b1 F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34 F src/tclsqlite.c c8cf60d0c5411d5e70e7c136470d29dbe760d250f55198b71682c67086524e4a -F src/test1.c c99f0442918a7a5d5b68a95d6024c211989e6c782c15ced5a558994baaf76a5e +F src/test1.c 4856c3d776e11d0975bf56ad41626b467af6af6443a937c36c3d9d4643029f7c F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5 F src/test3.c d03f5b5da9a2410b7a91c64b0d3306ed28ab6fee F src/test4.c 18ec393bb4d0ad1de729f0b94da7267270f3d8e6 F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d -F src/test6.c 004ad42f121f693b8cbe060d1a330678abc61620 +F src/test6.c e8d839fbc552ce044bec8234561a2d5b8819b48e29548ad0ba400471697946a8 F src/test7.c 5612e9aecf934d6df7bba6ce861fdf5ba5456010 F src/test8.c 4f4904721167b32f7a4fa8c7b32a07a673d6cc86 F src/test9.c 12e5ba554d2d1cbe0158f6ab3f7ffcd7a86ee4e5 @@ -431,7 +431,7 @@ F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274 F src/test_config.c edcba290248dc18736dd814c9b95863c6762e0b35753048d8cbe5bf65f7abfbb F src/test_delete.c e2fe07646dff6300b48d49b2fee2fe192ed389e834dd635e3b3bac0ce0bf9f8f F src/test_demovfs.c a0c3bdd45ed044115c2c9f7779e56eafff18741e -F src/test_devsym.c 4e58dec2602d8e139ca08659f62a62450587cb58 +F src/test_devsym.c 1960abbb234b97e9b920f07e99503fc04b443f62bbc3c6ff2c2cea2133e3b8a2 F src/test_fs.c e16cbe68d3b107e00a907c20a9a02629870eb69b F src/test_func.c a4fdab3363b436c1b12660e9362ce3f3782b7b5e F src/test_hexio.c 1d4469ca61ab202a1fcec6543f584d2407205e8d @@ -703,7 +703,7 @@ F test/exists.test 79a75323c78f02bbe9c251ea502a092f9ef63dac F test/expr.test 66a2c9ac34f74f036faa4092f5402c7d3162fc93 F test/extension01.test 00d13cec817f331a687a243e0e5a2d87b0e358c9 F test/extraquick.test cb254400bd42bfb777ff675356aabf3287978f79 -F test/fallocate.test 3e979af17dfa7e5e9dda5eba1a696c04fa9d47f7 +F test/fallocate.test 87b5e43c872b7e69cd80b7b8813eb102b571a75d45dda24e38b65537bcc85733 F test/filectrl.test 6e871c2d35dead1d9a88e176e8d2ca094fec6bb3 F test/filefmt.test e4edbdc637ca9576ccf4337a3cce627d9df7a56c F test/fkey1.test ba64806ff9a04eecab2679caad377ae99a5e94e4 @@ -977,7 +977,7 @@ F test/minmax.test 6751e87b409fe11b02e70a306d846fa544e25a41 F test/minmax2.test b44bae787fc7b227597b01b0ca5575c7cb54d3bc F test/minmax3.test cc1e8b010136db0d01a6f2a29ba5a9f321034354 F test/minmax4.test 936941484ebdceb8adec7c86b6cd9b6e5e897c1f -F test/misc1.test 6430dabfb4b4fa480633590118964201f94d3ccc +F test/misc1.test 51ec3f56a2a7965de226964cff856695e743186826561536647f1e8b7d1d0eb3 F test/misc2.test 00d7de54eda90e237fc9a38b9e5ccc769ebf6d4d F test/misc3.test cf3dda47d5dda3e53fc5804a100d3c82be736c9d F test/misc4.test 0d8be3466adf123a7791a66ba2bc8e8d229e87f3 @@ -1035,7 +1035,7 @@ F test/parser1.test 391b9bf9a229547a129c61ac345ed1a6f5eb1854 F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442 F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff -F test/permutations.test 8aaa22a0f428a7e6b8446b97bc7691a273eaeff5dc290fb9129bf79fa9813a6e +F test/permutations.test 0672e2db670217b156ae6d32c7de3abcda50a305743e7d45d1063ede31159bd0 F test/pragma.test 1e94755164a3a3264cd39836de4bebcb7809e5f8 F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f F test/pragma3.test 14c12bc5352b1e100e0b6b44f371053a81ccf8ed @@ -1061,7 +1061,7 @@ F test/regexp2.test 40e894223b3d6672655481493f1be12012f2b33c F test/reindex.test 44edd3966b474468b823d481eafef0c305022254 F test/releasetest.tcl 936c8b9c72d46a86d0f9d65c536e883c2d79a8f53a5324283757bfc9d8b5a656 x F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb -F test/rollback.test 458fe73eb3ffdfdf9f6ba3e9b7350a6220414dea +F test/rollback.test f580934279800d480a19176c6b44909df31ce7ad45267ea475a541daa522f3d3 F test/rollback2.test 8435d6ff0f13f51d2a4181c232e706005fa90fc5 F test/rollbackfault.test 0e646aeab8840c399cfbfa43daab46fd609cf04a F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc @@ -1179,7 +1179,7 @@ F test/superlock.test ec94f0556b6488d97f71c79f9061ae08d9ab8f12 F test/symlink.test c9ebe7330d228249e447038276bfc8a7b22f4849 F test/sync.test 2f84bdbc2b2df1fcb0220575b4b9f8cea94b7529 F test/sync2.test 6be8ed007fa063b147773c1982b5bdba97a32badc536bdc6077eff5cf8710ece -F test/syscall.test f59ba4e25f7ba4a4c031026cc2ef8b6e4b4c639c +F test/syscall.test 26f05bc237853a7588b9a4c9111aacb8cdd32d2562c36fee7d9dfc4731c6c329 F test/sysfault.test c9f2b0d8d677558f74de750c75e12a5454719d04 F test/tabfunc01.test 699251cb99651415218a891384510a685c7ab012 F test/table.test b708f3e5fa2542fa51dfab21fc07b36ea445cb2f @@ -1193,7 +1193,7 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30 F test/temptable2.test cd396beb41117a5302fff61767c35fa4270a0d5e F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc -F test/tester.tcl 581f0185434daf7026ccede4c07e8d1479186ec5 +F test/tester.tcl eb7ec55fe074a909423c1de701f7c545417b8aa96787b8c3e7a79203f2cebec8 F test/thread001.test 9f22fd3525a307ff42a326b6bc7b0465be1745a5 F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -1487,6 +1487,7 @@ F test/without_rowid4.test 4e08bcbaee0399f35d58b5581881e7a6243d458a F test/without_rowid5.test 89b1c587bd92a0590e440da33e7666bf4891572a F test/without_rowid6.test 1f99644e6508447fb050f73697350c7ceca3392e F test/wordcount.c 06efb84b7c48a4973c2c24ea06c93d00bce24389 +F test/writecrash.test e6dc9d470d43710c3f780a50d29cce1c3884ca34ff50e737bc9ab8724c7c2398 F test/zeroblob.test 3857870fe681b8185654414a9bccfde80b62a0fa F test/zerodamage.test e59a56443d6298ecf7435f618f0b27654f0c849e F tool/GetFile.cs a15e08acb5dd7539b75ba23501581d7c2b462cb5 @@ -1580,7 +1581,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 0ee482a1e0eae22e08edc8978c9733a96603d4509645f348ebf55b579e89636b -R 5aba315ce772ced25c1b87936307ec42 +P 24c9cd460051781f7e6417b7e205274319258f061ae1191a6c5ff5af615439c9 67bad7fb9b2fdb29b63308f22062444084dc28191a542e08076dc2e39caf6f62 +R 52d5da1e6c65e285a15752ad8c60b72a +T *branch * batch-atomic-write-3.19 +T *sym-batch-atomic-write-3.19 * +T -sym-branch-3.19 * U drh -Z c0ec9b17a9a0e51afaa3dc2485cf6a08 +Z db5307b269591715fdc95cf9a91a4aa8 diff --git a/manifest.uuid b/manifest.uuid index 24b676d883..65256a71e9 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -24c9cd460051781f7e6417b7e205274319258f061ae1191a6c5ff5af615439c9 \ No newline at end of file +def55027b1f1db9c083830019dbcc3daed94f6cc70a76b285ac1af9d82f81695 \ No newline at end of file diff --git a/src/ctime.c b/src/ctime.c index 20e94edba5..90ea8cf701 100644 --- a/src/ctime.c +++ b/src/ctime.c @@ -90,6 +90,9 @@ static const char * const azCompileOpt[] = { #if SQLITE_ENABLE_ATOMIC_WRITE "ENABLE_ATOMIC_WRITE", #endif +#if SQLITE_ENABLE_BATCH_ATOMIC_WRITE + "ENABLE_BATCH_ATOMIC_WRITE", +#endif #if SQLITE_ENABLE_CEROD "ENABLE_CEROD", #endif diff --git a/src/memjournal.c b/src/memjournal.c index cd8b87d8aa..31ab5ef93f 100644 --- a/src/memjournal.c +++ b/src/memjournal.c @@ -96,7 +96,8 @@ static int memjrnlRead( int iChunkOffset; FileChunk *pChunk; -#ifdef SQLITE_ENABLE_ATOMIC_WRITE +#if defined(SQLITE_ENABLE_ATOMIC_WRITE) \ + || defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) if( (iAmt+iOfst)>p->endpoint.iOffset ){ return SQLITE_IOERR_SHORT_READ; } @@ -215,7 +216,8 @@ static int memjrnlWrite( ** atomic-write optimization. In this case the first 28 bytes of the ** journal file may be written as part of committing the transaction. */ assert( iOfst==p->endpoint.iOffset || iOfst==0 ); -#ifdef SQLITE_ENABLE_ATOMIC_WRITE +#if defined(SQLITE_ENABLE_ATOMIC_WRITE) \ + || defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) if( iOfst==0 && p->pFirst ){ assert( p->nChunkSize>iAmt ); memcpy((u8*)p->pFirst->zChunk, zBuf, iAmt); @@ -384,17 +386,23 @@ void sqlite3MemJournalOpen(sqlite3_file *pJfd){ sqlite3JournalOpen(0, 0, pJfd, 0, -1); } -#ifdef SQLITE_ENABLE_ATOMIC_WRITE +#if defined(SQLITE_ENABLE_ATOMIC_WRITE) \ + || defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) /* ** If the argument p points to a MemJournal structure that is not an ** in-memory-only journal file (i.e. is one that was opened with a +ve -** nSpill parameter), and the underlying file has not yet been created, -** create it now. +** nSpill parameter or as SQLITE_OPEN_MAIN_JOURNAL), and the underlying +** file has not yet been created, create it now. */ -int sqlite3JournalCreate(sqlite3_file *p){ +int sqlite3JournalCreate(sqlite3_file *pJfd){ int rc = SQLITE_OK; - if( p->pMethods==&MemJournalMethods && ((MemJournal*)p)->nSpill>0 ){ - rc = memjrnlCreateFile((MemJournal*)p); + MemJournal *p = (MemJournal*)pJfd; + if( p->pMethod==&MemJournalMethods && (p->nSpill>0 +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE + || (p->flags & SQLITE_OPEN_MAIN_JOURNAL) +#endif + )){ + rc = memjrnlCreateFile(p); } return rc; } diff --git a/src/os_unix.c b/src/os_unix.c index 7f0ebdba6f..430c5386ea 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -90,6 +90,7 @@ #include #include #include +#include #include #include #include @@ -220,10 +221,8 @@ struct unixFile { sqlite3_int64 mmapSizeMax; /* Configured FCNTL_MMAP_SIZE value */ void *pMapRegion; /* Memory mapped region */ #endif -#ifdef __QNXNTO__ int sectorSize; /* Device sector size */ int deviceCharacteristics; /* Precomputed device characteristics */ -#endif #if SQLITE_ENABLE_LOCKING_STYLE int openFlags; /* The flags specified at open() */ #endif @@ -328,6 +327,20 @@ static pid_t randomnessPid = 0; # define lseek lseek64 #endif +#ifdef __linux__ +/* +** Linux-specific IOCTL magic numbers used for controlling F2FS +*/ +#define F2FS_IOCTL_MAGIC 0xf5 +#define F2FS_IOC_START_ATOMIC_WRITE _IO(F2FS_IOCTL_MAGIC, 1) +#define F2FS_IOC_COMMIT_ATOMIC_WRITE _IO(F2FS_IOCTL_MAGIC, 2) +#define F2FS_IOC_START_VOLATILE_WRITE _IO(F2FS_IOCTL_MAGIC, 3) +#define F2FS_IOC_ABORT_VOLATILE_WRITE _IO(F2FS_IOCTL_MAGIC, 5) +#define F2FS_IOC_GET_FEATURES _IOR(F2FS_IOCTL_MAGIC, 12, u32) +#define F2FS_FEATURE_ATOMIC_WRITE 0x0004 +#endif /* __linux__ */ + + /* ** Different Unix systems declare open() in different ways. Same use ** open(const char*,int,mode_t). Others use open(const char*,int,...). @@ -500,6 +513,9 @@ static struct unix_syscall { #endif #define osLstat ((int(*)(const char*,struct stat*))aSyscall[27].pCurrent) + { "ioctl", (sqlite3_syscall_ptr)ioctl, 0 }, +#define osIoctl ((int(*)(int,int,...))aSyscall[28].pCurrent) + }; /* End of the overrideable system calls */ @@ -3777,6 +3793,21 @@ static int unixGetTempname(int nBuf, char *zBuf); static int unixFileControl(sqlite3_file *id, int op, void *pArg){ unixFile *pFile = (unixFile*)id; switch( op ){ +#if defined(__linux__) && defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) + case SQLITE_FCNTL_BEGIN_ATOMIC_WRITE: { + int rc = osIoctl(pFile->h, F2FS_IOC_START_ATOMIC_WRITE); + return rc ? SQLITE_ERROR : SQLITE_OK; + } + case SQLITE_FCNTL_COMMIT_ATOMIC_WRITE: { + int rc = osIoctl(pFile->h, F2FS_IOC_COMMIT_ATOMIC_WRITE); + return rc ? SQLITE_ERROR : SQLITE_OK; + } + case SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: { + int rc = osIoctl(pFile->h, F2FS_IOC_ABORT_VOLATILE_WRITE); + return rc ? SQLITE_ERROR : SQLITE_OK; + } +#endif /* __linux__ && SQLITE_ENABLE_BATCH_ATOMIC_WRITE */ + case SQLITE_FCNTL_LOCKSTATE: { *(int*)pArg = pFile->eFileLock; return SQLITE_OK; @@ -3860,30 +3891,41 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){ } /* -** Return the sector size in bytes of the underlying block device for -** the specified file. This is almost always 512 bytes, but may be -** larger for some devices. +** If pFd->sectorSize is non-zero when this function is called, it is a +** no-op. Otherwise, the values of pFd->sectorSize and +** pFd->deviceCharacteristics are set according to the file-system +** characteristics. ** -** SQLite code assumes this function cannot fail. It also assumes that -** if two files are created in the same file-system directory (i.e. -** a database and its journal file) that the sector size will be the -** same for both. +** There are two versions of this function. One for QNX and one for all +** other systems. */ -#ifndef __QNXNTO__ -static int unixSectorSize(sqlite3_file *NotUsed){ - UNUSED_PARAMETER(NotUsed); - return SQLITE_DEFAULT_SECTOR_SIZE; -} -#endif +#ifndef __QNXNTO__ +static void setDeviceCharacteristics(unixFile *pFd){ + assert( pFd->deviceCharacteristics==0 || pFd->sectorSize!=0 ); + if( pFd->sectorSize==0 ){ +#if defined(__linux__) && defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) + int res; + u32 f = 0; -/* -** The following version of unixSectorSize() is optimized for QNX. -*/ -#ifdef __QNXNTO__ + /* Check for support for F2FS atomic batch writes. */ + res = osIoctl(pFd->h, F2FS_IOC_GET_FEATURES, &f); + if( res==0 && (f & F2FS_FEATURE_ATOMIC_WRITE) ){ + pFd->deviceCharacteristics = SQLITE_IOCAP_BATCH_ATOMIC; + } +#endif /* __linux__ && SQLITE_ENABLE_BATCH_ATOMIC_WRITE */ + + /* Set the POWERSAFE_OVERWRITE flag if requested. */ + if( pFd->ctrlFlags & UNIXFILE_PSOW ){ + pFd->deviceCharacteristics |= SQLITE_IOCAP_POWERSAFE_OVERWRITE; + } + + pFd->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE; + } +} +#else #include #include -static int unixSectorSize(sqlite3_file *id){ - unixFile *pFile = (unixFile*)id; +static void setDeviceCharacteristics(unixFile *pFile){ if( pFile->sectorSize == 0 ){ struct statvfs fsInfo; @@ -3952,9 +3994,24 @@ static int unixSectorSize(sqlite3_file *id){ pFile->deviceCharacteristics = 0; pFile->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE; } - return pFile->sectorSize; } -#endif /* __QNXNTO__ */ +#endif + +/* +** Return the sector size in bytes of the underlying block device for +** the specified file. This is almost always 512 bytes, but may be +** larger for some devices. +** +** SQLite code assumes this function cannot fail. It also assumes that +** if two files are created in the same file-system directory (i.e. +** a database and its journal file) that the sector size will be the +** same for both. +*/ +static int unixSectorSize(sqlite3_file *id){ + unixFile *pFd = (unixFile*)id; + setDeviceCharacteristics(pFd); + return pFd->sectorSize; +} /* ** Return the device characteristics for the file. @@ -3970,16 +4027,9 @@ static int unixSectorSize(sqlite3_file *id){ ** available to turn it off and URI query parameter available to turn it off. */ static int unixDeviceCharacteristics(sqlite3_file *id){ - unixFile *p = (unixFile*)id; - int rc = 0; -#ifdef __QNXNTO__ - if( p->sectorSize==0 ) unixSectorSize(id); - rc = p->deviceCharacteristics; -#endif - if( p->ctrlFlags & UNIXFILE_PSOW ){ - rc |= SQLITE_IOCAP_POWERSAFE_OVERWRITE; - } - return rc; + unixFile *pFd = (unixFile*)id; + setDeviceCharacteristics(pFd); + return pFd->deviceCharacteristics; } #if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 @@ -7598,7 +7648,7 @@ int sqlite3_os_init(void){ /* Double-check that the aSyscall[] array has been constructed ** correctly. See ticket [bb3a86e890c8e96ab] */ - assert( ArraySize(aSyscall)==28 ); + assert( ArraySize(aSyscall)==29 ); /* Register all VFSes defined in the aVfs[] array */ for(i=0; i<(sizeof(aVfs)/sizeof(sqlite3_vfs)); i++){ diff --git a/src/pager.c b/src/pager.c index 58d63dd439..a81fca0b87 100644 --- a/src/pager.c +++ b/src/pager.c @@ -947,6 +947,7 @@ static int assert_pager_state(Pager *p){ assert( isOpen(p->jfd) || p->journalMode==PAGER_JOURNALMODE_OFF || p->journalMode==PAGER_JOURNALMODE_WAL + || (sqlite3OsDeviceCharacteristics(p->fd)&SQLITE_IOCAP_BATCH_ATOMIC) ); assert( pPager->dbOrigSize<=pPager->dbHintSize ); break; @@ -958,6 +959,7 @@ static int assert_pager_state(Pager *p){ assert( isOpen(p->jfd) || p->journalMode==PAGER_JOURNALMODE_OFF || p->journalMode==PAGER_JOURNALMODE_WAL + || (sqlite3OsDeviceCharacteristics(p->fd)&SQLITE_IOCAP_BATCH_ATOMIC) ); break; @@ -1168,34 +1170,45 @@ static int pagerLockDb(Pager *pPager, int eLock){ } /* -** This function determines whether or not the atomic-write optimization -** can be used with this pager. The optimization can be used if: +** This function determines whether or not the atomic-write or +** atomic-batch-write optimizations can be used with this pager. The +** atomic-write optimization can be used if: ** ** (a) the value returned by OsDeviceCharacteristics() indicates that ** a database page may be written atomically, and ** (b) the value returned by OsSectorSize() is less than or equal ** to the page size. ** -** The optimization is also always enabled for temporary files. It is -** an error to call this function if pPager is opened on an in-memory -** database. +** If it can be used, then the value returned is the size of the journal +** file when it contains rollback data for exactly one page. ** -** If the optimization cannot be used, 0 is returned. If it can be used, -** then the value returned is the size of the journal file when it -** contains rollback data for exactly one page. +** The atomic-batch-write optimization can be used if OsDeviceCharacteristics() +** returns a value with the SQLITE_IOCAP_BATCH_ATOMIC bit set. -1 is +** returned in this case. +** +** If neither optimization can be used, 0 is returned. */ -#ifdef SQLITE_ENABLE_ATOMIC_WRITE static int jrnlBufferSize(Pager *pPager){ assert( !MEMDB ); - if( !pPager->tempFile ){ - int dc; /* Device characteristics */ - int nSector; /* Sector size */ - int szPage; /* Page size */ - assert( isOpen(pPager->fd) ); - dc = sqlite3OsDeviceCharacteristics(pPager->fd); - nSector = pPager->sectorSize; - szPage = pPager->pageSize; +#if defined(SQLITE_ENABLE_ATOMIC_WRITE) \ + || defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) + int dc; /* Device characteristics */ + + assert( isOpen(pPager->fd) ); + dc = sqlite3OsDeviceCharacteristics(pPager->fd); +#endif + +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE + if( dc&SQLITE_IOCAP_BATCH_ATOMIC ){ + return -1; + } +#endif + +#ifdef SQLITE_ENABLE_ATOMIC_WRITE + { + int nSector = pPager->sectorSize; + int szPage = pPager->pageSize; assert(SQLITE_IOCAP_ATOMIC512==(512>>8)); assert(SQLITE_IOCAP_ATOMIC64K==(65536>>8)); @@ -1205,11 +1218,11 @@ static int jrnlBufferSize(Pager *pPager){ } return JOURNAL_HDR_SZ(pPager) + JOURNAL_PG_SZ(pPager); -} -#else -# define jrnlBufferSize(x) 0 #endif + return 0; +} + /* ** If SQLITE_CHECK_PAGES is defined then we do some sanity checking ** on the cache using a hash function. This is used for testing @@ -2012,7 +2025,9 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){ } releaseAllSavepoints(pPager); - assert( isOpen(pPager->jfd) || pPager->pInJournal==0 ); + assert( isOpen(pPager->jfd) || pPager->pInJournal==0 + || (sqlite3OsDeviceCharacteristics(pPager->fd)&SQLITE_IOCAP_BATCH_ATOMIC) + ); if( isOpen(pPager->jfd) ){ assert( !pagerUseWal(pPager) ); @@ -4569,6 +4584,13 @@ static int pagerStress(void *p, PgHdr *pPg){ rc = pagerWalFrames(pPager, pPg, 0, 0); } }else{ + +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE + if( pPager->tempFile==0 ){ + rc = sqlite3JournalCreate(pPager->jfd); + if( rc!=SQLITE_OK ) return pager_error(pPager, rc); + } +#endif /* Sync the journal file if required. */ if( pPg->flags&PGHDR_NEED_SYNC @@ -6354,6 +6376,21 @@ int sqlite3PagerCommitPhaseOne( sqlite3PcacheCleanAll(pPager->pPCache); } }else{ + /* The bBatch boolean is true if the batch-atomic-write commit method + ** should be used. No rollback journal is created if batch-atomic-write + ** is enabled. + */ + sqlite3_file *fd = pPager->fd; +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE + const int bBatch = zMaster==0 /* An SQLITE_IOCAP_BATCH_ATOMIC commit */ + && (sqlite3OsDeviceCharacteristics(fd) & SQLITE_IOCAP_BATCH_ATOMIC) + && !pPager->noSync + && sqlite3JournalIsInMemory(pPager->jfd); +#else +# define bBatch 0 +#endif + +#ifdef SQLITE_ENABLE_ATOMIC_WRITE /* The following block updates the change-counter. Exactly how it ** does this depends on whether or not the atomic-update optimization ** was enabled at compile time, and if this transaction meets the @@ -6377,33 +6414,40 @@ int sqlite3PagerCommitPhaseOne( ** in 'direct' mode. In this case the journal file will never be ** created for this transaction. */ - #ifdef SQLITE_ENABLE_ATOMIC_WRITE - PgHdr *pPg; - assert( isOpen(pPager->jfd) - || pPager->journalMode==PAGER_JOURNALMODE_OFF - || pPager->journalMode==PAGER_JOURNALMODE_WAL - ); - if( !zMaster && isOpen(pPager->jfd) - && pPager->journalOff==jrnlBufferSize(pPager) - && pPager->dbSize>=pPager->dbOrigSize - && (0==(pPg = sqlite3PcacheDirtyList(pPager->pPCache)) || 0==pPg->pDirty) - ){ - /* Update the db file change counter via the direct-write method. The - ** following call will modify the in-memory representation of page 1 - ** to include the updated change counter and then write page 1 - ** directly to the database file. Because of the atomic-write - ** property of the host file-system, this is safe. - */ - rc = pager_incr_changecounter(pPager, 1); - }else{ - rc = sqlite3JournalCreate(pPager->jfd); - if( rc==SQLITE_OK ){ - rc = pager_incr_changecounter(pPager, 0); + if( bBatch==0 ){ + PgHdr *pPg; + assert( isOpen(pPager->jfd) + || pPager->journalMode==PAGER_JOURNALMODE_OFF + || pPager->journalMode==PAGER_JOURNALMODE_WAL + ); + if( !zMaster && isOpen(pPager->jfd) + && pPager->journalOff==jrnlBufferSize(pPager) + && pPager->dbSize>=pPager->dbOrigSize + && (!(pPg = sqlite3PcacheDirtyList(pPager->pPCache)) || 0==pPg->pDirty) + ){ + /* Update the db file change counter via the direct-write method. The + ** following call will modify the in-memory representation of page 1 + ** to include the updated change counter and then write page 1 + ** directly to the database file. Because of the atomic-write + ** property of the host file-system, this is safe. + */ + rc = pager_incr_changecounter(pPager, 1); + }else{ + rc = sqlite3JournalCreate(pPager->jfd); + if( rc==SQLITE_OK ){ + rc = pager_incr_changecounter(pPager, 0); + } } } - #else +#else +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE + if( zMaster ){ + rc = sqlite3JournalCreate(pPager->jfd); + if( rc!=SQLITE_OK ) goto commit_phase_one_exit; + } +#endif rc = pager_incr_changecounter(pPager, 0); - #endif +#endif if( rc!=SQLITE_OK ) goto commit_phase_one_exit; /* Write the master journal name into the journal file. If a master @@ -6426,8 +6470,24 @@ int sqlite3PagerCommitPhaseOne( */ rc = syncJournal(pPager, 0); if( rc!=SQLITE_OK ) goto commit_phase_one_exit; - + + if( bBatch ){ + /* The pager is now in DBMOD state. But regardless of what happens + ** next, attempting to play the journal back into the database would + ** be unsafe. Close it now to make sure that does not happen. */ + sqlite3OsClose(pPager->jfd); + rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_BEGIN_ATOMIC_WRITE, 0); + if( rc!=SQLITE_OK ) goto commit_phase_one_exit; + } rc = pager_write_pagelist(pPager,sqlite3PcacheDirtyList(pPager->pPCache)); + if( bBatch ){ + if( rc==SQLITE_OK ){ + rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_COMMIT_ATOMIC_WRITE, 0); + }else{ + sqlite3OsFileControl(fd, SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE, 0); + } + } + if( rc!=SQLITE_OK ){ assert( rc!=SQLITE_IOERR_BLOCKED ); goto commit_phase_one_exit; diff --git a/src/sqlite.h.in b/src/sqlite.h.in index d836e97058..e32f66f7cf 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -580,6 +580,11 @@ int sqlite3_exec( ** SQLITE_IOCAP_IMMUTABLE flag indicates that the file is on ** read-only media and cannot be changed even by processes with ** elevated privileges. +** +** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying +** filesystem supports doing multiple write operations atomically when those +** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and +** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. */ #define SQLITE_IOCAP_ATOMIC 0x00000001 #define SQLITE_IOCAP_ATOMIC512 0x00000002 @@ -595,6 +600,7 @@ int sqlite3_exec( #define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800 #define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000 #define SQLITE_IOCAP_IMMUTABLE 0x00002000 +#define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000 /* ** CAPI3REF: File Locking Levels @@ -729,6 +735,7 @@ struct sqlite3_file { **
  • [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN] **
  • [SQLITE_IOCAP_POWERSAFE_OVERWRITE] **
  • [SQLITE_IOCAP_IMMUTABLE] +**
  • [SQLITE_IOCAP_BATCH_ATOMIC] ** ** ** The SQLITE_IOCAP_ATOMIC property means that all writes of @@ -1012,6 +1019,40 @@ struct sqlite3_io_methods { ** The [SQLITE_FCNTL_RBU] opcode is implemented by the special VFS used by ** the RBU extension only. All other VFS should return SQLITE_NOTFOUND for ** this opcode. +** +**
  • [[SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]] +** If the [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] opcode returns SQLITE_OK, then +** the file descriptor is placed in "batch write mode", which +** means all subsequent write operations will be deferred and done +** atomically at the next [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. Systems +** that do not support batch atomic writes will return SQLITE_NOTFOUND. +** ^Following a successful SQLITE_FCNTL_BEGIN_ATOMIC_WRITE and prior to +** the closing [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] or +** [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE], SQLite will make +** no VFS interface calls on the same [sqlite3_file] file descriptor +** except for calls to the xWrite method and the xFileControl method +** with [SQLITE_FCNTL_SIZE_HINT]. +** +**
  • [[SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]] +** The [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] opcode causes all write +** operations since the previous successful call to +** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be performed atomically. +** This file control returns [SQLITE_OK] if and only if the writes were +** all performed successfully and have been committed to persistent storage. +** ^Regardless of whether or not it is successful, this file control takes +** the file descriptor out of batch write mode so that all subsequent +** write operations are independent. +** ^SQLite will never invoke SQLITE_FCNTL_COMMIT_ATOMIC_WRITE without +** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]. +** +**
  • [[SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE]] +** The [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE] opcode causes all write +** operations since the previous successful call to +** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be rolled back. +** ^This file control takes the file descriptor out of batch write mode +** so that all subsequent write operations are independent. +** ^SQLite will never invoke SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE without +** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]. ** */ #define SQLITE_FCNTL_LOCKSTATE 1 @@ -1043,6 +1084,9 @@ struct sqlite3_io_methods { #define SQLITE_FCNTL_JOURNAL_POINTER 28 #define SQLITE_FCNTL_WIN32_GET_HANDLE 29 #define SQLITE_FCNTL_PDB 30 +#define SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31 +#define SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32 +#define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33 /* deprecated names */ #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE diff --git a/src/sqliteInt.h b/src/sqliteInt.h index fc24885e28..7b9cde0bff 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -625,6 +625,15 @@ # define SQLITE_DEFAULT_PCACHE_INITSZ 20 #endif +/* +** The compile-time options SQLITE_MMAP_READWRITE and +** SQLITE_ENABLE_BATCH_ATOMIC_WRITE are not compatible with one another. +** You must choose one or the other (or neither) but not both. +*/ +#if defined(SQLITE_MMAP_READWRITE) && defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) +#error Cannot use both SQLITE_MMAP_READWRITE and SQLITE_ENABLE_BATCH_ATOMIC_WRITE +#endif + /* ** GCC does not define the offsetof() macro so we'll have to do it ** ourselves. @@ -4256,7 +4265,8 @@ int sqlite3FindInIndex(Parse *, Expr *, u32, int*, int*); int sqlite3JournalOpen(sqlite3_vfs *, const char *, sqlite3_file *, int, int); int sqlite3JournalSize(sqlite3_vfs *); -#ifdef SQLITE_ENABLE_ATOMIC_WRITE +#if defined(SQLITE_ENABLE_ATOMIC_WRITE) \ + || defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) int sqlite3JournalCreate(sqlite3_file *); #endif diff --git a/src/test1.c b/src/test1.c index 87b255c9e9..0fc9d8a876 100644 --- a/src/test1.c +++ b/src/test1.c @@ -2550,6 +2550,46 @@ static int SQLITE_TCLAPI test_delete_database( return TCL_OK; } +/* +** Usage: atomic_batch_write PATH +*/ +static int SQLITE_TCLAPI test_atomic_batch_write( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + char *zFile = 0; /* Path to file to test */ + sqlite3 *db = 0; /* Database handle */ + sqlite3_file *pFd = 0; /* SQLite fd open on zFile */ + int bRes = 0; /* Integer result of this command */ + int dc = 0; /* Device-characteristics mask */ + int rc; /* sqlite3_open() return code */ + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "PATH"); + return TCL_ERROR; + } + zFile = Tcl_GetString(objv[1]); + + rc = sqlite3_open(zFile, &db); + if( rc!=SQLITE_OK ){ + Tcl_AppendResult(interp, sqlite3_errmsg(db), 0); + sqlite3_close(db); + return TCL_ERROR; + } + + rc = sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, (void*)&pFd); + dc = pFd->pMethods->xDeviceCharacteristics(pFd); + if( dc & SQLITE_IOCAP_BATCH_ATOMIC ){ + bRes = 1; + } + + Tcl_SetObjResult(interp, Tcl_NewIntObj(bRes)); + sqlite3_close(db); + return TCL_OK; +} + /* ** Usage: sqlite3_next_stmt DB STMT ** @@ -7639,6 +7679,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "sqlite3_snapshot_cmp_blob", test_snapshot_cmp_blob, 0 }, #endif { "sqlite3_delete_database", test_delete_database, 0 }, + { "atomic_batch_write", test_atomic_batch_write, 0 }, }; static int bitmask_size = sizeof(Bitmask)*8; static int longdouble_size = sizeof(LONGDOUBLE_TYPE); diff --git a/src/test6.c b/src/test6.c index 849cdeb3b9..9a3aa093f0 100644 --- a/src/test6.c +++ b/src/test6.c @@ -736,6 +736,7 @@ static int processDevSymArgs( { "sequential", SQLITE_IOCAP_SEQUENTIAL }, { "safe_append", SQLITE_IOCAP_SAFE_APPEND }, { "powersafe_overwrite", SQLITE_IOCAP_POWERSAFE_OVERWRITE }, + { "batch-atomic", SQLITE_IOCAP_BATCH_ATOMIC }, { 0, 0 } }; @@ -976,7 +977,30 @@ static int SQLITE_TCLAPI devSymObjCmd( devsym_register(iDc, iSectorSize); return TCL_OK; +} +/* +** tclcmd: sqlite3_crash_on_write N +*/ +static int SQLITE_TCLAPI writeCrashObjCmd( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + void devsym_crash_on_write(int); + int nWrite = 0; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "NWRITE"); + return TCL_ERROR; + } + if( Tcl_GetIntFromObj(interp, objv[1], &nWrite) ){ + return TCL_ERROR; + } + + devsym_crash_on_write(nWrite); + return TCL_OK; } /* @@ -1068,6 +1092,7 @@ int Sqlitetest6_Init(Tcl_Interp *interp){ Tcl_CreateObjCommand(interp, "sqlite3_crashparams", crashParamsObjCmd, 0, 0); Tcl_CreateObjCommand(interp, "sqlite3_crash_now", crashNowCmd, 0, 0); Tcl_CreateObjCommand(interp, "sqlite3_simulate_device", devSymObjCmd, 0, 0); + Tcl_CreateObjCommand(interp, "sqlite3_crash_on_write", writeCrashObjCmd,0,0); Tcl_CreateObjCommand(interp, "unregister_devsim", dsUnregisterObjCmd, 0, 0); Tcl_CreateObjCommand(interp, "register_jt_vfs", jtObjCmd, 0, 0); Tcl_CreateObjCommand(interp, "unregister_jt_vfs", jtUnregisterObjCmd, 0, 0); diff --git a/src/test_devsym.c b/src/test_devsym.c index 9a1ba09d69..0da6671cf4 100644 --- a/src/test_devsym.c +++ b/src/test_devsym.c @@ -28,6 +28,7 @@ ** Name used to identify this VFS. */ #define DEVSYM_VFS_NAME "devsym" +#define WRITECRASH_NAME "writecrash" typedef struct devsym_file devsym_file; struct devsym_file { @@ -72,61 +73,13 @@ static int devsymRandomness(sqlite3_vfs*, int nByte, char *zOut); static int devsymSleep(sqlite3_vfs*, int microseconds); static int devsymCurrentTime(sqlite3_vfs*, double*); -static sqlite3_vfs devsym_vfs = { - 2, /* iVersion */ - sizeof(devsym_file), /* szOsFile */ - DEVSYM_MAX_PATHNAME, /* mxPathname */ - 0, /* pNext */ - DEVSYM_VFS_NAME, /* zName */ - 0, /* pAppData */ - devsymOpen, /* xOpen */ - devsymDelete, /* xDelete */ - devsymAccess, /* xAccess */ - devsymFullPathname, /* xFullPathname */ -#ifndef SQLITE_OMIT_LOAD_EXTENSION - devsymDlOpen, /* xDlOpen */ - devsymDlError, /* xDlError */ - devsymDlSym, /* xDlSym */ - devsymDlClose, /* xDlClose */ -#else - 0, /* xDlOpen */ - 0, /* xDlError */ - 0, /* xDlSym */ - 0, /* xDlClose */ -#endif /* SQLITE_OMIT_LOAD_EXTENSION */ - devsymRandomness, /* xRandomness */ - devsymSleep, /* xSleep */ - devsymCurrentTime, /* xCurrentTime */ - 0, /* xGetLastError */ - 0 /* xCurrentTimeInt64 */ -}; - -static sqlite3_io_methods devsym_io_methods = { - 2, /* iVersion */ - devsymClose, /* xClose */ - devsymRead, /* xRead */ - devsymWrite, /* xWrite */ - devsymTruncate, /* xTruncate */ - devsymSync, /* xSync */ - devsymFileSize, /* xFileSize */ - devsymLock, /* xLock */ - devsymUnlock, /* xUnlock */ - devsymCheckReservedLock, /* xCheckReservedLock */ - devsymFileControl, /* xFileControl */ - devsymSectorSize, /* xSectorSize */ - devsymDeviceCharacteristics, /* xDeviceCharacteristics */ - devsymShmMap, /* xShmMap */ - devsymShmLock, /* xShmLock */ - devsymShmBarrier, /* xShmBarrier */ - devsymShmUnmap /* xShmUnmap */ -}; - struct DevsymGlobal { sqlite3_vfs *pVfs; int iDeviceChar; int iSectorSize; + int nWriteCrash; }; -struct DevsymGlobal g = {0, 0, 512}; +struct DevsymGlobal g = {0, 0, 512, 0}; /* ** Close an devsym-file. @@ -271,6 +224,26 @@ static int devsymOpen( int flags, int *pOutFlags ){ +static sqlite3_io_methods devsym_io_methods = { + 2, /* iVersion */ + devsymClose, /* xClose */ + devsymRead, /* xRead */ + devsymWrite, /* xWrite */ + devsymTruncate, /* xTruncate */ + devsymSync, /* xSync */ + devsymFileSize, /* xFileSize */ + devsymLock, /* xLock */ + devsymUnlock, /* xUnlock */ + devsymCheckReservedLock, /* xCheckReservedLock */ + devsymFileControl, /* xFileControl */ + devsymSectorSize, /* xSectorSize */ + devsymDeviceCharacteristics, /* xDeviceCharacteristics */ + devsymShmMap, /* xShmMap */ + devsymShmLock, /* xShmLock */ + devsymShmBarrier, /* xShmBarrier */ + devsymShmUnmap /* xShmUnmap */ +}; + int rc; devsym_file *p = (devsym_file *)pFile; p->pReal = (sqlite3_file *)&p[1]; @@ -372,6 +345,137 @@ static int devsymCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ return g.pVfs->xCurrentTime(g.pVfs, pTimeOut); } +/* +** Return the sector-size in bytes for an writecrash-file. +*/ +static int writecrashSectorSize(sqlite3_file *pFile){ + devsym_file *p = (devsym_file *)pFile; + return sqlite3OsSectorSize(p->pReal); +} + +/* +** Return the device characteristic flags supported by an writecrash-file. +*/ +static int writecrashDeviceCharacteristics(sqlite3_file *pFile){ + devsym_file *p = (devsym_file *)pFile; + return sqlite3OsDeviceCharacteristics(p->pReal); +} + +/* +** Write data to an writecrash-file. +*/ +static int writecrashWrite( + sqlite3_file *pFile, + const void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + devsym_file *p = (devsym_file *)pFile; + if( g.nWriteCrash>0 ){ + g.nWriteCrash--; + if( g.nWriteCrash==0 ) abort(); + } + return sqlite3OsWrite(p->pReal, zBuf, iAmt, iOfst); +} + +/* +** Open an writecrash file handle. +*/ +static int writecrashOpen( + sqlite3_vfs *pVfs, + const char *zName, + sqlite3_file *pFile, + int flags, + int *pOutFlags +){ +static sqlite3_io_methods writecrash_io_methods = { + 2, /* iVersion */ + devsymClose, /* xClose */ + devsymRead, /* xRead */ + writecrashWrite, /* xWrite */ + devsymTruncate, /* xTruncate */ + devsymSync, /* xSync */ + devsymFileSize, /* xFileSize */ + devsymLock, /* xLock */ + devsymUnlock, /* xUnlock */ + devsymCheckReservedLock, /* xCheckReservedLock */ + devsymFileControl, /* xFileControl */ + writecrashSectorSize, /* xSectorSize */ + writecrashDeviceCharacteristics, /* xDeviceCharacteristics */ + devsymShmMap, /* xShmMap */ + devsymShmLock, /* xShmLock */ + devsymShmBarrier, /* xShmBarrier */ + devsymShmUnmap /* xShmUnmap */ +}; + + int rc; + devsym_file *p = (devsym_file *)pFile; + p->pReal = (sqlite3_file *)&p[1]; + rc = sqlite3OsOpen(g.pVfs, zName, p->pReal, flags, pOutFlags); + if( p->pReal->pMethods ){ + pFile->pMethods = &writecrash_io_methods; + } + return rc; +} + +static sqlite3_vfs devsym_vfs = { + 2, /* iVersion */ + sizeof(devsym_file), /* szOsFile */ + DEVSYM_MAX_PATHNAME, /* mxPathname */ + 0, /* pNext */ + DEVSYM_VFS_NAME, /* zName */ + 0, /* pAppData */ + devsymOpen, /* xOpen */ + devsymDelete, /* xDelete */ + devsymAccess, /* xAccess */ + devsymFullPathname, /* xFullPathname */ +#ifndef SQLITE_OMIT_LOAD_EXTENSION + devsymDlOpen, /* xDlOpen */ + devsymDlError, /* xDlError */ + devsymDlSym, /* xDlSym */ + devsymDlClose, /* xDlClose */ +#else + 0, /* xDlOpen */ + 0, /* xDlError */ + 0, /* xDlSym */ + 0, /* xDlClose */ +#endif /* SQLITE_OMIT_LOAD_EXTENSION */ + devsymRandomness, /* xRandomness */ + devsymSleep, /* xSleep */ + devsymCurrentTime, /* xCurrentTime */ + 0, /* xGetLastError */ + 0 /* xCurrentTimeInt64 */ +}; + +static sqlite3_vfs writecrash_vfs = { + 2, /* iVersion */ + sizeof(devsym_file), /* szOsFile */ + DEVSYM_MAX_PATHNAME, /* mxPathname */ + 0, /* pNext */ + WRITECRASH_NAME, /* zName */ + 0, /* pAppData */ + writecrashOpen, /* xOpen */ + devsymDelete, /* xDelete */ + devsymAccess, /* xAccess */ + devsymFullPathname, /* xFullPathname */ +#ifndef SQLITE_OMIT_LOAD_EXTENSION + devsymDlOpen, /* xDlOpen */ + devsymDlError, /* xDlError */ + devsymDlSym, /* xDlSym */ + devsymDlClose, /* xDlClose */ +#else + 0, /* xDlOpen */ + 0, /* xDlError */ + 0, /* xDlSym */ + 0, /* xDlClose */ +#endif /* SQLITE_OMIT_LOAD_EXTENSION */ + devsymRandomness, /* xRandomness */ + devsymSleep, /* xSleep */ + devsymCurrentTime, /* xCurrentTime */ + 0, /* xGetLastError */ + 0 /* xCurrentTimeInt64 */ +}; + /* ** This procedure registers the devsym vfs with SQLite. If the argument is @@ -379,10 +483,13 @@ static int devsymCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ ** available function in this file. */ void devsym_register(int iDeviceChar, int iSectorSize){ + if( g.pVfs==0 ){ g.pVfs = sqlite3_vfs_find(0); devsym_vfs.szOsFile += g.pVfs->szOsFile; + writecrash_vfs.szOsFile += g.pVfs->szOsFile; sqlite3_vfs_register(&devsym_vfs, 0); + sqlite3_vfs_register(&writecrash_vfs, 0); } if( iDeviceChar>=0 ){ g.iDeviceChar = iDeviceChar; @@ -403,4 +510,15 @@ void devsym_unregister(){ g.iSectorSize = 0; } +void devsym_crash_on_write(int nWrite){ + if( g.pVfs==0 ){ + g.pVfs = sqlite3_vfs_find(0); + devsym_vfs.szOsFile += g.pVfs->szOsFile; + writecrash_vfs.szOsFile += g.pVfs->szOsFile; + sqlite3_vfs_register(&devsym_vfs, 0); + sqlite3_vfs_register(&writecrash_vfs, 0); + } + g.nWriteCrash = nWrite; +} + #endif diff --git a/test/fallocate.test b/test/fallocate.test index f523c2cc4a..63d88ea885 100644 --- a/test/fallocate.test +++ b/test/fallocate.test @@ -59,7 +59,9 @@ do_test fallocate-1.6 { # do_test fallocate-1.7 { execsql { BEGIN; INSERT INTO t1 VALUES(1, 2); } - if {[permutation] != "inmemory_journal"} { + if {[permutation] != "inmemory_journal" + && [permutation] != "atomic-batch-write" + } { hexio_get_int [hexio_read test.db-journal 16 4] } else { set {} 1024 diff --git a/test/misc1.test b/test/misc1.test index e646bfd098..be64a8f7a9 100644 --- a/test/misc1.test +++ b/test/misc1.test @@ -479,26 +479,28 @@ ifcapable curdir { # Make sure a database connection still works after changing the # working directory. # -do_test misc1-14.1 { - file mkdir tempdir - cd tempdir - execsql {BEGIN} - file exists ./test.db-journal -} {0} -do_test misc1-14.2a { - execsql {UPDATE t1 SET a=a||'x' WHERE 0} - file exists ../test.db-journal -} {0} -do_test misc1-14.2b { - execsql {UPDATE t1 SET a=a||'y' WHERE 1} - file exists ../test.db-journal -} {1} -do_test misc1-14.3 { - cd .. - forcedelete tempdir - execsql {COMMIT} - file exists ./test.db-journal -} {0} +if {[atomic_batch_write test.db]==0} { + do_test misc1-14.1 { + file mkdir tempdir + cd tempdir + execsql {BEGIN} + file exists ./test.db-journal + } {0} + do_test misc1-14.2a { + execsql {UPDATE t1 SET a=a||'x' WHERE 0} + file exists ../test.db-journal + } {0} + do_test misc1-14.2b { + execsql {UPDATE t1 SET a=a||'y' WHERE 1} + file exists ../test.db-journal + } {1} + do_test misc1-14.3 { + cd .. + forcedelete tempdir + execsql {COMMIT} + file exists ./test.db-journal + } {0} +} } # A failed create table should not leave the table in the internal diff --git a/test/permutations.test b/test/permutations.test index 92eb2eec07..7b6ef3d402 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -384,6 +384,30 @@ test_suite "vfslog" -prefix "" -description { wal* mmap* ] +test_suite "atomic-batch-write" -prefix "" -description { + Like veryquick.test, but must be run on a file-system that supports + atomic-batch-writes. Tests that depend on the journal file being present + are omitted. +} -files [ + test_set $allquicktests -exclude *malloc* *ioerr* *fault* *bigfile* *_err* \ + *fts5corrupt* *fts5big* *fts5aj* \ + crash8.test delete_db.test \ + exclusive.test journal3.test \ + journal1.test \ + jrnlmode.test jrnlmode2.test \ + lock4.test pager1.test \ + pager3.test sharedA.test \ + symlink.test stmt.test \ + sync.test sync2.test \ + tempdb.test tkt3457.test \ + vacuum5.test wal2.test \ + walmode.test zerodamage.test +] -initialize { + if {[atomic_batch_write test.db]==0} { + error "File system does NOT support atomic-batch-write" + } +} + lappend ::testsuitelist xxx #------------------------------------------------------------------------- # Define the coverage related test suites: diff --git a/test/rollback.test b/test/rollback.test index 7abafece61..60a6190317 100644 --- a/test/rollback.test +++ b/test/rollback.test @@ -82,6 +82,7 @@ do_test rollback-1.9 { if {$tcl_platform(platform) == "unix" && [permutation] ne "onefile" && [permutation] ne "inmemory_journal" + && [permutation] ne "atomic-batch-write" } { do_test rollback-2.1 { execsql { diff --git a/test/syscall.test b/test/syscall.test index 5716b35a6e..25b034e7c0 100644 --- a/test/syscall.test +++ b/test/syscall.test @@ -61,7 +61,7 @@ foreach s { fcntl read pread write pwrite fchmod fallocate pread64 pwrite64 unlink openDirectory mkdir rmdir statvfs fchown geteuid umask mmap munmap mremap - getpagesize readlink lstat + getpagesize readlink lstat ioctl } { if {[test_syscall exists $s]} {lappend syscall_list $s} } diff --git a/test/tester.tcl b/test/tester.tcl index dc6547d033..38c19701c6 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -1608,6 +1608,54 @@ proc crashsql {args} { lappend r $msg } +# crash_on_write ?-devchar DEVCHAR? CRASHDELAY SQL +# +proc crash_on_write {args} { + + set nArg [llength $args] + if {$nArg<2 || $nArg%2} { + error "bad args: $args" + } + set zSql [lindex $args end] + set nDelay [lindex $args end-1] + + set devchar {} + for {set ii 0} {$ii < $nArg-2} {incr ii 2} { + set opt [lindex $args $ii] + switch -- [lindex $args $ii] { + -devchar { + set devchar [lindex $args [expr $ii+1]] + } + + default { error "unrecognized option: $opt" } + } + } + + set f [open crash.tcl w] + puts $f "sqlite3_crash_on_write $nDelay" + puts $f "sqlite3_test_control_pending_byte $::sqlite_pending_byte" + puts $f "sqlite3 db test.db -vfs writecrash" + puts $f "db eval {$zSql}" + puts $f "set {} {}" + + close $f + set r [catch { + exec [info nameofexec] crash.tcl >@stdout + } msg] + + # Windows/ActiveState TCL returns a slightly different + # error message. We map that to the expected message + # so that we don't have to change all of the test + # cases. + if {$::tcl_platform(platform)=="windows"} { + if {$msg=="child killed: unknown signal"} { + set msg "child process exited abnormally" + } + } + + lappend r $msg +} + proc run_ioerr_prep {} { set ::sqlite_io_error_pending 0 catch {db close} diff --git a/test/writecrash.test b/test/writecrash.test new file mode 100644 index 0000000000..25fe6b423f --- /dev/null +++ b/test/writecrash.test @@ -0,0 +1,62 @@ +# 2009 January 8 +# +# 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 the outcome of a writer crashing within a call to the VFS +# xWrite function. +# + + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix writecrash + +do_not_use_codec + +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b BLOB UNIQUE); + WITH s(i) AS ( + VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<100 + ) + INSERT INTO t1 SELECT NULL, randomblob(900) FROM s; +} {} + +set bGo 1 +for {set tn 1} {$bGo} {incr tn} { + +db close +sqlite3 db test.db + + do_test 1.$tn.1 { + set res [crash_on_write $tn { + UPDATE t1 SET b = randomblob(899) WHERE (a%3)==0 + }] + set bGo 0 + if {[string match {1 {child killed:*}} $res]} { + set res {0 {}} + set bGo 1 + } + set res + } {0 {}} + +#db close +#sqlite3 db test.db + + do_execsql_test 1.$tn.2 { PRAGMA integrity_check } {ok} + +db close +sqlite3 db test.db + + do_execsql_test 1.$tn.3 { PRAGMA integrity_check } {ok} +} + + + +finish_test