]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Added support for proxy file locking style
authoraswift <aswift@noemail.net>
Fri, 21 Nov 2008 00:10:35 +0000 (00:10 +0000)
committeraswift <aswift@noemail.net>
Fri, 21 Nov 2008 00:10:35 +0000 (00:10 +0000)
Added pragma support for controlling proxy file locking
Added file control access to last errno and proxy locking
Added support for TMPDIR environment variable
Extended unit tests to cover new proxy locking pragmas and file control features (CVS 5934)

FossilOrigin-Name: b9bc36d3d5e35821ef69c0881a84c0afed253c9e

15 files changed:
manifest
manifest.uuid
src/os_unix.c
src/pragma.c
src/sqlite.h.in
src/test1.c
src/test_config.c
test/exclusive.test
test/filectrl.test
test/lock5.test
test/lock6.test [new file with mode: 0644]
test/manydb.test
test/pragma.test
test/shared.test
test/tester.tcl

index 2dce09267f9eff6baa4c4db79ec59211d625b5a9..027d4184f9772361f0c152c6af687c01ff286925 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C When\sa\smemory\sallocation\sfails\son\sthe\s%Q\sconversion\sin\ssqlite3_mprintf(),\nmake\ssure\sthe\serror\sis\sreported\sback\sup\sthe\scall\sstack.\s(CVS\s5933)
-D 2008-11-20T18:20:28
+C Added\ssupport\sfor\sproxy\sfile\slocking\sstyle\nAdded\spragma\ssupport\sfor\scontrolling\sproxy\sfile\slocking\nAdded\sfile\scontrol\saccess\sto\slast\serrno\sand\sproxy\slocking\nAdded\ssupport\sfor\sTMPDIR\senvironment\svariable\nExtended\sunit\stests\sto\scover\snew\sproxy\slocking\spragmas\sand\sfile\scontrol\sfeatures\s(CVS\s5934)
+D 2008-11-21T00:10:35
 F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0
 F Makefile.in 0aa7bbe3be6acc4045706e3bb3fd0b8f38f4a3b5
 F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654
@@ -137,7 +137,7 @@ F src/os.c 0b411644b87ad689d7250bbfd1834d99b81a3df4
 F src/os.h ef8abeb9afc694b82dbd169a91c9b7e26db3c892
 F src/os_common.h 24525d8b7bce66c374dfc1810a6c9043f3359b60
 F src/os_os2.c d12285d66df674c42f6f544a6f7c21bf1a954ee1
-F src/os_unix.c 03c76b5269361adcd68cf9d6713181922535ac6e
+F src/os_unix.c bdb0b66407141b7cd3520e92c685ea097b191c0e
 F src/os_win.c 3dff41670fb9798a869c636626bb7d6d8b6a45bb
 F src/pager.c db12a8333e54e7bbf62dc621ada5507adb3a6493
 F src/pager.h a02ef8e6cc7e78b54874166e5ce786c9d4c489bf
@@ -145,21 +145,21 @@ F src/parse.y 2c4758b4c5ead6de8cf7112f5a7cce7561d313fe
 F src/pcache.c f3121a531745b20f5b824201eb63949a7e2959ac
 F src/pcache.h f20c3e82dd6da622c3fe296170cb1801f9a2d75a
 F src/pcache1.c 2453a3e925841fdb610f39bba24def8b2ad4a908
-F src/pragma.c 0a5051029c5b687e2f2669d91aa3281ebcbe4fdb
+F src/pragma.c 539e28c90e782fa909a3b3a6849d18a9eb11a548
 F src/prepare.c fcadb25d2ad722d87103517333c825b56b79a770
 F src/printf.c ce86aed93a704938ca4d0344aaa763271842a4b0
 F src/random.c a87afbd598aa877e23ac676ee92fd8ee5c786a51
 F src/resolve.c 4af5391d2b4c1d6c583a6805ac6660181de4545b
 F src/select.c e01537d4d8b8b81a6507e07ebb17fc232dacdda7
 F src/shell.c 650d1a87408682280d0e9d014d0d328c59c84b38
-F src/sqlite.h.in e9a0aa2502dfe01bf166956051528f28871474c3
+F src/sqlite.h.in b5d50f12fb9c7460a4ddfef8c1e799afaabefebf
 F src/sqlite3ext.h 1db7d63ab5de4b3e6b83dd03d1a4e64fef6d2a17
 F src/sqliteInt.h beacd5768e55285097fbb419bcd741721b455003
 F src/sqliteLimit.h f435e728c6b620ef7312814d660a81f9356eb5c8
 F src/status.c 237b193efae0cf6ac3f0817a208de6c6c6ef6d76
 F src/table.c 22744786199c9195720c15a7a42cb97b2e2728d8
 F src/tclsqlite.c 96049bd454f1547abff0a57c45f0dfa57701e076
-F src/test1.c 9b440b38e1710e9be1512c003fa141e0e3948e05
+F src/test1.c d915e5cf26c5ba2a9778ad5b26cbaf97412b8b74
 F src/test2.c 897528183edf2839c2a3c991d415905db56f1240
 F src/test3.c 88a246b56b824275300e6c899634fbac1dc94b14
 F src/test4.c f79ab52d27ff49b784b631a42e2ccd52cfd5c84c
@@ -171,7 +171,7 @@ F src/test9.c 904ebe0ed1472d6bad17a81e2ecbfc20017dc237
 F src/test_async.c 45024094ed7cf780c5d5dccda645145f95cf78ef
 F src/test_autoext.c f53b0cdf7bf5f08100009572a5d65cdb540bd0ad
 F src/test_btree.c d7b8716544611c323860370ee364e897c861f1b0
-F src/test_config.c bc71818f468494ff95ac1fe1f5906a21b25f937b
+F src/test_config.c 47c66ced0faa6b18fbab72cbe77098ee04789722
 F src/test_devsym.c 802d10e65b4217208cb47059b84adf46318bcdf4
 F src/test_func.c a55c4d5479ff2eb5c0a22d4d88e9528ab59c953b
 F src/test_hexio.c 2f1122aa3f012fa0142ee3c36ce5c902a70cd12f
@@ -293,11 +293,11 @@ F test/enc.test e54531cd6bf941ee6760be041dff19a104c7acea
 F test/enc2.test 6d91a5286f59add0cfcbb2d0da913b76f2242398
 F test/enc3.test 5c550d59ff31dccdba5d1a02ae11c7047d77c041
 F test/eval.test bc269c365ba877554948441e91ad5373f9f91be3
-F test/exclusive.test 8d32ccf8eaf0260977dc8406bd70080ca2d7e6f8
+F test/exclusive.test ecc64c394f5086d02159d8c0a82520f11420cf6a
 F test/exclusive2.test 7d2b1c0370f1e1dac4a728bd653f2dea5100fcf6
 F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7
 F test/expr.test 135ed46c049916688171e618c5c14312811618d4
-F test/filectrl.test 524853082d5d7fb442599730ec3a0f3f84a3a936
+F test/filectrl.test 8923a6dc7630f31c8a9dd3d3d740aa0922df7bf8
 F test/filefmt.test 053b622009fbbb74dd37921ffad374d852c13cd8
 F test/fkey1.test c373e4f1ec43416957e0591c4a5cebb63b8a12e7
 F test/format4.test 1f0cac8ff3895e9359ed87e41aaabee982a812eb
@@ -410,7 +410,8 @@ F test/lock.test 6825aea0b5885578b1b63a3b178803842c4ee9f1
 F test/lock2.test 018b846f6f3b3b695fad07e317b7988442b556f4
 F test/lock3.test 615111293cf32aa2ed16d01c6611737651c96fb9
 F test/lock4.test 09d97d52cae18fadfe631552af9880dac6b3ae90
-F test/lock5.test 904c20aec51d5dbff0a3aec6a4d35c5ae0257449
+F test/lock5.test a6eaac62eb14bc125f9cc0c2d06a80009fc67587
+F test/lock6.test f4e9052b14da3bd6807a757d5aed15c17321031a
 F test/lookaside.test e69f822f13745f1d5c445c6e30e30f059f30c8e5
 F test/main.test 187a9a1b5248ed74a83838c581c15ec6023b555b
 F test/malloc.test 248fc5e0be632d8db1d771ed5413a93c47212c5a
@@ -433,7 +434,7 @@ F test/mallocH.test 79b65aed612c9b3ed2dcdaa727c85895fd1bfbdb
 F test/mallocI.test 6e24fe6444bd2999ccc81f984977b44c0d6e5591
 F test/mallocJ.test 44dfbbaca731cb933818ad300b4566265d652609
 F test/malloc_common.tcl 984baeb6c6b185e798827d1187d426acc2bc4962
-F test/manydb.test 8de36b8d33aab5ef295b11d9e95310aeded31af8
+F test/manydb.test b3d3bc4c25657e7f68d157f031eb4db7b3df0d3c
 F test/memdb.test a67bda4ff90a38f2b19f6c7f95aa7289e051d893
 F test/memleak.test d2d2a1ff7105d32dc3fdf691458cf6cba58c7217
 F test/memsubsys1.test bdc24a38d198679d777ca4efcc089fd3948567a6
@@ -463,7 +464,7 @@ F test/pagesize.test 0d9ff3fedfce6e5ffe8fa7aca9b6d3433a2e843b
 F test/pcache.test 515b4c26e9f57660357dfff5b6b697acac1abc5f
 F test/pcache2.test 46efd980a89f737847b99327bda19e08fe11e402
 F test/permutations.test 5308a94878efc81a8e8ce133926dfb2c53d19133
-F test/pragma.test 165372b62391d233715cde82d99f34d306f9257f
+F test/pragma.test 0f299601c3b15e8941eb48d2f7a43e6678e3f735
 F test/pragma2.test 5364893491b9231dd170e3459bfc2e2342658b47
 F test/printf.test 262a5acd3158f788e9bdf7f18d718f3af32ff6ef
 F test/progress.test 5b075c3c790c7b2a61419bc199db87aaf48b8301 x
@@ -491,7 +492,7 @@ F test/selectA.test 06d1032fa9009314c95394f2ca2e60d9f7ae8532
 F test/selectB.test 31e81ac9af7d224850e0706350f070ecb92fcbc7
 F test/selectC.test 44f4e1b2c86e40f8135294a826ec4142766e21c3
 F test/server1.test f5b790d4c0498179151ca8a7715a65a7802c859c
-F test/shared.test b9f3bbd3ba727c5f1f8c815b7d0199262aacf214
+F test/shared.test 2f6c65de8123c130b92e4e18a516f669eaa02fea
 F test/shared2.test 0ee9de8964d70e451936a48c41cb161d9134ccf4
 F test/shared3.test 9c880afc081d797da514ef64bccf36f3fce2f09c
 F test/shared4.test d0fadacb50bb6981b2fb9dc6d1da30fa1edddf83
@@ -519,7 +520,7 @@ F test/tableapi.test 505031f15b18a750184d967d2c896cf88fcc969c
 F test/tclsqlite.test 001682e3c188967fbd790c617991efadf9518386
 F test/tempdb.test b88ac8a19823cf771d742bf61eef93ef337c06b1
 F test/temptable.test 19b851b9e3e64d91e9867619b2a3f5fffee6e125
-F test/tester.tcl 12fd8394caeb71f7d961707da8668756389bc9d3
+F test/tester.tcl 66c41fc4d8a7f185d9abb21d68821c1f05e41f53
 F test/thread001.test dda1d39cea954d7d43f520891d77a93d3325ab58
 F test/thread002.test 84c03a9fc4f7a5f92eefe551266afa840c2eb6ae
 F test/thread003.test e17754799649c2b732c295620dca041c32f01e16
@@ -659,7 +660,7 @@ F tool/speedtest16.c c8a9c793df96db7e4933f0852abb7a03d48f2e81
 F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
-P 00b68a1e3164fbf04dabb480c2037be45612b4e4
-R 38e4d91990d6f71d4806b7fd8231a0a6
-U drh
-Z c8c595ff0394bbf0b4f1b46a403a8fd7
+P eebacbc9d7d0625dfbe6367046fa4a0ca9c04e74
+R 91b324303811443cc8a01d95fd1a412a
+U aswift
+Z 225056935d0aa6350680303c08feb50b
index cb60f91a955a2f8ffe86ae411a9985872ee5458a..24bbfcda51ba18d00fbb1d58a5fc0b76e62f9757 100644 (file)
@@ -1 +1 @@
-eebacbc9d7d0625dfbe6367046fa4a0ca9c04e74
\ No newline at end of file
+b9bc36d3d5e35821ef69c0881a84c0afed253c9e
\ No newline at end of file
index e016fecde0e4140f6f582fb76829302b42470f0c..a75514d8af081f201b5ae018ada4e4e4c62f73cb 100644 (file)
@@ -12,7 +12,7 @@
 **
 ** This file contains code that is specific to Unix systems.
 **
-** $Id: os_unix.c,v 1.216 2008/11/19 16:52:44 danielk1977 Exp $
+** $Id: os_unix.c,v 1.217 2008/11/21 00:10:35 aswift Exp $
 */
 #include "sqliteInt.h"
 #if SQLITE_OS_UNIX              /* This file is used on unix only */
@@ -26,7 +26,8 @@
 **   * Dot-file locking,
 **   * flock() locking,
 **   * AFP locking (OSX only),
-**   * Named POSIX semaphores (VXWorks only).
+**   * Named POSIX semaphores (VXWorks only),
+**   * proxy locking.
 **
 ** SQLITE_ENABLE_LOCKING_STYLE only works on a Mac. It is turned on by
 ** default on a Mac and disabled on all other posix platforms.
 # define SQLITE_DEFAULT_FILE_PERMISSIONS 0644
 #endif
 
+/*
+ ** Default permissions when creating auto proxy dir
+ */
+#ifndef SQLITE_DEFAULT_PROXYDIR_PERMISSIONS
+# define SQLITE_DEFAULT_PROXYDIR_PERMISSIONS 0755
+#endif
+
 /*
 ** Maximum supported path-length.
 */
@@ -144,6 +152,9 @@ struct unixFile {
   int isDelete;             /* Delete on close if true */
   char *zRealpath;
 #endif
+#if SQLITE_ENABLE_LOCKING_STYLE
+  int oflags;               /* The flags specified at open */
+#endif
 };
 
 /*
@@ -414,10 +425,12 @@ static Hash nameHash;
 ** DOTLOCK isn't a true locking style, it refers to the use of a special
 **   file named the same as the database file with a '.lock' extension, this
 **   can be used on file systems that do not offer any reliable file locking
-** NO locking means that no locking will be attempted, this is only used for
+** NONE locking means that no locking will be attempted, this is only used for
 **   read-only file systems currently
 ** NAMEDSEM is similar to DOTLOCK but uses a named semaphore instead of an
 **   indicator file.
+** PROXY uses a second file to represent the lock state of the database file
+**   which is never actually locked, a third file controls access to the proxy
 ** UNSUPPORTED means that no locking will be attempted, this is only used for
 **   file systems that are known to be unsupported
 */
@@ -427,6 +440,7 @@ static Hash nameHash;
 #define LOCKING_STYLE_FLOCK        4
 #define LOCKING_STYLE_AFP          5
 #define LOCKING_STYLE_NAMEDSEM     6
+#define LOCKING_STYLE_PROXY        7
 
 /*
 ** Only set the lastErrno if the error code is a real error and not 
@@ -739,6 +753,31 @@ vxrealpath(const char *pathname, int dostat)
 #endif
 
 #if SQLITE_ENABLE_LOCKING_STYLE
+/*
+** The proxyLockingContext has the path and file structures for the remote 
+** and local proxy files in it
+*/
+typedef struct proxyLockingContext proxyLockingContext;
+struct proxyLockingContext {
+  unixFile *conchFile;
+  char *conchFilePath;
+  unixFile *lockProxy;
+  char *lockProxyPath;
+  char *dbPath;
+  int conchHeld;
+  void *oldLockingContext; /* preserve the original locking context for close */
+  sqlite3_io_methods const *pOldMethod; /* ditto pMethod */
+};
+
+static int getDbPathForUnixFile(unixFile *pFile, char *dbPath);
+static int getLockPath(const char *dbPath, char *lPath, size_t maxLen);
+static sqlite3_io_methods *ioMethodForLockingStyle(int style);
+static int createProxyUnixFile(const char *path, unixFile **ppFile);
+static int fillInUnixFile(sqlite3_vfs *pVfs, int h, int dirfd, sqlite3_file *pId, const char *zFilename, int noLock, int isDelete);
+static int takeConch(unixFile *pFile);
+static int releaseConch(unixFile *pFile);
+static int unixRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf);
+
 /*
 ** Tests a byte-range locking query to see if byte range locks are 
 ** supported, if not we fall back to dotlockLockingStyle.
@@ -806,7 +845,6 @@ static int detectLockingStyle(
 #else
     { "smbfs",  LOCKING_STYLE_FLOCK },
 #endif
-    { "msdos",  LOCKING_STYLE_DOTFILE },
     { "webdav", LOCKING_STYLE_NONE },
     { 0, 0 }
   };
@@ -816,7 +854,7 @@ static int detectLockingStyle(
   if( !filePath ){
     return LOCKING_STYLE_NONE;
   }
-  if( pVfs->pAppData ){
+  if( pVfs && pVfs->pAppData ){
     return SQLITE_PTR_TO_INT(pVfs->pAppData);
   }
 
@@ -838,7 +876,7 @@ static int detectLockingStyle(
 }
 #else
   #define detectLockingStyle(x,y,z) LOCKING_STYLE_POSIX
-#endif /* ifdef SQLITE_ENABLE_LOCKING_STYLE */
+#endif /* if SQLITE_ENABLE_LOCKING_STYLE */
 
 /*
 ** Given a file descriptor, locate lockInfo and openCnt structures that
@@ -848,7 +886,7 @@ static int detectLockingStyle(
 ** Return an appropriate error code.
 */
 static int findLockInfo(
-  int fd,                      /* The file descriptor used in the key */
+  unixFile *pFile,             /* Unix file with file desc used in the key */
 #if IS_VXWORKS
   void *rnam,                  /* vxWorks realname */
 #endif
@@ -856,15 +894,18 @@ static int findLockInfo(
   struct openCnt **ppOpen      /* Return the openCnt structure here */
 ){
   int rc;
+  int fd;
   struct lockKey key1;
   struct openKey key2;
   struct stat statbuf;
   struct lockInfo *pLock;
   struct openCnt *pOpen;
+  fd = pFile->h;
   rc = fstat(fd, &statbuf);
   if( rc!=0 ){
+    pFile->lastErrno = errno;
 #ifdef EOVERFLOW
-    if( errno==EOVERFLOW ) return SQLITE_NOLFS;
+    if( pFile->lastErrno==EOVERFLOW ) return SQLITE_NOLFS;
 #endif
     return SQLITE_IOERR;
   }
@@ -883,6 +924,7 @@ static int findLockInfo(
     write(fd, "S", 1);
     rc = fstat(fd, &statbuf);
     if( rc!=0 ){
+      pFile->lastErrno = errno;
       return SQLITE_IOERR;
     }
   }
@@ -907,28 +949,30 @@ static int findLockInfo(
 #else
   key2.ino = statbuf.st_ino;
 #endif
-  pLock = lockList;
-  while( pLock && memcmp(&key1, &pLock->key, sizeof(key1)) ){
-    pLock = pLock->pNext;
-  }
-  if( pLock==0 ){
-    pLock = sqlite3_malloc( sizeof(*pLock) );
+  if( ppLock!=0 ){
+    pLock = lockList;
+    while( pLock && memcmp(&key1, &pLock->key, sizeof(key1)) ){
+      pLock = pLock->pNext;
+    }
     if( pLock==0 ){
-      rc = SQLITE_NOMEM;
-      goto exit_findlockinfo;
-    }
-    pLock->key = key1;
-    pLock->nRef = 1;
-    pLock->cnt = 0;
-    pLock->locktype = 0;
-    pLock->pNext = lockList;
-    pLock->pPrev = 0;
-    if( lockList ) lockList->pPrev = pLock;
-    lockList = pLock;
-  }else{
-    pLock->nRef++;
+      pLock = sqlite3_malloc( sizeof(*pLock) );
+      if( pLock==0 ){
+        rc = SQLITE_NOMEM;
+        goto exit_findlockinfo;
+      }
+      pLock->key = key1;
+      pLock->nRef = 1;
+      pLock->cnt = 0;
+      pLock->locktype = 0;
+      pLock->pNext = lockList;
+      pLock->pPrev = 0;
+      if( lockList ) lockList->pPrev = pLock;
+      lockList = pLock;
+    }else{
+      pLock->nRef++;
+    }
+    *ppLock = pLock;
   }
-  *ppLock = pLock;
   if( ppOpen!=0 ){
     pOpen = openList;
     while( pOpen && memcmp(&key2, &pOpen->key, sizeof(key2)) ){
@@ -1019,9 +1063,9 @@ static int transferOwnership(unixFile *pFile){
   if (pFile->pLock != NULL) {
     releaseLockInfo(pFile->pLock);
 #if IS_VXWORKS
-    rc = findLockInfo(pFile->h, pFile->zRealpath, &pFile->pLock, 0);
+    rc = findLockInfo(pFile, pFile->zRealpath, &pFile->pLock, 0);
 #else
-    rc = findLockInfo(pFile->h, &pFile->pLock, 0);
+    rc = findLockInfo(pFile, &pFile->pLock, 0);
 #endif
     OSTRACE5("LOCK    %d is now %s(%s,%d)\n", pFile->h,
            locktypeName(pFile->locktype),
@@ -1045,6 +1089,9 @@ static int transferOwnership(unixFile *pFile){
 ** one system to another.  Since SQLite does not define USE_PREAD
 ** any any form by default, we will not attempt to define _XOPEN_SOURCE.
 ** See tickets #2741 and #2681.
+**
+** To avoid stomping the errno value on a failed read the lastErrno value
+** is set before returning.
 */
 static int seekAndRead(unixFile *id, sqlite3_int64 offset, void *pBuf, int cnt){
   int got;
@@ -1060,11 +1107,19 @@ static int seekAndRead(unixFile *id, sqlite3_int64 offset, void *pBuf, int cnt){
   newOffset = lseek(id->h, offset, SEEK_SET);
   SimulateIOError( newOffset-- );
   if( newOffset!=offset ){
+    if( newOffet == -1 ){
+      ((unixFile*)id)->lastErrno = errno;
+    }else{
+      ((unixFile*)id)->lastErrno = 0;                  
+    }
     return -1;
   }
   got = read(id->h, pBuf, cnt);
 #endif
   TIMER_END;
+  if( got<0 ){
+    ((unixFile*)id)->lastErrno = errno;
+  }
   OSTRACE5("READ    %-3d %5d %7lld %llu\n", id->h, got, offset, TIMER_ELAPSED);
   return got;
 }
@@ -1086,8 +1141,10 @@ static int unixRead(
   if( got==amt ){
     return SQLITE_OK;
   }else if( got<0 ){
+    /* lastErrno set by seekAndRead */
     return SQLITE_IOERR_READ;
   }else{
+    ((unixFile*)id)->lastErrno = 0; /* not a system error */
     /* Unread parts of the buffer must be zero-filled */
     memset(&((char*)pBuf)[got], 0, amt-got);
     return SQLITE_IOERR_SHORT_READ;
@@ -1097,6 +1154,9 @@ static int unixRead(
 /*
 ** Seek to the offset in id->offset then read cnt bytes into pBuf.
 ** Return the number of bytes actually read.  Update the offset.
+**
+** To avoid stomping the errno value on a failed write the lastErrno value
+** is set before returning.
 */
 static int seekAndWrite(unixFile *id, i64 offset, const void *pBuf, int cnt){
   int got;
@@ -1109,11 +1169,20 @@ static int seekAndWrite(unixFile *id, i64 offset, const void *pBuf, int cnt){
 #else
   newOffset = lseek(id->h, offset, SEEK_SET);
   if( newOffset!=offset ){
+    if( newOffet == -1 ){
+      ((unixFile*)id)->lastErrno = errno;
+    }else{
+      ((unixFile*)id)->lastErrno = 0;                  
+    }
     return -1;
   }
   got = write(id->h, pBuf, cnt);
 #endif
   TIMER_END;
+  if( got<0 ){
+    ((unixFile*)id)->lastErrno = errno;
+  }
+
   OSTRACE5("WRITE   %-3d %5d %7lld %llu\n", id->h, got, offset, TIMER_ELAPSED);
   return got;
 }
@@ -1141,8 +1210,10 @@ static int unixWrite(
   SimulateDiskfullError(( wrote=0, amt=1 ));
   if( amt>0 ){
     if( wrote<0 ){
+      /* lastErrno set by seekAndWrite */
       return SQLITE_IOERR_WRITE;
     }else{
+      ((unixFile*)id)->lastErrno = 0; /* not a system error */
       return SQLITE_FULL;
     }
   }
@@ -1290,9 +1361,11 @@ static int unixSync(sqlite3_file *id, int flags){
   rc = full_fsync(pFile->h, isFullsync, isDataOnly);
   SimulateIOError( rc=1 );
   if( rc ){
+    pFile->lastErrno = errno;
     return SQLITE_IOERR_FSYNC;
   }
   if( pFile->dirfd>=0 ){
+    int err;
     OSTRACE4("DIRSYNC %-3d (have_fullfsync=%d fullsync=%d)\n", pFile->dirfd,
             HAVE_FULLFSYNC, isFullsync);
 #ifndef SQLITE_DISABLE_DIRSYNC
@@ -1307,13 +1380,19 @@ static int unixSync(sqlite3_file *id, int flags){
        ** A failed directory sync is not a big deal.  So it seems
        ** better to ignore the error.  Ticket #1657
        */
+       /* pFile->lastErrno = errno; */
        /* return SQLITE_IOERR; */
     }
 #endif
-    close(pFile->dirfd);  /* Only need to sync once, so close the directory */
-    pFile->dirfd = -1;    /* when we are done. */
+    err = close(pFile->dirfd); /* Only need to sync once, so close the */
+    if( err==0 ){              /* directory when we are done */
+      pFile->dirfd = -1;
+    }else{
+      pFile->lastErrno = errno;
+      rc = SQLITE_IOERR_DIR_CLOSE;
+    }
   }
-  return SQLITE_OK;
+  return rc;
 }
 
 /*
@@ -1325,6 +1404,7 @@ static int unixTruncate(sqlite3_file *id, i64 nByte){
   SimulateIOError( return SQLITE_IOERR_TRUNCATE );
   rc = ftruncate(((unixFile*)id)->h, (off_t)nByte);
   if( rc ){
+    ((unixFile*)id)->lastErrno = errno;
     return SQLITE_IOERR_TRUNCATE;
   }else{
     return SQLITE_OK;
@@ -1341,6 +1421,7 @@ static int unixFileSize(sqlite3_file *id, i64 *pSize){
   rc = fstat(((unixFile*)id)->h, &buf);
   SimulateIOError( rc=1 );
   if( rc!=0 ){
+    ((unixFile*)id)->lastErrno = errno;
     return SQLITE_IOERR_FSTAT;
   }
   *pSize = buf.st_size;
@@ -1808,11 +1889,22 @@ static int unixUnlock(sqlite3_file *id, int locktype){
       if( pOpen->nLock==0 && pOpen->nPending>0 ){
         int i;
         for(i=0; i<pOpen->nPending; i++){
-          close(pOpen->aPending[i]);
+          /* close pending fds, but if closing fails don't free the array
+          ** assign -1 to the successfully closed descriptors and record the
+          ** error.  The next attempt to unlock will try again. */
+          if( pOpen->aPending[i] < 0 ) continue;
+          if( close(pOpen->aPending[i]) ){
+            pFile->lastErrno = errno;
+            rc = SQLITE_IOERR_CLOSE;
+          }else{
+            pOpen->aPending[i] = -1;
+          }
+        }
+        if( rc==SQLITE_OK ){
+          sqlite3_free(pOpen->aPending);
+          pOpen->nPending = 0;
+          pOpen->aPending = 0;
         }
-        sqlite3_free(pOpen->aPending);
-        pOpen->nPending = 0;
-        pOpen->aPending = 0;
       }
     }
   }
@@ -1833,10 +1925,20 @@ static int closeUnixFile(sqlite3_file *id){
   unixFile *pFile = (unixFile*)id;
   if( pFile ){
     if( pFile->dirfd>=0 ){
-      close(pFile->dirfd);
+      int err = close(pFile->dirfd);
+      if( err ){
+        pFile->lastErrno = errno;
+        return SQLITE_IOERR_DIR_CLOSE;
+      }else{
+        pFile->dirfd=-1;
+      }
     }
     if( pFile->h>=0 ){
-      close(pFile->h);
+      int err = close(pFile->h);
+      if( err ){
+        pFile->lastErrno = errno;
+        return SQLITE_IOERR_CLOSE;
+      }
     }
 #if IS_VXWORKS
     if( pFile->isDelete && pFile->zRealpath ){
@@ -1868,6 +1970,7 @@ static int closeUnixFile(sqlite3_file *id){
 ** Close a file.
 */
 static int unixClose(sqlite3_file *id){
+  int rc = SQLITE_OK;
   if( id ){
     unixFile *pFile = (unixFile *)id;
     unixUnlock(id, NO_LOCK);
@@ -1892,25 +1995,24 @@ static int unixClose(sqlite3_file *id){
     }
     releaseLockInfo(pFile->pLock);
     releaseOpenCnt(pFile->pOpen);
-    closeUnixFile(id);
+    rc = closeUnixFile(id);
     leaveMutex();
   }
-  return SQLITE_OK;
+  return rc;
 }
 
 
 #if SQLITE_ENABLE_LOCKING_STYLE
-
 #if !IS_VXWORKS
-#pragma mark AFP Support
+#pragma mark AFP support
 
 /*
  ** The afpLockingContext structure contains all afp lock specific state
  */
 typedef struct afpLockingContext afpLockingContext;
 struct afpLockingContext {
-  unsigned long long sharedLockByte;
-  const char *filePath;
+  unsigned long long sharedByte;
+  const char *dbPath;
 };
 
 struct ByteRangeLockPB2
@@ -1943,14 +2045,22 @@ static int _AFPFSSetLock(
   pb.offset = offset;
   pb.length = length; 
   pb.fd = pFile->h;
-  OSTRACE5("AFPLOCK setting lock %s for %d in range %llx:%llx\n", 
-    (setLockFlag?"ON":"OFF"), pFile->h, offset, length);
+  //SimulateIOErrorBenign(1);
+  //SimulateIOError( pb.fd=(-1) )
+  //SimulateIOErrorBenign(0);
+  
+  OSTRACE6("AFPSETLOCK [%s] for %d%s in range %llx:%llx\n", 
+    (setLockFlag?"ON":"OFF"), pFile->h, (pb.fd==-1?"[testval-1]":""), offset, length);
   err = fsctl(path, afpfsByteRangeLock2FSCTL, &pb, 0);
   if ( err==-1 ) {
     int rc;
     int tErrno = errno;
-    OSTRACE4("AFPLOCK failed to fsctl() '%s' %d %s\n", path, tErrno, strerror(tErrno));
-    rc = sqliteErrorFromPosixError(tErrno, setLockFlag ? SQLITE_IOERR_LOCK : SQLITE_IOERR_UNLOCK); /* error */
+    OSTRACE4("AFPSETLOCK failed to fsctl() '%s' %d %s\n", path, tErrno, strerror(tErrno));
+#ifdef SQLITE_IGNORE_AFP_LOCK_ERRORS
+    rc = SQLITE_BUSY;
+#else
+    rc = sqliteErrorFromPosixError(tErrno, setLockFlag ? SQLITE_IOERR_LOCK : SQLITE_IOERR_UNLOCK);
+#endif /* SQLITE_IGNORE_AFP_LOCK_ERRORS */
     if( IS_LOCK_ERROR(rc) ){
       pFile->lastErrno = tErrno;
     }
@@ -1981,11 +2091,11 @@ static int afpCheckReservedLock(sqlite3_file *id, int *pResOut){
    */
   if( !reserved ){
     /* lock the RESERVED byte */
-    int lrc = _AFPFSSetLock(context->filePath, pFile, RESERVED_BYTE, 1,1);  
+    int lrc = _AFPFSSetLock(context->dbPath, pFile, RESERVED_BYTE, 1,1);  
     if( SQLITE_OK==lrc ){
       /* if we succeeded in taking the reserved lock, unlock it to restore
       ** the original state */
-      lrc = _AFPFSSetLock(context->filePath, pFile, RESERVED_BYTE, 1, 0);
+      lrc = _AFPFSSetLock(context->dbPath, pFile, RESERVED_BYTE, 1, 0);
     } else {
       /* if we failed to get the lock then someone else must have it */
       reserved = 1;
@@ -2048,7 +2158,7 @@ static int afpLock(sqlite3_file *id, int locktype){
       || (locktype==EXCLUSIVE_LOCK && pFile->locktype<PENDING_LOCK)
   ){
     int failed;
-    failed = _AFPFSSetLock(context->filePath, pFile, PENDING_BYTE, 1, 1);
+    failed = _AFPFSSetLock(context->dbPath, pFile, PENDING_BYTE, 1, 1);
     if (failed) {
       rc = failed;
       goto afp_end_lock;
@@ -2064,14 +2174,14 @@ static int afpLock(sqlite3_file *id, int locktype){
     /* Now get the read-lock SHARED_LOCK */
     /* note that the quality of the randomness doesn't matter that much */
     lk = random(); 
-    context->sharedLockByte = (lk & 0x7fffffff)%(SHARED_SIZE - 1);
-    lrc1 = _AFPFSSetLock(context->filePath, pFile, 
-          SHARED_FIRST+context->sharedLockByte, 1, 1);
+    context->sharedByte = (lk & 0x7fffffff)%(SHARED_SIZE - 1);
+    lrc1 = _AFPFSSetLock(context->dbPath, pFile, 
+          SHARED_FIRST+context->sharedByte, 1, 1);
     if( IS_LOCK_ERROR(lrc1) ){
       lrc1Errno = pFile->lastErrno;
     }
     /* Drop the temporary PENDING lock */
-    lrc2 = _AFPFSSetLock(context->filePath, pFile, PENDING_BYTE, 1, 0);
+    lrc2 = _AFPFSSetLock(context->dbPath, pFile, PENDING_BYTE, 1, 0);
     
     if( IS_LOCK_ERROR(lrc1) ) {
       pFile->lastErrno = lrc1Errno;
@@ -2084,6 +2194,7 @@ static int afpLock(sqlite3_file *id, int locktype){
       rc = lrc1;
     } else {
       pFile->locktype = SHARED_LOCK;
+      pFile->pOpen->nLock++;
     }
   }else{
     /* The request was for a RESERVED or EXCLUSIVE lock.  It is
@@ -2094,7 +2205,7 @@ static int afpLock(sqlite3_file *id, int locktype){
     assert( 0!=pFile->locktype );
     if (locktype >= RESERVED_LOCK && pFile->locktype < RESERVED_LOCK) {
         /* Acquire a RESERVED lock */
-        failed = _AFPFSSetLock(context->filePath, pFile, RESERVED_BYTE, 1,1);
+        failed = _AFPFSSetLock(context->dbPath, pFile, RESERVED_BYTE, 1,1);
     }
     if (!failed && locktype == EXCLUSIVE_LOCK) {
       /* Acquire an EXCLUSIVE lock */
@@ -2102,16 +2213,22 @@ static int afpLock(sqlite3_file *id, int locktype){
       /* Remove the shared lock before trying the range.  we'll need to 
       ** reestablish the shared lock if we can't get the  afpUnlock
       */
-      if (!(failed = _AFPFSSetLock(context->filePath, pFile, SHARED_FIRST +
-                         context->sharedLockByte, 1, 0))) {
+      if( !(failed = _AFPFSSetLock(context->dbPath, pFile, SHARED_FIRST +
+                         context->sharedByte, 1, 0)) ){
+        int failed2 = SQLITE_OK;
         /* now attemmpt to get the exclusive lock range */
-        failed = _AFPFSSetLock(context->filePath, pFile, SHARED_FIRST, 
+        failed = _AFPFSSetLock(context->dbPath, pFile, SHARED_FIRST, 
                                SHARED_SIZE, 1);
-        if (failed && (failed = _AFPFSSetLock(context->filePath, pFile, 
-                       SHARED_FIRST + context->sharedLockByte, 1, 1))) {
-          rc = failed;
-        }
-      } else {
+        if( failed && (failed2 = _AFPFSSetLock(context->dbPath, pFile, 
+                       SHARED_FIRST + context->sharedByte, 1, 1)) ){
+          /* Can't reestablish the shared lock.  Sqlite can't deal, this is
+          ** a critical I/O error
+          */
+          rc = ((failed & SQLITE_IOERR) == SQLITE_IOERR) ? failed2 : 
+               SQLITE_IOERR_LOCK;
+          goto afp_end_lock;
+        } 
+      }else{
         rc = failed; 
       }
     }
@@ -2143,7 +2260,7 @@ afp_end_lock:
 static int afpUnlock(sqlite3_file *id, int locktype) {
   int rc = SQLITE_OK;
   unixFile *pFile = (unixFile*)id;
-  afpLockingContext *context = (afpLockingContext *) pFile->lockingContext;
+  afpLockingContext *pCtx = (afpLockingContext *) pFile->lockingContext;
 
   assert( pFile );
   OSTRACE5("UNLOCK  %d %d was %d pid=%d\n", pFile->h, locktype,
@@ -2157,51 +2274,55 @@ static int afpUnlock(sqlite3_file *id, int locktype) {
     return SQLITE_MISUSE;
   }
   enterMutex();
-  int failed = SQLITE_OK;
   if( pFile->locktype>SHARED_LOCK ){
-    if( locktype==SHARED_LOCK ){
-
-      /* unlock the exclusive range - then re-establish the shared lock */
-      if (pFile->locktype==EXCLUSIVE_LOCK) {
-        failed = _AFPFSSetLock(context->filePath, pFile, SHARED_FIRST, 
-                                 SHARED_SIZE, 0);
-        if (!failed) {
-          /* successfully removed the exclusive lock */
-          if ((failed = _AFPFSSetLock(context->filePath, pFile, SHARED_FIRST+
-                            context->sharedLockByte, 1, 1))) {
-            /* failed to re-establish our shared lock */
-            rc = failed;
-          }
-        } else {
-          rc = failed;
-        } 
+    
+    if( pFile->locktype==EXCLUSIVE_LOCK ){
+      rc = _AFPFSSetLock(pCtx->dbPath, pFile, SHARED_FIRST, SHARED_SIZE, 0);
+      if( rc==SQLITE_OK && locktype==SHARED_LOCK ){
+        /* only re-establish the shared lock if necessary */
+        int sharedLockByte = SHARED_FIRST+pCtx->sharedByte;
+        rc = _AFPFSSetLock(pCtx->dbPath, pFile, sharedLockByte, 1, 1);
       }
     }
-    if (rc == SQLITE_OK && pFile->locktype>=PENDING_LOCK) {
-      if ((failed = _AFPFSSetLock(context->filePath, pFile, 
-                                  PENDING_BYTE, 1, 0))){
-        /* failed to release the pending lock */
-        rc = failed; 
-      }
-    } 
-    if (rc == SQLITE_OK && pFile->locktype>=RESERVED_LOCK) {
-      if ((failed = _AFPFSSetLock(context->filePath, pFile, 
-                                  RESERVED_BYTE, 1, 0))) {
-        /* failed to release the reserved lock */
-        rc = failed;  
-      }
+    if( rc==SQLITE_OK && pFile->locktype>=PENDING_LOCK ){
+      rc = _AFPFSSetLock(pCtx->dbPath, pFile, PENDING_BYTE, 1, 0);
     } 
+    if( rc==SQLITE_OK && pFile->locktype>=RESERVED_LOCK ){
+      rc = _AFPFSSetLock(pCtx->dbPath, pFile, RESERVED_BYTE, 1, 0);
+    }
+  }else if( locktype==NO_LOCK ){
+    /* clear the shared lock */
+    int sharedLockByte = SHARED_FIRST+pCtx->sharedByte;
+    rc = _AFPFSSetLock(pCtx->dbPath, pFile, sharedLockByte, 1, 0);
   }
-  if( locktype==NO_LOCK ){
-    int failed = _AFPFSSetLock(context->filePath, pFile, 
-                               SHARED_FIRST + context->sharedLockByte, 1, 0);
-    if (failed) {
-      rc = failed;  
+
+  if( rc==SQLITE_OK ){
+    if( locktype==NO_LOCK ){
+      struct openCnt *pOpen = pFile->pOpen;
+      pOpen->nLock--;
+      assert( pOpen->nLock>=0 );
+      if( pOpen->nLock==0 && pOpen->nPending>0 ){
+        int i;
+        for(i=0; i<pOpen->nPending; i++){
+          if( pOpen->aPending[i] < 0 ) continue;
+          if( close(pOpen->aPending[i]) ){
+            pFile->lastErrno = errno;
+            rc = SQLITE_IOERR_CLOSE;
+          }else{
+            pOpen->aPending[i] = -1;
+          }
+        }
+        if( rc==SQLITE_OK ){
+          sqlite3_free(pOpen->aPending);
+          pOpen->nPending = 0;
+          pOpen->aPending = 0;
+        }
+      }
     }
   }
-  if (rc == SQLITE_OK)
-    pFile->locktype = locktype;
+end_afpunlock:
   leaveMutex();
+  if( rc==SQLITE_OK ) pFile->locktype = locktype;
   return rc;
 }
 
@@ -2212,9 +2333,31 @@ static int afpClose(sqlite3_file *id) {
   if( id ){
     unixFile *pFile = (unixFile*)id;
     afpUnlock(id, NO_LOCK);
+    enterMutex();
+    if( pFile->pOpen && pFile->pOpen->nLock ){
+      /* If there are outstanding locks, do not actually close the file just
+       ** yet because that would clear those locks.  Instead, add the file
+       ** descriptor to pOpen->aPending.  It will be automatically closed when
+       ** the last lock is cleared.
+       */
+      int *aNew;
+      struct openCnt *pOpen = pFile->pOpen;
+      aNew = sqlite3_realloc(pOpen->aPending, (pOpen->nPending+1)*sizeof(int) );
+      if( aNew==0 ){
+        /* If a malloc fails, just leak the file descriptor */
+      }else{
+        pOpen->aPending = aNew;
+        pOpen->aPending[pOpen->nPending] = pFile->h;
+        pOpen->nPending++;
+        pFile->h = -1;
+      }
+    }
+    releaseOpenCnt(pFile->pOpen);
     sqlite3_free(pFile->lockingContext);
+    closeUnixFile(id);
+    leaveMutex();
   }
-  return closeUnixFile(id);
+  return SQLITE_OK;
 }
 
 
@@ -2270,12 +2413,19 @@ static int flockCheckReservedLock(sqlite3_file *id, int *pResOut){
   }
   OSTRACE4("TEST WR-LOCK %d %d %d\n", pFile->h, rc, reserved);
 
+#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS
+  if( (rc & SQLITE_IOERR) == SQLITE_IOERR ){
+    rc = SQLITE_OK;
+    reserved=1;
+  }
+#endif /* SQLITE_IGNORE_FLOCK_LOCK_ERRORS */
   *pResOut = reserved;
   return rc;
 }
 
 static int flockLock(sqlite3_file *id, int locktype) {
   int rc = SQLITE_OK;
+  int lrc;
   unixFile *pFile = (unixFile*)id;
 
   assert( pFile );
@@ -2302,6 +2452,11 @@ static int flockLock(sqlite3_file *id, int locktype) {
   }
   OSTRACE4("LOCK    %d %s %s\n", pFile->h, locktypeName(locktype), 
            rc==SQLITE_OK ? "ok" : "failed");
+#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS
+  if( (rc & SQLITE_IOERR) == SQLITE_IOERR ){
+    rc = SQLITE_BUSY;
+  }
+#endif /* SQLITE_IGNORE_FLOCK_LOCK_ERRORS */
   return rc;
 }
 
@@ -2332,6 +2487,12 @@ static int flockUnlock(sqlite3_file *id, int locktype) {
     if( IS_LOCK_ERROR(r) ){
       pFile->lastErrno = tErrno;
     }
+#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS
+    if( (r & SQLITE_IOERR) == SQLITE_IOERR ){
+      r = SQLITE_BUSY;
+    }
+#endif /* SQLITE_IGNORE_FLOCK_LOCK_ERRORS */
+    
     return r;
   } else {
     pFile->locktype = NO_LOCK;
@@ -2352,6 +2513,7 @@ static int flockClose(sqlite3_file *id) {
 #endif /* !IS_VXWORKS */
 
 #pragma mark Old-School .lock file based locking
+#define DOTLOCK_SUFFIX ".lock"
 
 /* Dotlock-style reserved lock checking following the behavior of 
 ** unixCheckReservedLock, see the unixCheckReservedLock function comments */
@@ -2427,12 +2589,15 @@ static int dotlockLock(sqlite3_file *id, int locktype) {
     } else {
       rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK);
       if( IS_LOCK_ERROR(rc) ){
-       pFile->lastErrno = tErrno;
+        pFile->lastErrno = tErrno;
       }
     }
     goto dotlock_end_lock;
   } 
-  close(fd);
+  if( close(fd) ){
+    pFile->lastErrno = errno;
+    rc = SQLITE_IOERR_CLOSE;
+  }
   
   /* got it, set the type and return ok */
   pFile->locktype = locktype;
@@ -2523,8 +2688,8 @@ static int namedsemCheckReservedLock(sqlite3_file *id, int *pResOut) {
         rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_CHECKRESERVEDLOCK);
         pFile->lastErrno = tErrno;
       } else {
-       /* someone else has the lock when we are in NO_LOCK */
-       reserved = (pFile->locktype < SHARED_LOCK);
+        /* someone else has the lock when we are in NO_LOCK */
+        reserved = (pFile->locktype < SHARED_LOCK);
       }
     }else{
       /* we could have it if we want it */
@@ -2617,6 +2782,577 @@ static int namedsemClose(sqlite3_file *id) {
 
 #endif /* IS_VXWORKS */
 
+#pragma mark Proxy locking support
+
+static int proxyCheckReservedLock(sqlite3_file *id, int *pResOut) {
+  unixFile *pFile = (unixFile*)id;
+  int rc = takeConch(pFile);
+  if( rc==SQLITE_OK ){
+    proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext;
+    unixFile *proxy = pCtx->lockProxy;
+    return proxy->pMethod->xCheckReservedLock((sqlite3_file*)proxy, pResOut);
+  }
+  return rc;
+}
+
+static int proxyLock(sqlite3_file *id, int locktype) {
+  unixFile *pFile = (unixFile*)id;
+  int rc = takeConch(pFile);
+  if( rc==SQLITE_OK ){
+    proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext;
+    unixFile *proxy = pCtx->lockProxy;
+    rc = proxy->pMethod->xLock((sqlite3_file*)proxy, locktype);
+    pFile->locktype = proxy->locktype;
+  }
+  return rc;
+}
+
+static int proxyUnlock(sqlite3_file *id, int locktype) {
+  unixFile *pFile = (unixFile*)id;
+  int rc = takeConch(pFile);
+  if( rc==SQLITE_OK ){
+    proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext;
+    unixFile *proxy = pCtx->lockProxy;
+    rc = proxy->pMethod->xUnlock((sqlite3_file*)proxy, locktype);
+    pFile->locktype = proxy->locktype;
+  }
+  return rc;
+}
+
+/*
+ ** Close a file.
+ */
+static int proxyClose(sqlite3_file *id) {
+  if( id ){
+    unixFile *pFile = (unixFile*)id;
+    proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext;
+    unixFile *lockProxy = pCtx->lockProxy;
+    unixFile *conchFile = pCtx->conchFile;
+    int rc = SQLITE_OK;
+    
+    if( lockProxy ){
+      rc = lockProxy->pMethod->xUnlock((sqlite3_file*)lockProxy, NO_LOCK);
+      if( rc ) return rc;
+      rc = lockProxy->pMethod->xClose((sqlite3_file*)lockProxy);
+      if( rc ) return rc;
+      sqlite3_free(lockProxy);
+    }
+    if( conchFile ){
+      if( pCtx->conchHeld ){
+        rc = releaseConch(pFile);
+        if( rc ) return rc;
+      }
+      rc = conchFile->pMethod->xClose((sqlite3_file*)conchFile);
+      if( rc ) return rc;
+      sqlite3_free(conchFile);
+    }
+    sqlite3_free(pCtx->lockProxyPath);
+    sqlite3_free(pCtx->conchFilePath);
+    sqlite3_free(pCtx->dbPath);
+    /* restore the original locking context and pMethod then close it */
+    pFile->lockingContext = pCtx->oldLockingContext;
+    pFile->pMethod = pCtx->pOldMethod;
+    sqlite3_free(pCtx);
+    return pFile->pMethod->xClose(id);
+  }
+  return SQLITE_OK;
+}
+
+/* HOSTIDLEN and CONCHLEN both include space for the string 
+** terminating nul 
+*/
+#define HOSTIDLEN         128
+#define CONCHLEN          (MAXPATHLEN+HOSTIDLEN+1)
+#ifndef HOSTIDPATH
+# define HOSTIDPATH       "/Library/Caches/.com.apple.sqliteConchHostId"
+#endif
+
+/* basically a copy of unixRandomness with different
+** test behavior built in */
+static int genHostID(char *pHostID){
+  int pid, fd, i, len;
+  unsigned char *key = (unsigned char *)pHostID;
+  
+  memset(key, 0, HOSTIDLEN);
+  len = 0;
+  fd = open("/dev/urandom", O_RDONLY);
+  if( fd>=0 ){
+    len = read(fd, key, HOSTIDLEN);
+    close(fd); /* silently leak the fd if it fails */
+  }
+  if( len < HOSTIDLEN ){
+    time_t t;
+    time(&t);
+    memcpy(key, &t, sizeof(t));
+    pid = getpid();
+    memcpy(&key[sizeof(t)], &pid, sizeof(pid));
+  }
+  
+#ifdef MAKE_PRETTY_HOSTID
+  /* filter the bytes into printable ascii characters and NUL terminate */
+  key[(HOSTIDLEN-1)] = 0x00;
+  for( i=0; i<(HOSTIDLEN-1); i++ ){
+    unsigned char pa = key[i]&0x7F;
+    if( pa<0x20 ){
+      key[i] = (key[i]&0x80 == 0x80) ? pa+0x40 : pa+0x20;
+    }else if( pa==0x7F ){
+      key[i] = (key[i]&0x80 == 0x80) ? pa=0x20 : pa+0x7E;
+    }
+  }
+#endif
+  return SQLITE_OK;
+}
+
+#ifdef SQLITE_TEST
+/* simulate multiple hosts by creating unique hostid file paths */
+int sqlite3_hostid_num = 0;
+#endif
+
+/* writes the host id path to path, path should be an pre-allocated buffer
+** with enough space for a path */
+static int getHostIDPath(char *path, size_t len){
+  strlcpy(path, HOSTIDPATH, len);
+#ifdef SQLITE_TEST
+  if( sqlite3_hostid_num>0 ){
+    char suffix[2] = "1";
+    suffix[0] = suffix[0] + sqlite3_hostid_num;
+    strlcat(path, suffix, len);
+  }
+#endif
+  OSTRACE3("GETHOSTIDPATH  %s pid=%d\n", path, getpid());
+}
+
+/* get the host ID from a sqlite hostid file stored in the 
+** user-specific tmp directory, create the ID if it's not there already 
+*/
+static int getHostID(char *pHostID, int *pError){
+  int fd;
+  char path[MAXPATHLEN]; 
+  size_t len;
+       int rc=SQLITE_OK;
+
+  getHostIDPath(path, MAXPATHLEN);
+  /* try to create the host ID file, if it already exists read the contents */
+  fd = open(path, O_CREAT|O_WRONLY|O_EXCL, 0644);
+  if( fd<0 ){
+    int err=errno;
+               
+    if( err!=EEXIST ){
+#ifdef SQLITE_PROXY_DEBUG /* set the sqlite error message instead */
+      fprintf(stderr, "sqlite error creating host ID file %s: %s\n", path, strerror(err));
+#endif
+      return SQLITE_PERM;
+    }
+    /* couldn't create the file, read it instead */
+    fd = open(path, O_RDONLY|O_EXCL);
+    if( fd<0 ){
+      int err = errno;
+#ifdef SQLITE_PROXY_DEBUG /* set the sqlite error message instead */
+      fprintf(stderr, "sqlite error opening host ID file %s: %s\n", path, strerror(err));
+#endif
+      return SQLITE_PERM;
+    }
+    len = pread(fd, pHostID, HOSTIDLEN, 0);
+               if( len<0 ){
+                       *pError = errno;
+                       rc = SQLITE_IOERR_READ;
+               }else if( len<HOSTIDLEN ){
+                       *pError = 0;
+                       rc = SQLITE_IOERR_SHORT_READ;
+               }
+    close(fd); /* silently leak the fd if it fails */
+    OSTRACE3("GETHOSTID  read %s pid=%d\n", pHostID, getpid());
+    return rc;
+  }else{
+    int i;
+    /* we're creating the host ID file (use a random string of bytes) */
+    genHostID(pHostID);
+    len = pwrite(fd, pHostID, HOSTIDLEN, 0);
+               if( len<0 ){
+                       *pError = errno;
+                       rc = SQLITE_IOERR_WRITE;
+               }else if( len<HOSTIDLEN ){
+                       *pError = 0;
+                       rc = SQLITE_IOERR_WRITE;
+               }
+    close(fd); /* silently leak the fd if it fails */
+    OSTRACE3("GETHOSTID  wrote %s pid=%d\n", pHostID, getpid());
+    return rc;
+  }
+}
+
+/* takes the conch by taking a shared lock and read the contents conch, if 
+** lockPath is non-NULL, the host ID and lock file path must match.  A NULL 
+** lockPath means that the lockPath in the conch file will be used if the 
+** host IDs match, or a new lock path will be generated automatically 
+** and written to the conch file.
+*/
+static int takeConch(unixFile *pFile){
+  proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; 
+  
+  if( pCtx->conchHeld>0 ){
+    return SQLITE_OK;
+  }else{
+    unixFile *conchFile = pCtx->conchFile;
+    char testValue[CONCHLEN];
+    char conchValue[CONCHLEN];
+    char lockPath[MAXPATHLEN];
+    char *tLockPath = NULL;
+    int rc = SQLITE_OK;
+    int readRc = SQLITE_OK;
+    int syncPerms = 0;
+
+    OSTRACE4("TAKECONCH  %d for %s pid=%d\n", conchFile->h,
+             (pCtx->lockProxyPath ? pCtx->lockProxyPath : ":auto:"), getpid());
+
+    rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, SHARED_LOCK);
+    if( rc==SQLITE_OK ){
+                       int pError = 0;
+      memset(testValue, 0, CONCHLEN); // conch is fixed size
+      rc = getHostID(testValue, &pError);
+                       if( rc&SQLITE_IOERR==SQLITE_IOERR ){
+                               pFile->lastErrno = pError;
+                       }
+      if( pCtx->lockProxyPath ){
+        strlcpy(&testValue[HOSTIDLEN], pCtx->lockProxyPath, MAXPATHLEN);
+      }
+    }
+    if( rc!=SQLITE_OK ){
+      goto end_takeconch;
+    }
+    
+    readRc = unixRead((sqlite3_file *)conchFile, conchValue, CONCHLEN, 0);
+    if( readRc!=SQLITE_IOERR_SHORT_READ ){
+      int match = 0;
+      if( readRc!=SQLITE_OK ){
+                               if( rc&SQLITE_IOERR==SQLITE_IOERR ){
+                                       pFile->lastErrno = conchFile->lastErrno;
+                               }
+        rc = readRc;
+        goto end_takeconch;
+      }
+      /* if the conch has data compare the contents */
+      if( !pCtx->lockProxyPath ){
+        /* for auto-named local lock file, just check the host ID and we'll
+         ** use the local lock file path that's already in there */
+        if( !memcmp(testValue, conchValue, HOSTIDLEN) ){
+          tLockPath = (char *)&conchValue[HOSTIDLEN];
+          goto end_takeconch;
+        }
+      }else{
+        /* we've got the conch if conchValue matches our path and host ID */
+        if( !memcmp(testValue, conchValue, CONCHLEN) ){
+          goto end_takeconch;
+        }
+      }
+    }else{
+      /* a short read means we're "creating" the conch (even though it could 
+      ** have been user-intervention), if we acquire the exclusive lock,
+      ** we'll try to match the current on-disk permissions of the database
+      */
+      syncPerms = 1;
+    }
+    
+    /* either conch was emtpy or didn't match */
+    if( !pCtx->lockProxyPath ){
+      getLockPath(pCtx->dbPath, lockPath, MAXPATHLEN);
+      tLockPath = lockPath;
+      strlcpy(&testValue[HOSTIDLEN], lockPath, MAXPATHLEN);
+    }
+    
+    /* update conch with host and path (this will fail if other process
+     ** has a shared lock already) */
+    rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, EXCLUSIVE_LOCK);
+    if( rc==SQLITE_OK ){
+      rc = unixWrite((sqlite3_file *)conchFile, testValue, CONCHLEN, 0);
+      if( rc==SQLITE_OK && syncPerms ){
+        struct stat buf;
+        int err = fstat(pFile->h, &buf);
+        if( err==0 ){
+          mode_t mode = buf.st_mode & 0100666;
+          /* try to match the database file permissions, ignore failure */
+#ifndef SQLITE_PROXY_DEBUG
+          fchmod(conchFile->h, buf.st_mode);
+#else
+          if( fchmod(conchFile->h, buf.st_mode)!=0 ){
+            int code = errno;
+            fprintf(stderr, "fchmod %o FAILED with %d %s\n",buf.st_mode, code, strerror(code));
+          } else {
+            fprintf(stderr, "fchmod %o SUCCEDED\n",buf.st_mode);
+          }
+        }else{
+          int code = errno;
+          fprintf(stderr, "STAT FAILED[%d] with %d %s\n", err, code, strerror(code));
+#endif
+        }
+      }
+    }
+    conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, SHARED_LOCK);
+  
+end_takeconch:
+    OSTRACE2("TRANSPROXY: CLOSE  %d\n", pFile->h);
+    if( rc==SQLITE_OK && pFile->oflags ){
+      if( pFile->h>=0 ){
+#ifdef STRICT_CLOSE_ERROR
+        if( close(pFile->h) ){
+          pFile->lastErrno = errno;
+          return SQLITE_IOERR_CLOSE;
+        }
+#else
+        close(pFile->h); /* silently leak fd if fail */
+#endif
+      }
+      pFile->h = -1;
+      int fd = open(pCtx->dbPath, pFile->oflags, SQLITE_DEFAULT_FILE_PERMISSIONS);
+      OSTRACE2("TRANSPROXY: OPEN  %d\n", fd);
+      if( fd>=0 ){
+        pFile->h = fd;
+      }else{
+        rc=SQLITE_CANTOPEN; // SQLITE_BUSY? takeConch called during locking
+      }
+    }
+    if( rc==SQLITE_OK && !pCtx->lockProxy ){
+      char *path = tLockPath ? tLockPath : pCtx->lockProxyPath;
+      // ACS: Need to make a copy of path sometimes
+      rc = createProxyUnixFile(path, &pCtx->lockProxy);
+    }
+    if( rc==SQLITE_OK ){
+      pCtx->conchHeld = 1;
+
+      if( tLockPath ){
+        pCtx->lockProxyPath = sqlite3DbStrDup(0, tLockPath);
+        if( pCtx->lockProxy->pMethod == ioMethodForLockingStyle(LOCKING_STYLE_AFP) ){
+          ((afpLockingContext *)pCtx->lockProxy->lockingContext)->dbPath = pCtx->lockProxyPath;
+        }
+      }
+    } else {
+      conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, NO_LOCK);
+    }
+    OSTRACE3("TAKECONCH  %d %s\n", conchFile->h, rc==SQLITE_OK ? "ok" : "failed");
+    return rc;
+  }
+}
+  
+static int releaseConch(unixFile *pFile){
+  proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext;
+  int rc;
+  unixFile *conchFile = pCtx->conchFile;
+
+  OSTRACE4("RELEASECONCH  %d for %s pid=%d\n", conchFile->h,
+           (pCtx->lockProxyPath ? pCtx->lockProxyPath : ":auto:"), 
+           getpid());
+  pCtx->conchHeld = 0;
+  rc = conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, NO_LOCK);
+  OSTRACE3("RELEASECONCH  %d %s\n", conchFile->h,
+           (rc==SQLITE_OK ? "ok" : "failed"));
+  return rc;
+}
+
+static int getConchPathFromDBPath(char *dbPath, char **pConchPath){
+  int i;
+  int len = strlen(dbPath);
+  char *conchPath;
+  
+  conchPath = (char *)sqlite3_malloc(len + 8);
+  if( conchPath==0 ){
+    return SQLITE_NOMEM;
+  }
+  strlcpy(conchPath, dbPath, len+1);
+  
+  /* now insert a "." before the last / character */
+  for( i=(len-1); i>=0; i-- ){
+    if( conchPath[i]=='/' ){
+      i++;
+      break;
+    }
+  }
+  conchPath[i]='.';
+  while ( i<len ){
+    conchPath[i+1]=dbPath[i];
+    i++;
+  }
+  conchPath[i+1]='\0';
+  strlcat(conchPath, "-conch", len + 8);
+  *pConchPath = conchPath;
+  return SQLITE_OK;
+}
+
+static int getLockPath(const char *dbPath, char *lPath, size_t maxLen){
+  int len;
+  int dbLen;
+  int i;
+
+#ifdef LOCKPROXYDIR
+  len = strlcpy(lPath, LOCKPROXYDIR, maxLen);
+#else
+# ifdef _CS_DARWIN_USER_TEMP_DIR
+  {
+    char utdir[MAXPATHLEN];
+    
+    confstr(_CS_DARWIN_USER_TEMP_DIR, lPath, maxLen);
+    len = strlcat(lPath, "sqliteplocks", maxLen);
+    if( mkdir(lPath, SQLITE_DEFAULT_PROXYDIR_PERMISSIONS) ){
+      /* if mkdir fails, handle as lock file creation failure */
+      int err = errno;
+#  ifdef SQLITE_DEBUG
+      if( err!=EEXIST ){
+        fprintf(stderr, "getLockPath: mkdir(%s,0%o) error %d %s\n", lPath,
+                SQLITE_DEFAULT_PROXYDIR_PERMISSIONS, err, strerror(err));
+      }
+#  endif
+    }else{
+      OSTRACE3("GETLOCKPATH  mkdir %s pid=%d\n", lPath, getpid());
+    }
+    
+  }
+# else
+  len = strlcpy(lPath, "/tmp/", maxLen);
+# endif
+#endif
+
+  if( lPath[len-1]!='/' ){
+    len = strlcat(lPath, "/", maxLen);
+  }
+  
+  /* transform the db path to a unique cache name */
+  dbLen = strlen(dbPath);
+  for( i=0; i<dbLen && (i+len+7)<maxLen; i++){
+    char c = dbPath[i];
+    lPath[i+len] = (c=='/')?'_':c;
+  }
+  lPath[i+len]='\0';
+  strlcat(lPath, ":auto:", maxLen);
+  return SQLITE_OK;
+}
+
+/* Takes a fully configured proxy locking-style unix file and switches
+** the local lock file path 
+*/
+static int switchLockProxyPath(unixFile *pFile, const char *path) {
+  proxyLockingContext *pCtx = (proxyLockingContext*)pFile->lockingContext;
+  char *oldPath = pCtx->lockProxyPath;
+  int taken = 0;
+  int rc = SQLITE_OK;
+
+  if( pFile->locktype!=NO_LOCK ){
+    return SQLITE_BUSY;
+  }  
+
+  /* nothing to do if the path is NULL, :auto: or matches the existing path */
+  if( !path || path[0]=='\0' || !strcmp(path, ":auto:") ||
+    (oldPath && !strncmp(oldPath, path, MAXPATHLEN)) ){
+    return SQLITE_OK;
+  }else{
+    unixFile *lockProxy = pCtx->lockProxy;
+    pCtx->lockProxy=NULL;
+    pCtx->conchHeld = 0;
+    if( lockProxy!=NULL ){
+      rc=lockProxy->pMethod->xClose((sqlite3_file *)lockProxy);
+      if( rc ) return rc;
+      sqlite3_free(lockProxy);
+    }
+    sqlite3_free(oldPath);
+    pCtx->lockProxyPath = sqlite3DbStrDup(0, path);
+  }
+  
+  return rc;
+}
+
+/*
+** Takes an already filled in unix file and alters it so all file locking 
+** will be performed on the local proxy lock file.  The following fields
+** are preserved in the locking context so that they can be restored and 
+** the unix structure properly cleaned up at close time:
+**  ->lockingContext
+**  ->pMethod
+*/
+static int transformUnixFileForLockProxy(unixFile *pFile, const char *path) {
+  proxyLockingContext *pCtx;
+  char dbPath[MAXPATHLEN];
+  char *lockPath=NULL;
+  int rc = SQLITE_OK;
+  
+  if( pFile->locktype!=NO_LOCK ){
+    return SQLITE_BUSY;
+  }
+  getDbPathForUnixFile(pFile, dbPath);
+  if( !path || path[0]=='\0' || !strcmp(path, ":auto:") ){
+    lockPath=NULL;
+  }else{
+    lockPath=(char *)path;
+  }
+  
+  OSTRACE4("TRANSPROXY  %d for %s pid=%d\n", pFile->h,
+           (lockPath ? lockPath : ":auto:"), getpid());
+
+  pCtx = sqlite3_malloc( sizeof(*pCtx) );
+  if( pCtx==0 ){
+    return SQLITE_NOMEM;
+  }
+  memset(pCtx, 0, sizeof(*pCtx));
+
+  rc = getConchPathFromDBPath(dbPath, &pCtx->conchFilePath);
+  if( rc==SQLITE_OK ){
+    rc = createProxyUnixFile(pCtx->conchFilePath, &pCtx->conchFile);
+  }  
+  if( rc==SQLITE_OK && lockPath ){
+    pCtx->lockProxyPath = sqlite3DbStrDup(0, lockPath);
+  }
+
+end_transform_file:
+  if( rc==SQLITE_OK ){
+    /* all memory is allocated, proxys are created and assigned, 
+    ** switch the locking context and pMethod then return.
+    */
+    pCtx->dbPath = sqlite3DbStrDup(0, dbPath);
+    pCtx->oldLockingContext = pFile->lockingContext;
+    pFile->lockingContext = pCtx;
+    pCtx->pOldMethod = pFile->pMethod;
+    pFile->pMethod = ioMethodForLockingStyle(LOCKING_STYLE_PROXY);
+  }else{
+    if( pCtx->conchFile ){ 
+      rc = pCtx->conchFile->pMethod->xClose((sqlite3_file *)pCtx->conchFile);
+      if( rc ) return rc;
+      sqlite3_free(pCtx->conchFile);
+    }
+    sqlite3_free(pCtx->conchFilePath); 
+    sqlite3_free(pCtx);
+  }
+  OSTRACE3("TRANSPROXY  %d %s\n", pFile->h,
+           (rc==SQLITE_OK ? "ok" : "failed"));
+  return rc;
+} 
+
+static int createProxyUnixFile(const char *path, unixFile **ppFile) {
+  int fd;
+  int dirfd = -1;
+  unixFile *pNew;
+  int rc = SQLITE_OK;
+
+  fd = open(path, O_RDWR | O_CREAT, SQLITE_DEFAULT_FILE_PERMISSIONS);
+  if( fd<0 ){
+    return SQLITE_CANTOPEN;
+  }
+  
+  pNew = (unixFile *)sqlite3_malloc(sizeof(unixFile));
+  if( pNew==NULL ){
+    rc = SQLITE_NOMEM;
+    goto end_create_proxy;
+  }
+  memset(pNew, 0, sizeof(unixFile));
+  
+  rc = fillInUnixFile(NULL, fd, dirfd, (sqlite3_file*)pNew, path, 0, 0);
+  if( rc==SQLITE_OK ){
+    *ppFile = pNew;
+    return SQLITE_OK;
+  }
+end_create_proxy:    
+  close(fd); /* silently leak fd if error, we're already in error */
+  sqlite3_free(pNew);
+  return rc;
+}
+
+
 #endif /* SQLITE_ENABLE_LOCKING_STYLE */
 
 /*
@@ -2661,6 +3397,63 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){
       *(int*)pArg = ((unixFile*)id)->locktype;
       return SQLITE_OK;
     }
+    case SQLITE_GET_LOCKPROXYFILE: {
+#if SQLITE_ENABLE_LOCKING_STYLE
+      unixFile *pFile = (unixFile*)id;
+      if( pFile->pMethod == ioMethodForLockingStyle(LOCKING_STYLE_PROXY) ){
+        proxyLockingContext *pCtx = (proxyLockingContext*)pFile->lockingContext;
+        takeConch(pFile);
+        if( pCtx->lockProxyPath ){
+          *(const char **)pArg = pCtx->lockProxyPath;
+        }else{
+          *(const char **)pArg = ":auto: (not held)";
+        }
+      } else {
+        *(const char **)pArg = NULL;
+      }
+#else
+      *(void*)pArg = NULL;
+#endif
+      return SQLITE_OK;
+    }
+    case SQLITE_SET_LOCKPROXYFILE: {
+#if SQLITE_ENABLE_LOCKING_STYLE
+      unixFile *pFile = (unixFile*)id;
+      int rc = SQLITE_OK;
+      int isProxyStyle = (pFile->pMethod == ioMethodForLockingStyle(LOCKING_STYLE_PROXY));
+      if( pArg==NULL || (const char *)pArg==0 ){
+        if( isProxyStyle ){
+          // turn off proxy locking - not supported
+          rc = SQLITE_ERROR /*SQLITE_PROTOCOL? SQLITE_MISUSE?*/;
+        }else{
+          // turn off proxy locking - already off - NOOP
+          rc = SQLITE_OK;
+        }
+      }else{
+        const char *proxyPath = (const char *)pArg;
+        if( isProxyStyle ){
+          proxyLockingContext *pCtx = 
+            (proxyLockingContext*)pFile->lockingContext;
+          if( !strcmp(pArg, ":auto:") || (pCtx->lockProxyPath && !strncmp(pCtx->lockProxyPath, proxyPath, MAXPATHLEN)) ){
+            rc = SQLITE_OK;
+          }else{
+            rc = switchLockProxyPath(pFile, proxyPath);
+          }
+        }else{
+          // turn on proxy file locking 
+          rc = transformUnixFileForLockProxy(pFile, proxyPath);
+        }
+      }
+      return rc;
+#else
+      return SQLITE_ERROR /*SQLITE_PROTOCOL? SQLITE_MISUSE?*/;
+#endif
+    }
+    case SQLITE_LAST_ERRNO: {
+      *(int*)pArg = ((unixFile*)id)->lastErrno;
+      return SQLITE_OK;
+    }
+      
   }
   return SQLITE_ERROR;
 }
@@ -2688,6 +3481,43 @@ static int unixDeviceCharacteristics(sqlite3_file *NotUsed){
   return 0;
 }
 
+#define IOMETHODS(xClose, xLock, xUnlock, xCheckReservedLock) {    \
+1,                          /* iVersion */                           \
+xClose,                     /* xClose */                             \
+unixRead,                   /* xRead */                              \
+unixWrite,                  /* xWrite */                             \
+unixTruncate,               /* xTruncate */                          \
+unixSync,                   /* xSync */                              \
+unixFileSize,               /* xFileSize */                          \
+xLock,                      /* xLock */                              \
+xUnlock,                    /* xUnlock */                            \
+xCheckReservedLock,         /* xCheckReservedLock */                 \
+unixFileControl,            /* xFileControl */                       \
+unixSectorSize,             /* xSectorSize */                        \
+unixDeviceCharacteristics   /* xDeviceCapabilities */                \
+}
+static sqlite3_io_methods aIoMethod[] = {
+IOMETHODS(unixClose, unixLock, unixUnlock, unixCheckReservedLock) 
+,IOMETHODS(nolockClose, nolockLock, nolockUnlock, nolockCheckReservedLock)
+#if SQLITE_ENABLE_LOCKING_STYLE
+,IOMETHODS(dotlockClose, dotlockLock, dotlockUnlock,dotlockCheckReservedLock)
+#if IS_VXWORKS
+  ,IOMETHODS(nolockClose, nolockLock, nolockUnlock, nolockCheckReservedLock)
+  ,IOMETHODS(nolockClose, nolockLock, nolockUnlock, nolockCheckReservedLock)
+  ,IOMETHODS(namedsemClose, namedsemLock, namedsemUnlock, namedsemCheckReservedLock)
+  ,IOMETHODS(nolockClose, nolockLock, nolockUnlock, nolockCheckReservedLock)
+#else
+  ,IOMETHODS(flockClose, flockLock, flockUnlock, flockCheckReservedLock)
+  ,IOMETHODS(afpClose, afpLock, afpUnlock, afpCheckReservedLock)
+  ,IOMETHODS(nolockClose, nolockLock, nolockUnlock, nolockCheckReservedLock)
+  ,IOMETHODS(proxyClose, proxyLock, proxyUnlock, proxyCheckReservedLock)
+#endif
+#endif
+/* The order of the IOMETHODS macros above is important.  It must be the
+ ** same order as the LOCKING_STYLE numbers
+ */
+};
+
 /*
 ** Initialize the contents of the unixFile structure pointed to by pId.
 **
@@ -2709,52 +3539,6 @@ static int fillInUnixFile(
   unixFile *pNew = (unixFile *)pId;
   int rc = SQLITE_OK;
 
-  /* Macro to define the static contents of an sqlite3_io_methods 
-  ** structure for a unix backend file. Different locking methods
-  ** require different functions for the xClose, xLock, xUnlock and
-  ** xCheckReservedLock methods.
-  */
-  #define IOMETHODS(xClose, xLock, xUnlock, xCheckReservedLock) {    \
-    1,                          /* iVersion */                           \
-    xClose,                     /* xClose */                             \
-    unixRead,                   /* xRead */                              \
-    unixWrite,                  /* xWrite */                             \
-    unixTruncate,               /* xTruncate */                          \
-    unixSync,                   /* xSync */                              \
-    unixFileSize,               /* xFileSize */                          \
-    xLock,                      /* xLock */                              \
-    xUnlock,                    /* xUnlock */                            \
-    xCheckReservedLock,         /* xCheckReservedLock */                 \
-    unixFileControl,            /* xFileControl */                       \
-    unixSectorSize,             /* xSectorSize */                        \
-    unixDeviceCharacteristics   /* xDeviceCapabilities */                \
-  }
-  static sqlite3_io_methods aIoMethod[] = {
-    IOMETHODS(unixClose, unixLock, unixUnlock, unixCheckReservedLock) 
-   ,IOMETHODS(nolockClose, nolockLock, nolockUnlock, nolockCheckReservedLock)
-#if SQLITE_ENABLE_LOCKING_STYLE
-   ,IOMETHODS(dotlockClose, dotlockLock, dotlockUnlock,dotlockCheckReservedLock)
-#if IS_VXWORKS
-   ,IOMETHODS(nolockClose, nolockLock, nolockUnlock, nolockCheckReservedLock)
-   ,IOMETHODS(nolockClose, nolockLock, nolockUnlock, nolockCheckReservedLock)
-   ,IOMETHODS(namedsemClose, namedsemLock, namedsemUnlock, namedsemCheckReservedLock)
-#else
-   ,IOMETHODS(flockClose, flockLock, flockUnlock, flockCheckReservedLock)
-   ,IOMETHODS(afpClose, afpLock, afpUnlock, afpCheckReservedLock)
-   ,IOMETHODS(nolockClose, nolockLock, nolockUnlock, nolockCheckReservedLock)
-#endif
-#endif
-  };
-  /* The order of the IOMETHODS macros above is important.  It must be the
-  ** same order as the LOCKING_STYLE numbers
-  */
-  assert(LOCKING_STYLE_POSIX==1);
-  assert(LOCKING_STYLE_NONE==2);
-  assert(LOCKING_STYLE_DOTFILE==3);
-  assert(LOCKING_STYLE_FLOCK==4);
-  assert(LOCKING_STYLE_AFP==5);
-  assert(LOCKING_STYLE_NAMEDSEM==6);
-
   assert( pNew->pLock==NULL );
   assert( pNew->pOpen==NULL );
 
@@ -2811,16 +3595,35 @@ static int fillInUnixFile(
     eLockingStyle = LOCKING_STYLE_NONE;
   }else{
     eLockingStyle = detectLockingStyle(pVfs, zFilename, h);
+#if SQLITE_ENABLE_LOCKING_STYLE
+    /* Cache zFilename in the locking context (AFP and dotlock override) for
+    ** proxyLock activation is possible (remote proxy is based on db name)
+    ** zFilename remains valid until file is closed, to support */
+    pNew->lockingContext = (void*)zFilename;
+#endif
   }
 
+  /* Macro to define the static contents of an sqlite3_io_methods 
+  ** structure for a unix backend file. Different locking methods
+  ** require different functions for the xClose, xLock, xUnlock and
+  ** xCheckReservedLock methods.
+  */
+  assert(LOCKING_STYLE_POSIX==1);
+  assert(LOCKING_STYLE_NONE==2);
+  assert(LOCKING_STYLE_DOTFILE==3);
+  assert(LOCKING_STYLE_FLOCK==4);
+  assert(LOCKING_STYLE_AFP==5);
+  assert(LOCKING_STYLE_NAMEDSEM==6);
+  assert(LOCKING_STYLE_PROXY==7);
+    
   switch( eLockingStyle ){
 
     case LOCKING_STYLE_POSIX: {
       enterMutex();
 #if IS_VXWORKS
-      rc = findLockInfo(h, pNew->zRealpath, &pNew->pLock, &pNew->pOpen);
+      rc = findLockInfo(pNew, pNew->zRealpath, &pNew->pLock, &pNew->pOpen);
 #else
-      rc = findLockInfo(h, &pNew->pLock, &pNew->pOpen);
+      rc = findLockInfo(pNew, &pNew->pLock, &pNew->pOpen);
 #endif
       leaveMutex();
       break;
@@ -2841,8 +3644,11 @@ static int fillInUnixFile(
         /* NB: zFilename exists and remains valid until the file is closed
         ** according to requirement F11141.  So we do not need to make a
         ** copy of the filename. */
-        pCtx->filePath = zFilename;
+        pCtx->dbPath = zFilename;
         srandomdev();
+        enterMutex();
+        rc = findLockInfo(pNew, NULL, &pNew->pOpen);
+        leaveMutex();        
       }
       break;
     }
@@ -2859,7 +3665,7 @@ static int fillInUnixFile(
       if( zLockFile==0 ){
         rc = SQLITE_NOMEM;
       }else{
-        sqlite3_snprintf(nFilename, zLockFile, "%s.lock", zFilename);
+        sqlite3_snprintf(nFilename, zLockFile, "%s" DOTLOCK_SUFFIX, zFilename);
       }
       pNew->lockingContext = zLockFile;
       break;
@@ -2868,8 +3674,8 @@ static int fillInUnixFile(
 #if IS_VXWORKS
     case LOCKING_STYLE_NAMEDSEM: {
       /* Named semaphore locking uses the file path so it needs to be
-      ** included in the namedsemLockingContext
-      */
+       ** included in the namedsemLockingContext
+       */
       enterMutex();
       rc = findLockInfo(h, pNew->zRealpath, &pNew->pLock, &pNew->pOpen);
       if( (rc==SQLITE_OK) && (pNew->pOpen->pSem==NULL) ){
@@ -2904,7 +3710,7 @@ static int fillInUnixFile(
   pNew->isDelete = isDelete;
 #endif
   if( rc!=SQLITE_OK ){
-    if( dirfd>=0 ) close(dirfd);
+    if( dirfd>=0 ) close(dirfd); /* silent leak if fail, already in error */
     close(h);
   }else{
     pNew->pMethod = &aIoMethod[eLockingStyle-1];
@@ -2913,6 +3719,29 @@ static int fillInUnixFile(
   return rc;
 }
 
+#if SQLITE_ENABLE_LOCKING_STYLE
+static sqlite3_io_methods *ioMethodForLockingStyle(int style){
+  return &aIoMethod[style-1];
+}
+
+static int getDbPathForUnixFile(unixFile *pFile, char *dbPath){
+  if( pFile->pMethod==ioMethodForLockingStyle(LOCKING_STYLE_AFP) ){
+    /* afp style keeps a reference to the db path in the filePath field of the struct */
+    strlcpy(dbPath, ((afpLockingContext *)pFile->lockingContext)->dbPath, MAXPATHLEN);
+    return SQLITE_OK;
+  }
+  if( pFile->pMethod==ioMethodForLockingStyle(LOCKING_STYLE_DOTFILE) ){
+    /* dot lock style uses the locking context to store the dot lock file path */
+    int len = strlen((char *)pFile->lockingContext) - strlen(DOTLOCK_SUFFIX);
+    strlcpy(dbPath, (char *)pFile->lockingContext, len + 1);
+    return SQLITE_OK;
+  }
+  /* all other styles use the locking context to store the db file path */
+  strlcpy(dbPath, (char *)pFile->lockingContext, MAXPATHLEN);
+  return SQLITE_OK;
+}
+#endif
+
 /*
 ** Open a file descriptor to the directory containing file zFilename.
 ** If successful, *pFd is set to the opened file descriptor and
@@ -2951,6 +3780,7 @@ static int openDirectory(const char *zFilename, int *pFd){
 */
 static int getTempname(int nBuf, char *zBuf){
   static const char *azDirs[] = {
+     0,
      0,
      "/var/tmp",
      "/usr/tmp",
@@ -2972,7 +3802,11 @@ static int getTempname(int nBuf, char *zBuf){
   SimulateIOError( return SQLITE_IOERR );
 
   azDirs[0] = sqlite3_temp_directory;
-  for(i=0; i<ArraySize(azDirs); i++){
+  if (NULL == azDirs[1]) {
+    azDirs[1] = getenv("TMPDIR");
+  }
+  
+  for(i=0; i<sizeof(azDirs)/sizeof(azDirs[0]); i++){
     if( azDirs[i]==0 ) continue;
     if( stat(azDirs[i], &buf) ) continue;
     if( !S_ISDIR(buf.st_mode) ) continue;
@@ -3035,6 +3869,7 @@ static int unixOpen(
   int oflags = 0;                /* Flags to pass to open() */
   int eType = flags&0xFFFFFF00;  /* Type of file to open */
   int noLock;                    /* True to omit locking primitives */
+  int rc = SQLITE_OK;
 
   int isExclusive  = (flags & SQLITE_OPEN_EXCLUSIVE);
   int isDelete     = (flags & SQLITE_OPEN_DELETEONCLOSE);
@@ -3085,7 +3920,6 @@ static int unixOpen(
   memset(pFile, 0, sizeof(unixFile));
 
   if( !zName ){
-    int rc;
     assert(isDelete && !isOpenDirectory);
     rc = getTempname(MAX_PATHNAME+1, zTmpname);
     if( rc!=SQLITE_OK ){
@@ -3117,6 +3951,8 @@ static int unixOpen(
 #else
     unlink(zName);
 #endif
+  }else{
+    ((unixFile *)pFile)->oflags = oflags;
   }
   if( pOutFlags ){
     *pOutFlags = flags;
@@ -3124,9 +3960,9 @@ static int unixOpen(
 
   assert(fd!=0);
   if( isOpenDirectory ){
-    int rc = openDirectory(zPath, &dirfd);
+    rc = openDirectory(zPath, &dirfd);
     if( rc!=SQLITE_OK ){
-      close(fd);
+      close(fd); /* silently leak if fail, already in error */
       return rc;
     }
   }
@@ -3136,6 +3972,38 @@ static int unixOpen(
 #endif
 
   noLock = eType!=SQLITE_OPEN_MAIN_DB;
+
+#if SQLITE_PREFER_PROXY_LOCKING
+  if( zPath!=NULL && !noLock ){
+    char *envforce = getenv("SQLITE_FORCE_PROXY_LOCKING");
+    int useProxy = 0;
+
+    /* SQLITE_FORCE_PROXY_LOCKING==1 means force always use proxy, 
+     ** 0 means never use proxy, NULL means use proxy for non-local files only
+     */
+    if( envforce!=NULL ){
+      useProxy = atoi(envforce)>0;
+    }else{
+      struct statfs fsInfo;
+
+      if( statfs(zPath, &fsInfo) == -1 ){
+                               ((unixFile*)pFile)->lastErrno = errno;
+        if( dirfd>=0 ) close(dirfd); /* silently leak if fail, in error */
+        close(fd); /* silently leak if fail, in error */
+        return SQLITE_IOERR_ACCESS;
+      }
+      useProxy = !(fsInfo.f_flags&MNT_LOCAL);
+    }
+    if( useProxy ){
+      rc = fillInUnixFile(pVfs, fd, dirfd, pFile, zPath, noLock, isDelete);
+      if( rc==SQLITE_OK ){
+        rc = transformUnixFileForLockProxy((unixFile*)pFile, ":auto:");
+      }
+      return rc;
+    }
+  }
+#endif
+  
   return fillInUnixFile(pVfs, fd, dirfd, pFile, zPath, noLock, isDelete);
 }
 
@@ -3161,7 +4029,9 @@ static int unixDelete(sqlite3_vfs *NotUsed, const char *zPath, int dirSync){
       {
         rc = SQLITE_IOERR_DIR_FSYNC;
       }
-      close(fd);
+      if( close(fd)&&!rc ){
+        rc = SQLITE_IOERR_DIR_CLOSE;
+      }
     }
   }
 #endif
@@ -3282,7 +4152,7 @@ static int unixFullPathname(
     zFull[j] = 0;
   }
 #endif
-#endif
+#endif /* #if IS_VXWORKS */
 }
 
 
@@ -3482,6 +4352,7 @@ int sqlite3_os_init(void){
     UNIXVFS("unix-dotfile", LOCKING_STYLE_DOTFILE), 
     UNIXVFS("unix-none",    LOCKING_STYLE_NONE),
     UNIXVFS("unix-namedsem",LOCKING_STYLE_NAMEDSEM),
+    UNIXVFS("unix-proxy",   LOCKING_STYLE_PROXY)
   };
   for(i=0; i<(sizeof(aVfs)/sizeof(sqlite3_vfs)); i++){
     sqlite3_vfs_register(&aVfs[i], 0);
index 71464e1d8e0b11781cad1952688c92f1fa2ce4fc..a467b62897cd1a6f129adab24a8c4fb697a937a2 100644 (file)
@@ -11,7 +11,7 @@
 *************************************************************************
 ** This file contains code used to implement the PRAGMA command.
 **
-** $Id: pragma.c,v 1.194 2008/11/17 19:18:55 danielk1977 Exp $
+** $Id: pragma.c,v 1.195 2008/11/21 00:10:35 aswift Exp $
 */
 #include "sqliteInt.h"
 #include <ctype.h>
@@ -706,6 +706,48 @@ void sqlite3Pragma(
     }
   }else
 
+  /*
+   **   PRAGMA [database.]lock_proxy_file
+   **   PRAGMA [database.]lock_proxy_file = ":auto:"|"lock_file_path"
+   **
+   ** Return or set the value of the lock_proxy_file flag.  Changing
+   ** the value sets a specific file to be used for database access locks.
+   **
+   */
+  if( sqlite3StrICmp(zLeft, "lock_proxy_file")==0 ){
+    if( !zRight ){
+      Pager *pPager = sqlite3BtreePager(pDb->pBt);
+      char *proxy_file_path = NULL;
+      sqlite3_file *pFile = sqlite3PagerFile(pPager);
+      sqlite3OsFileControl(pFile, SQLITE_GET_LOCKPROXYFILE, 
+                           &proxy_file_path);
+      
+      if( proxy_file_path ){
+        sqlite3VdbeSetNumCols(v, 1);
+        sqlite3VdbeSetColName(v, 0, COLNAME_NAME, 
+                              "lock_proxy_file", SQLITE_STATIC);
+        sqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, proxy_file_path, 0);
+        sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+      }
+    }else{
+      Pager *pPager = sqlite3BtreePager(pDb->pBt);
+      sqlite3_file *pFile = sqlite3PagerFile(pPager);
+      int res;
+      if( zRight[0] ){
+        res=sqlite3OsFileControl(pFile, SQLITE_SET_LOCKPROXYFILE, 
+                                     zRight);
+      } else {
+        res=sqlite3OsFileControl(pFile, SQLITE_SET_LOCKPROXYFILE, 
+                                     NULL);
+      }
+      if( res!=SQLITE_OK ){
+        sqlite3ErrorMsg(pParse, "failed to set lock proxy file");
+        goto pragma_out;
+      }
+    }
+  }else
+      
+    
   /*
   **   PRAGMA [database.]synchronous
   **   PRAGMA [database.]synchronous=OFF|ON|NORMAL|FULL
index 8f1c734595c993c97c03d91b63ac8de9fdfec55e..e98cf0308046293c8f6a47ae76463ef67f81769f 100644 (file)
@@ -30,7 +30,7 @@
 ** the version number) and changes its name to "sqlite3.h" as
 ** part of the build process.
 **
-** @(#) $Id: sqlite.h.in,v 1.415 2008/11/19 01:20:26 drh Exp $
+** @(#) $Id: sqlite.h.in,v 1.416 2008/11/21 00:10:35 aswift Exp $
 */
 #ifndef _SQLITE3_H_
 #define _SQLITE3_H_
@@ -507,6 +507,8 @@ int sqlite3_exec(
 #define SQLITE_IOERR_ACCESS            (SQLITE_IOERR | (13<<8))
 #define SQLITE_IOERR_CHECKRESERVEDLOCK (SQLITE_IOERR | (14<<8))
 #define SQLITE_IOERR_LOCK              (SQLITE_IOERR | (15<<8))
+#define SQLITE_IOERR_CLOSE             (SQLITE_IOERR | (16<<8))
+#define SQLITE_IOERR_DIR_CLOSE         (SQLITE_IOERR | (17<<8))
 
 /*
 ** CAPI3REF: Flags For File Open Operations {H10230} <H11120> <H12700>
@@ -723,6 +725,9 @@ struct sqlite3_io_methods {
 ** is defined.
 */
 #define SQLITE_FCNTL_LOCKSTATE        1
+#define SQLITE_GET_LOCKPROXYFILE      2
+#define SQLITE_SET_LOCKPROXYFILE      3
+#define SQLITE_LAST_ERRNO             4
 
 /*
 ** CAPI3REF: Mutex Handle {H17110} <S20130>
index 4c502564b55633f51635e8dc99469ea4cecff8d7..62aa0e11b7c0b2bd145e6e3c6b2549f91c2bc10c 100644 (file)
@@ -13,7 +13,7 @@
 ** is not included in the SQLite library.  It is used for automated
 ** testing of the SQLite library.
 **
-** $Id: test1.c,v 1.329 2008/10/30 15:03:16 drh Exp $
+** $Id: test1.c,v 1.330 2008/11/21 00:10:35 aswift Exp $
 */
 #include "sqliteInt.h"
 #include "tcl.h"
@@ -4466,9 +4466,87 @@ static int file_control_test(
   assert( rc==SQLITE_ERROR );
   rc = sqlite3_file_control(db, "temp", -1, &iArg);
   assert( rc==SQLITE_ERROR );
+
+  return TCL_OK;
+}
+
+
+/*
+** tclcmd:   file_control_lasterrno_test DB
+**
+** This TCL command runs the sqlite3_file_control interface and
+** verifies correct operation of the SQLITE_LAST_ERRNO verb.
+*/
+static int file_control_lasterrno_test(
+  ClientData clientData, /* Pointer to sqlite3_enable_XXX function */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  int iArg = 0;
+  sqlite3 *db;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " DB", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  rc = sqlite3_file_control(db, NULL, SQLITE_LAST_ERRNO, &iArg);
+  if( rc ) { Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); return TCL_ERROR; }
+  if( iArg!=0 ) {
+    Tcl_AppendResult(interp, "Unexpected non-zero errno: ",
+                     Tcl_GetStringFromObj(Tcl_NewIntObj(iArg), 0), " ", 0);
+    return TCL_ERROR;
+  }
   return TCL_OK;  
 }
 
+/*
+** tclcmd:   file_control_lockproxy_test DB
+**
+** This TCL command runs the sqlite3_file_control interface and
+** verifies correct operation of the SQLITE_GET_LOCKPROXYFILE and
+** SQLITE_SET_LOCKPROXYFILE verbs.
+*/
+static int file_control_lockproxy_test(
+  ClientData clientData, /* Pointer to sqlite3_enable_XXX function */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  int iArg = 0;
+  sqlite3 *db;
+  int rc;
+  
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+                     Tcl_GetStringFromObj(objv[0], 0), " DB", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  
+#ifdef SQLITE_ENABLE_LOCKING_STYLE
+       {
+    char *proxyPath = "test.proxy";
+    char *testPath;
+               rc = sqlite3_file_control(db, NULL, SQLITE_SET_LOCKPROXYFILE, proxyPath);
+    if( rc ) { Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); return TCL_ERROR; }
+    rc = sqlite3_file_control(db, NULL, SQLITE_GET_LOCKPROXYFILE, &testPath);
+    if( strncmp(proxyPath,testPath,11) ) {
+      Tcl_AppendResult(interp, "Lock proxy file did not match the previously assigned value", 0);
+      return TCL_ERROR;
+    }
+    if( rc ) { Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); return TCL_ERROR; }
+               rc = sqlite3_file_control(db, NULL, SQLITE_SET_LOCKPROXYFILE, proxyPath);
+    if( rc ) { Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); return TCL_ERROR; }
+  }
+#endif
+  return TCL_OK;  
+}
+
+
 /*
 ** tclcmd:   sqlite3_vfs_list
 **
@@ -4640,6 +4718,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
   extern int sqlite3_open_file_count;
   extern int sqlite3_sort_count;
   extern int sqlite3_current_time;
+  extern int sqlite3_hostid_num;
   extern int sqlite3_max_blobsize;
   extern int sqlite3BtreeSharedCacheReport(void*,
                                           Tcl_Interp*,int,Tcl_Obj*CONST*);
@@ -4784,6 +4863,8 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
      { "vfs_unregister_all",         vfs_unregister_all,  0   },
      { "vfs_reregister_all",         vfs_reregister_all,  0   },
      { "file_control_test",          file_control_test,   0   },
+     { "file_control_lasterrno_test", file_control_lasterrno_test,  0   },
+     { "file_control_lockproxy_test", file_control_lockproxy_test,  0   },
      { "sqlite3_vfs_list",           vfs_list,     0   },
 
      /* Functions from os.h */
@@ -4855,6 +4936,8 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
       (char*)&sqlite3_open_file_count, TCL_LINK_INT);
   Tcl_LinkVar(interp, "sqlite_current_time", 
       (char*)&sqlite3_current_time, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite_hostid_num", 
+      (char*)&sqlite3_hostid_num, TCL_LINK_INT);
   Tcl_LinkVar(interp, "sqlite3_xferopt_count",
       (char*)&sqlite3_xferopt_count, TCL_LINK_INT);
   Tcl_LinkVar(interp, "sqlite3_pager_readdb_count",
index 2bbb3b6f1b4198b033ba1ffcd2ed2c353728b683..a5b847a9da366582a5366d9ed6c11fd2ab8d4dc3 100644 (file)
@@ -16,7 +16,7 @@
 ** The focus of this file is providing the TCL testing layer
 ** access to compile-time constants.
 **
-** $Id: test_config.c,v 1.42 2008/10/12 00:27:54 shane Exp $
+** $Id: test_config.c,v 1.43 2008/11/21 00:10:35 aswift Exp $
 */
 
 #include "sqliteLimit.h"
@@ -392,6 +392,13 @@ Tcl_SetVar2(interp, "sqlite_options", "long_double",
   Tcl_SetVar2(interp, "sqlite_options", "schema_version", "1", TCL_GLOBAL_ONLY);
 #endif
 
+#ifdef SQLITE_ENABLE_LOCKING_STYLE
+    Tcl_SetVar2(interp, "sqlite_options", "lock_proxy_pragmas", "1", TCL_GLOBAL_ONLY);
+#else
+    Tcl_SetVar2(interp, "sqlite_options", "lock_proxy_pragmas", "0", TCL_GLOBAL_ONLY);
+#endif
+    
+    
 #ifdef SQLITE_OMIT_SHARED_CACHE
   Tcl_SetVar2(interp, "sqlite_options", "shared_cache", "0", TCL_GLOBAL_ONLY);
 #else
index 91253d4afc4a46f03c69f1d54b09dc04c7dbe99e..f60de3eb7932eac1b4e487aa438c753d70416d3a 100644 (file)
@@ -12,7 +12,7 @@
 # of these tests is exclusive access mode (i.e. the thing activated by 
 # "PRAGMA locking_mode = EXCLUSIVE").
 #
-# $Id: exclusive.test,v 1.9 2008/09/24 14:03:43 danielk1977 Exp $
+# $Id: exclusive.test,v 1.10 2008/11/21 00:10:35 aswift Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -402,6 +402,17 @@ do_test exclusive-4.5 {
 db close
 sqlite db test.db
 
+# if we're using proxy locks, we use 3 filedescriptors for a db
+# that is open but NOT writing changes, normally
+# sqlite uses 1 (proxy locking adds the conch and the local lock)
+set using_proxy 0
+foreach {name value} [array get env SQLITE_FORCE_PROXY_LOCKING] {
+  set using_proxy $value
+}
+set extrafds 0
+if {$using_proxy!=0} {
+  set extrafds 2
+} 
 
 do_test exclusive-5.0 {
   execsql {
@@ -414,6 +425,7 @@ do_test exclusive-5.0 {
 do_test exclusive-5.1 {
   # Three files are open: The db, journal and statement-journal.
   set sqlite_open_file_count
+  expr $sqlite_open_file_count-$extrafds
 } {3}
 do_test exclusive-5.2 {
   execsql {
@@ -421,6 +433,7 @@ do_test exclusive-5.2 {
   }
   # One file open: the db.
   set sqlite_open_file_count
+  expr $sqlite_open_file_count-$extrafds
 } {1}
 do_test exclusive-5.3 {
   execsql {
@@ -430,6 +443,7 @@ do_test exclusive-5.3 {
   }
   # Two files open: the db and journal.
   set sqlite_open_file_count
+  expr $sqlite_open_file_count-$extrafds
 } {2}
 do_test exclusive-5.4 {
   execsql {
@@ -437,6 +451,7 @@ do_test exclusive-5.4 {
   }
   # Three files are open: The db, journal and statement-journal.
   set sqlite_open_file_count
+  expr $sqlite_open_file_count-$extrafds
 } {3}
 do_test exclusive-5.5 {
   execsql {
@@ -444,6 +459,7 @@ do_test exclusive-5.5 {
   }
   # Three files are still open: The db, journal and statement-journal.
   set sqlite_open_file_count
+  expr $sqlite_open_file_count-$extrafds
 } {3}
 do_test exclusive-5.6 {
   execsql {
@@ -454,6 +470,7 @@ do_test exclusive-5.6 {
 do_test exclusive-5.7 {
   # Just the db open.
   set sqlite_open_file_count
+  expr $sqlite_open_file_count-$extrafds
 } {1}
 
 finish_test
index eb0a0e461db13cc9658ef9bf36842191b9ccce52..fe89a62635aa62115a14a558e92b290b65dc859a 100644 (file)
@@ -9,7 +9,7 @@
 #
 #***********************************************************************
 #
-# $Id: filectrl.test,v 1.1 2008/01/22 14:50:17 drh Exp $
+# $Id: filectrl.test,v 1.2 2008/11/21 00:10:35 aswift Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -27,5 +27,15 @@ do_test filectrl-1.3 {
   sqlite3 db :memory:
   file_control_test db
 } {}
-
+do_test filectrl-1.4 {
+  sqlite3 db test.db
+  file_control_lasterrno_test db
+} {}
+do_test filectrl-1.5 {
+  db close
+  sqlite3 db test_control_lockproxy.db
+  file_control_lockproxy_test db
+} {}
+db close
+file delete -force .test_control_lockproxy.db-conch test.proxy
 finish_test
index 0f0b5d2f2a78a6c4380c6bf7e8f40f5d5e60fc70..4c740a32c47835bdaf2c60c888a7c85fd97f08a9 100644 (file)
@@ -11,7 +11,7 @@
 # This file implements regression tests for SQLite library.  The
 # focus of this script is database locks.
 #
-# $Id: lock5.test,v 1.3 2008/09/24 09:12:47 danielk1977 Exp $
+# $Id: lock5.test,v 1.4 2008/11/21 00:10:35 aswift Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -25,6 +25,16 @@ if {[catch {sqlite3 db test.db -vfs unix-none} msg]} {
 }
 db close
 
+ifcapable lock_proxy_pragmas {
+  set ::using_proxy 0
+  foreach {name value} [array get env SQLITE_FORCE_PROXY_LOCKING] {
+    set ::using_proxy $value
+  }
+  # Disable the proxy locking for these tests
+  set env(SQLITE_FORCE_PROXY_LOCKING) "0"
+}
+
+
 do_test lock5-dotfile.1 {
   sqlite3 db test.db -vfs unix-dotfile
   execsql {
@@ -173,4 +183,8 @@ do_test lock5-flock.X {
   db2 close
 } {}
 
+ifcapable lock_proxy_pragmas {
+  set env(SQLITE_FORCE_PROXY_LOCKING) $::using_proxy
+}
+
 finish_test
diff --git a/test/lock6.test b/test/lock6.test
new file mode 100644 (file)
index 0000000..23d32eb
--- /dev/null
@@ -0,0 +1,168 @@
+# 2008 October 6
+#
+# 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 implements regression tests for SQLite library.  The
+# focus of this script is database locks.
+#
+# $Id: lock6.test,v 1.1 2008/11/21 00:10:35 aswift Exp $
+
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+# Launch another testfixture process to be controlled by this one. A
+# channel name is returned that may be passed as the first argument to proc
+# 'testfixture' to execute a command. The child testfixture process is shut
+# down by closing the channel.
+proc launch_testfixture {} {
+  set prg [info nameofexec]
+  if {$prg eq ""} {
+    set prg [file join . testfixture]
+  }
+  set chan [open "|$prg tf_main2.tcl" r+]
+  fconfigure $chan -buffering line
+  return $chan
+}
+
+# Execute a command in a child testfixture process, connected by two-way
+# channel $chan. Return the result of the command, or an error message.
+proc testfixture {chan cmd} {
+  puts $chan $cmd
+  puts $chan OVER
+  set r ""
+  while { 1 } {
+    set line [gets $chan]
+    if { $line == "OVER" } { 
+      return $r
+    }
+    append r $line
+  }
+}
+
+# Write the main loop for the child testfixture processes into file
+# tf_main2.tcl. The parent (this script) interacts with the child processes
+# via a two way pipe. The parent writes a script to the stdin of the child
+# process, followed by the word "OVER" on a line of its own. The child
+# process evaluates the script and writes the results to stdout, followed
+# by an "OVER" of its own.
+set f [open tf_main2.tcl w]
+puts $f {
+  set l [open log w]
+  set script ""
+  while {![eof stdin]} {
+    flush stdout
+    set line [gets stdin]
+    puts $l "READ $line"
+    if { $line == "OVER" } {
+      catch {eval $script} result
+      puts $result
+      puts $l "WRITE $result"
+      puts OVER
+      puts $l "WRITE OVER"
+      flush stdout
+      set script ""
+    } else {
+      append script $line
+      append script " ; "
+    }
+  }
+  close $l
+}
+close $f
+
+
+ifcapable lock_proxy_pragmas {
+  set sqlite_hostid_num 1
+
+  set using_proxy 0
+  foreach {name value} [array get env SQLITE_FORCE_PROXY_LOCKING] {
+    set using_proxy $value
+  }
+
+  # Test the lock_proxy_file pragmas.
+  #
+  set env(SQLITE_FORCE_PROXY_LOCKING) "1"
+
+  do_test lock6-1.1 {
+    set ::tf1 [launch_testfixture]
+    testfixture $::tf1 "set sqlite_pending_byte $::sqlite_pending_byte"
+    testfixture $::tf1 {
+      set sqlite_hostid_num 2    
+      sqlite3 db test.db -key xyzzy
+      set lockpath [db eval {
+        PRAGMA lock_proxy_file=":auto:";
+        select * from sqlite_master;
+        PRAGMA lock_proxy_file;
+      }]
+      string match "*test.db:auto:" $lockpath
+    }
+  } {1}
+  
+  set sqlite_hostid_num 3   
+  do_test lock6-1.2 {
+    execsql {pragma lock_status}
+  } {main unlocked temp closed}
+
+  sqlite3_soft_heap_limit 0
+  do_test lock6-1.3 {
+    sqlite3 db test.db
+    catchsql {
+      select * from sqlite_master;
+    } 
+  } {1 {database is locked}}
+
+  do_test lock6-1.4 {
+    set lockpath [execsql {
+      PRAGMA lock_proxy_file=":auto:";
+      PRAGMA lock_proxy_file;
+    } db]
+    set lockpath
+  } {{:auto: (not held)}}
+
+  do_test lock6-1.4.1 {
+    catchsql {
+      PRAGMA lock_proxy_file="notmine";
+      select * from sqlite_master;
+    } db
+  } {1 {database is locked}}
+
+  do_test lock6-1.4.2 {
+    execsql {
+      PRAGMA lock_proxy_file;
+    } db
+  } {notmine}
+    
+  do_test lock6-1.5 {
+    testfixture $::tf1 {
+      db eval {
+        BEGIN;
+        SELECT * FROM sqlite_master;
+      }
+    }
+  } {}
+
+  catch {testfixture $::tf1 {db close}}
+
+  do_test lock6-1.6 {
+    execsql {
+      PRAGMA lock_proxy_file="mine";
+      select * from sqlite_master;
+    } db
+  } {}
+  
+  catch {close $::tf1}
+  set env(SQLITE_FORCE_PROXY_LOCKING) $using_proxy
+  set sqlite_hostid_num 0
+
+  sqlite3_soft_heap_limit $soft_limit
+
+}
+      
+finish_test
index 9af5465b5466f1b60b8fdcf4236b40dc076efeec..b9a8eaf8d49bcf8d76aac35851a4d91073cbb4cb 100644 (file)
 # This file implements tests the ability of the library to open
 # many different databases at the same time without leaking memory.
 #
-# $Id: manydb.test,v 1.3 2006/01/11 01:08:34 drh Exp $
+# $Id: manydb.test,v 1.4 2008/11/21 00:10:35 aswift Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
 
 set N 300
+# if we're using proxy locks, we use 5 filedescriptors for a db
+# that is open and in the middle of writing changes, normally
+# sqlite uses 3 (proxy locking adds the conch and the local lock)
+set using_proxy 0
+foreach {name value} [array get env SQLITE_FORCE_PROXY_LOCKING] {
+  set using_proxy value
+}
+set num_fd_per_openwrite_db 3
+if {$using_proxy>0} {
+  set num_fd_per_openwrite_db 5
+} 
 
 # First test how many file descriptors are available for use. To open a
 # database for writing SQLite requires 3 file descriptors (the database, the
@@ -35,7 +46,7 @@ foreach fd $filehandles {
 catch {
   file delete -force testfile.1
 }
-set N [expr $i / 3]
+set N [expr $i / $num_fd_per_openwrite_db]
 
 # Create a bunch of random database names
 #
index 348d17cac22d5745d3c9d35ed8a1e75a311b9846..2cfc961a70baf5674c849c5139b20b73d4fa717b 100644 (file)
@@ -12,7 +12,7 @@
 #
 # This file implements tests for the PRAGMA command.
 #
-# $Id: pragma.test,v 1.69 2008/10/23 05:45:07 danielk1977 Exp $
+# $Id: pragma.test,v 1.70 2008/11/21 00:10:35 aswift Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -34,6 +34,7 @@ source $testdir/tester.tcl
 # pragma-14.*: Test the page_count pragma.
 # pragma-15.*: Test that the value set using the cache_size pragma is not
 #              reset when the schema is reloaded.
+# pragma-16.*: Test proxy locking
 #
 
 ifcapable !pragma {
@@ -1252,4 +1253,134 @@ sqlite3 dbX :memory:
 dbX eval {PRAGMA temp_store_directory = ""}
 dbX close
 
+ifcapable lock_proxy_pragmas {
+  set sqlite_hostid_num 1
+
+  set using_proxy 0
+  foreach {name value} [array get env SQLITE_FORCE_PROXY_LOCKING] {
+    set using_proxy $value
+  }
+
+  # Test the lock_proxy_file pragmas.
+  #
+  db close
+  set env(SQLITE_FORCE_PROXY_LOCKING) "0"
+
+  sqlite3 db test.db
+  do_test pragma-16.1 {
+    execsql {
+      PRAGMA lock_proxy_file="mylittleproxy";
+      select * from sqlite_master;
+    }
+    execsql {
+      PRAGMA lock_proxy_file;
+    } 
+  } {mylittleproxy}
+
+  do_test pragma-16.2 {
+    sqlite3 db2 test.db
+    execsql {
+      PRAGMA lock_proxy_file="mylittleproxy";
+    } db2
+  } {}
+
+  db2 close
+  do_test pragma-16.2.1 {
+    sqlite3 db2 test.db
+    execsql {
+      PRAGMA lock_proxy_file=":auto:";
+      select * from sqlite_master;
+    } db2
+    execsql {
+      PRAGMA lock_proxy_file;
+    } db2
+  } {mylittleproxy}
+
+  db2 close
+  do_test pragma-16.3 {
+    sqlite3 db2 test.db
+    execsql {
+      PRAGMA lock_proxy_file="myotherproxy";
+    } db2
+    catchsql {
+      select * from sqlite_master;
+    } db2
+  } {1 {database is locked}}
+
+  do_test pragma-16.4 {
+    db2 close
+    db close
+    sqlite3 db2 test.db
+    execsql {
+      PRAGMA lock_proxy_file="myoriginalproxy";
+      PRAGMA lock_proxy_file="myotherproxy";
+      PRAGMA lock_proxy_file;
+    } db2
+  } {myotherproxy}
+
+  db2 close
+  set env(SQLITE_FORCE_PROXY_LOCKING) "1"
+  do_test pragma-16.5 {
+    sqlite3 db2 test.db
+    execsql {
+      PRAGMA lock_proxy_file=":auto:";
+      PRAGMA lock_proxy_file;
+    } db2
+  } {myotherproxy}
+  
+  do_test pragma-16.6 {
+    db2 close
+    sqlite3 db2 test2.db
+    set lockpath [execsql {
+      PRAGMA lock_proxy_file=":auto:";
+      PRAGMA lock_proxy_file;
+    } db2]
+    string match "*test2.db:auto:" $lockpath
+  } {1}
+  
+  set sqlite_hostid_num 2
+  do_test pragma-16.7 {
+    sqlite3 db test2.db
+    execsql {
+      PRAGMA lock_proxy_file=":auto:";
+    } 
+    catchsql {
+      select * from sqlite_master;
+    }
+  } {1 {database is locked}}
+  db close
+  
+  do_test pragma-16.8 {
+    sqlite3 db test2.db
+    catchsql {
+      select * from sqlite_master;
+    } 
+  } {1 {database is locked}}
+
+  db2 close
+  do_test pragma-16.8.1 {
+    execsql {
+      PRAGMA lock_proxy_file="yetanotherproxy";
+      PRAGMA lock_proxy_file;
+    } 
+  } {yetanotherproxy}
+  do_test pragma-16.8.2 {
+    execsql {
+      create table mine(x);
+    } 
+  } {}
+
+  db close
+  do_test pragma-16.9 {
+    sqlite3 db proxytest.db
+    set lockpath2 [execsql {
+      PRAGMA lock_proxy_file=":auto:";
+      PRAGMA lock_proxy_file;
+    } db]
+    string match "*proxytest.db:auto:" $lockpath2
+  } {1}
+
+  set env(SQLITE_FORCE_PROXY_LOCKING) $using_proxy
+  set sqlite_hostid_num 0
+}
 finish_test
index 61a28c49b544b30e1b0392e9abec44e8ecbd1265..d57f54e739881aef2e51ccefc98ec5060907c8b2 100644 (file)
@@ -9,7 +9,7 @@
 #
 #***********************************************************************
 #
-# $Id: shared.test,v 1.34 2008/07/12 14:52:20 drh Exp $
+# $Id: shared.test,v 1.35 2008/11/21 00:10:35 aswift Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -42,6 +42,20 @@ ifcapable autovacuum {
   }
 }
 
+# if we're using proxy locks, we use 2 filedescriptors for a db
+# that is open but NOT yet locked, after a lock is taken we'll have 3, 
+# normally sqlite uses 1 (proxy locking adds the conch and the local lock)
+set using_proxy 0
+foreach {name value} [array get env SQLITE_FORCE_PROXY_LOCKING] {
+  set using_proxy $value
+}
+set extrafds_prelock 0
+set extrafds_postlock 0
+if {$using_proxy>0} {
+  set extrafds_prelock 1
+  set extrafds_postlock 2
+} 
+
 # $av is currently 0 if this loop iteration is to test with auto-vacuum turned
 # off, and 1 if it is turned on. Increment it so that (1 -> no auto-vacuum) 
 # and (2 -> auto-vacuum). The sole reason for this is so that it looks nicer
@@ -74,6 +88,7 @@ do_test shared-$av.1.1 {
   # opened.
   sqlite3 db2 test.db
   set ::sqlite_open_file_count
+  expr $sqlite_open_file_count-$extrafds_postlock
 } {1}
 do_test shared-$av.1.2 {
   # Add a table and a single row of data via the first connection. 
@@ -154,6 +169,7 @@ do_test shared-$av.2.1 {
     sqlite3 db3 TEST.DB
   }
   set ::sqlite_open_file_count
+  expr $sqlite_open_file_count-($extrafds_prelock+$extrafds_postlock)
 } {2}
 do_test shared-$av.2.2 {
   # Start read transactions on db and db2 (the shared pager cache). Ensure
@@ -284,14 +300,17 @@ sqlite3 db  test.db
 sqlite3 db2 test2.db
 do_test shared-$av.4.1.1 {
   set sqlite_open_file_count
+  expr $sqlite_open_file_count-($extrafds_prelock*2)
 } {2}
 do_test shared-$av.4.1.2 {
   execsql {ATTACH 'test2.db' AS test2}
   set sqlite_open_file_count
+  expr $sqlite_open_file_count-($extrafds_postlock*2)
 } {2}
 do_test shared-$av.4.1.3 {
   execsql {ATTACH 'test.db' AS test} db2
   set sqlite_open_file_count
+  expr $sqlite_open_file_count-($extrafds_postlock*2)
 } {2}
 
 # Sanity check: Create a table in ./test.db via handle db, and test that handle
index 5a42196fd8e4a0b8aae371fc2caa3ae3eb17ee66..e6530a3316b766dfa736eb48951bb28ee30f6acd 100644 (file)
@@ -11,7 +11,7 @@
 # This file implements some common TCL routines used for regression
 # testing the SQLite library
 #
-# $Id: tester.tcl,v 1.134 2008/08/05 17:53:24 drh Exp $
+# $Id: tester.tcl,v 1.135 2008/11/21 00:10:35 aswift Exp $
 
 #
 # What for user input before continuing.  This gives an opportunity
@@ -659,6 +659,7 @@ proc do_ioerr_test {testname args} {
     do_test $testname.$n.1 {
       set ::sqlite_io_error_pending 0
       catch {db close}
+      catch {db2 close}
       catch {file delete -force test.db}
       catch {file delete -force test.db-journal}
       catch {file delete -force test2.db}