Index: src/os_unix.c ================================================================== --- src/os_unix.c +++ src/os_unix.c @@ -22,11 +22,11 @@ ** This source file is organized into divisions where the logic for various ** subfunctions is contained within the appropriate division. PLEASE ** KEEP THE STRUCTURE OF THIS FILE INTACT. New code should be placed ** in the correct division and should be clearly labeled. ** -** The current set of divisions is as follows: +** The layout of divisions is as follows: ** ** * General-purpose declarations and utility functions. ** * Unique file ID logic used by VxWorks. ** * Various locking primitive implementations: ** + for Posix Advisory Locks @@ -37,21 +37,22 @@ ** + for AFP filesystem locks (MacOSX only) ** + for proxy locks (MacOSX only) ** * sqlite3_file methods not associated with locking. ** * Definitions of sqlite3_io_methods objects for all locking ** methods plus "finder" functions for each locking method. -** * VFS method implementations. +** * sqlite3_vfs method implementations. ** * Definitions of sqlite3_vfs objects for all locking methods ** plus implementations of sqlite3_os_init() and sqlite3_os_end(). ** -** $Id: os_unix.c,v 1.224 2008/11/29 02:20:27 drh Exp $ +** $Id: os_unix.c,v 1.225 2008/12/03 19:34:47 drh Exp $ */ #include "sqliteInt.h" #if SQLITE_OS_UNIX /* This file is used on unix only */ /* -** This module implements the following locking styles: +** There are various methods for file locking used for concurrency +** control: ** ** 1. POSIX locking (the default), ** 2. No locking, ** 3. Dot-file locking, ** 4. flock() locking, @@ -1788,18 +1789,29 @@ /****************************************************************************** ************************** Begin flock Locking ******************************** ** ** Use the flock() system call to do file locking. +** +** flock() locking is like dot-file locking in that the various +** fine-grain locking levels supported by SQLite are collapsed into +** a single exclusive lock. In other words, SHARED, RESERVED, and +** PENDING locks are the same thing as an EXCLUSIVE lock. SQLite +** still works when you do this, but concurrency is reduced since +** only a single process can be reading the database at a time. ** ** Omit this section if SQLITE_ENABLE_LOCKING_STYLE is turned off or if ** compiling for VXWORKS. */ #if SQLITE_ENABLE_LOCKING_STYLE && !OS_VXWORKS -/* flock-style reserved lock checking following the behavior of - ** unixCheckReservedLock, see the unixCheckReservedLock function comments */ +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, set *pResOut +** to a non-zero value otherwise *pResOut is set to zero. The return value +** is set to SQLITE_OK unless an I/O error occurs during lock checking. +*/ static int flockCheckReservedLock(sqlite3_file *id, int *pResOut){ int rc = SQLITE_OK; int reserved = 0; unixFile *pFile = (unixFile*)id; @@ -1849,10 +1861,39 @@ #endif /* SQLITE_IGNORE_FLOCK_LOCK_ERRORS */ *pResOut = reserved; return rc; } +/* +** Lock the file with the lock specified by parameter locktype - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> (PENDING) -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** flock() only really support EXCLUSIVE locks. We track intermediate +** lock states in the sqlite3_file structure, but all locks SHARED or +** above are really EXCLUSIVE locks and exclude all other processes from +** access the file. +** +** This routine will only increase a lock. Use the sqlite3OsUnlock() +** routine to lower a locking level. +*/ static int flockLock(sqlite3_file *id, int locktype) { int rc = SQLITE_OK; unixFile *pFile = (unixFile*)id; assert( pFile ); @@ -1885,10 +1926,18 @@ } #endif /* SQLITE_IGNORE_FLOCK_LOCK_ERRORS */ return rc; } + +/* +** Lower the locking level on file descriptor pFile to locktype. locktype +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +*/ static int flockUnlock(sqlite3_file *id, int locktype) { unixFile *pFile = (unixFile*)id; assert( pFile ); OSTRACE5("UNLOCK %d %d was %d pid=%d\n", pFile->h, locktype, @@ -1944,15 +1993,24 @@ /****************************************************************************** ************************ Begin Named Semaphore Locking ************************ ** ** Named semaphore locking is only supported on VxWorks. +** +** Semaphore locking is like dot-lock and flock in that it really only +** supports EXCLUSIVE locking. Only a single process can read or write +** the database file at a time. This reduces potential concurrency, but +** makes the lock implementation much easier. */ #if OS_VXWORKS -/* Namedsem-style reserved lock checking following the behavior of -** unixCheckReservedLock, see the unixCheckReservedLock function comments */ +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, set *pResOut +** to a non-zero value otherwise *pResOut is set to zero. The return value +** is set to SQLITE_OK unless an I/O error occurs during lock checking. +*/ static int semCheckReservedLock(sqlite3_file *id, int *pResOut) { int rc = SQLITE_OK; int reserved = 0; unixFile *pFile = (unixFile*)id; @@ -1988,10 +2046,39 @@ *pResOut = reserved; return rc; } +/* +** Lock the file with the lock specified by parameter locktype - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> (PENDING) -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** Semaphore locks only really support EXCLUSIVE locks. We track intermediate +** lock states in the sqlite3_file structure, but all locks SHARED or +** above are really EXCLUSIVE locks and exclude all other processes from +** access the file. +** +** This routine will only increase a lock. Use the sqlite3OsUnlock() +** routine to lower a locking level. +*/ static int semLock(sqlite3_file *id, int locktype) { unixFile *pFile = (unixFile*)id; int fd; sem_t *pSem = pFile->pOpen->pSem; int rc = SQLITE_OK; @@ -2015,10 +2102,17 @@ sem_end_lock: return rc; } +/* +** Lower the locking level on file descriptor pFile to locktype. locktype +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +*/ static int semUnlock(sqlite3_file *id, int locktype) { unixFile *pFile = (unixFile*)id; sem_t *pSem = pFile->pOpen->pSem; assert( pFile ); @@ -2091,11 +2185,11 @@ ** The afpLockingContext structure contains all afp lock specific state */ typedef struct afpLockingContext afpLockingContext; struct afpLockingContext { unsigned long long sharedByte; - const char *dbPath; + const char *dbPath; /* Name of the open file */ }; struct ByteRangeLockPB2 { unsigned long long offset; /* offset to first byte to lock */ @@ -2106,22 +2200,25 @@ int fd; /* file desc to assoc this lock with */ }; #define afpfsByteRangeLock2FSCTL _IOWR('z', 23, struct ByteRangeLockPB2) -/* - ** Return SQLITE_OK on success, SQLITE_BUSY on failure. - */ -static int _AFPFSSetLock( - const char *path, - unixFile *pFile, - unsigned long long offset, - unsigned long long length, - int setLockFlag +/* +** This is a utility for setting or clearing a bit-range lock on an +** AFP filesystem. +** +** Return SQLITE_OK on success, SQLITE_BUSY on failure. +*/ +static int afpSetLock( + const char *path, /* Name of the file to be locked or unlocked */ + unixFile *pFile, /* Open file descriptor on path */ + unsigned long long offset, /* First byte to be locked */ + unsigned long long length, /* Number of bytes to lock */ + int setLockFlag /* True to set lock. False to clear lock */ ){ - struct ByteRangeLockPB2 pb; - int err; + struct ByteRangeLockPB2 pb; + int err; pb.unLockFlag = setLockFlag ? 0 : 1; pb.startEndFlag = 0; pb.offset = offset; pb.length = length; @@ -2152,12 +2249,16 @@ } else { return SQLITE_OK; } } -/* AFP-style reserved lock checking following the behavior of -** unixCheckReservedLock, see the unixCheckReservedLock function comments */ +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, set *pResOut +** to a non-zero value otherwise *pResOut is set to zero. The return value +** is set to SQLITE_OK unless an I/O error occurs during lock checking. +*/ static int afpCheckReservedLock(sqlite3_file *id, int *pResOut){ int rc = SQLITE_OK; int reserved = 0; unixFile *pFile = (unixFile*)id; @@ -2173,15 +2274,15 @@ /* Otherwise see if some other process holds it. */ if( !reserved ){ /* lock the RESERVED byte */ - int lrc = _AFPFSSetLock(context->dbPath, pFile, RESERVED_BYTE, 1,1); + int lrc = afpSetLock(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->dbPath, pFile, RESERVED_BYTE, 1, 0); + lrc = afpSetLock(context->dbPath, pFile, RESERVED_BYTE, 1, 0); } else { /* if we failed to get the lock then someone else must have it */ reserved = 1; } if( IS_LOCK_ERROR(lrc) ){ @@ -2193,12 +2294,34 @@ *pResOut = reserved; return rc; } -/* AFP-style locking following the behavior of unixLock, see the unixLock -** function comments for details of lock management. */ +/* +** Lock the file with the lock specified by parameter locktype - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> (PENDING) -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** This routine will only increase a lock. Use the sqlite3OsUnlock() +** routine to lower a locking level. +*/ static int afpLock(sqlite3_file *id, int locktype){ int rc = SQLITE_OK; unixFile *pFile = (unixFile*)id; afpLockingContext *context = (afpLockingContext *) pFile->lockingContext; @@ -2240,11 +2363,11 @@ */ if( locktype==SHARED_LOCK || (locktype==EXCLUSIVE_LOCK && pFile->locktypedbPath, pFile, PENDING_BYTE, 1, 1); + failed = afpSetLock(context->dbPath, pFile, PENDING_BYTE, 1, 1); if (failed) { rc = failed; goto afp_end_lock; } } @@ -2257,17 +2380,17 @@ /* Now get the read-lock SHARED_LOCK */ /* note that the quality of the randomness doesn't matter that much */ lk = random(); context->sharedByte = (lk & 0x7fffffff)%(SHARED_SIZE - 1); - lrc1 = _AFPFSSetLock(context->dbPath, pFile, + lrc1 = afpSetLock(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->dbPath, pFile, PENDING_BYTE, 1, 0); + lrc2 = afpSetLock(context->dbPath, pFile, PENDING_BYTE, 1, 0); if( IS_LOCK_ERROR(lrc1) ) { pFile->lastErrno = lrc1Errno; rc = lrc1; goto afp_end_lock; @@ -2287,25 +2410,25 @@ */ int failed = 0; assert( 0!=pFile->locktype ); if (locktype >= RESERVED_LOCK && pFile->locktype < RESERVED_LOCK) { /* Acquire a RESERVED lock */ - failed = _AFPFSSetLock(context->dbPath, pFile, RESERVED_BYTE, 1,1); + failed = afpSetLock(context->dbPath, pFile, RESERVED_BYTE, 1,1); } if (!failed && locktype == EXCLUSIVE_LOCK) { /* Acquire an EXCLUSIVE lock */ /* 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->dbPath, pFile, SHARED_FIRST + + if( !(failed = afpSetLock(context->dbPath, pFile, SHARED_FIRST + context->sharedByte, 1, 0)) ){ int failed2 = SQLITE_OK; /* now attemmpt to get the exclusive lock range */ - failed = _AFPFSSetLock(context->dbPath, pFile, SHARED_FIRST, + failed = afpSetLock(context->dbPath, pFile, SHARED_FIRST, SHARED_SIZE, 1); - if( failed && (failed2 = _AFPFSSetLock(context->dbPath, pFile, + if( failed && (failed2 = afpSetLock(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 : @@ -2359,27 +2482,27 @@ } unixEnterMutex(); if( pFile->locktype>SHARED_LOCK ){ if( pFile->locktype==EXCLUSIVE_LOCK ){ - rc = _AFPFSSetLock(pCtx->dbPath, pFile, SHARED_FIRST, SHARED_SIZE, 0); + rc = afpSetLock(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); + rc = afpSetLock(pCtx->dbPath, pFile, sharedLockByte, 1, 1); } } if( rc==SQLITE_OK && pFile->locktype>=PENDING_LOCK ){ - rc = _AFPFSSetLock(pCtx->dbPath, pFile, PENDING_BYTE, 1, 0); + rc = afpSetLock(pCtx->dbPath, pFile, PENDING_BYTE, 1, 0); } if( rc==SQLITE_OK && pFile->locktype>=RESERVED_LOCK ){ - rc = _AFPFSSetLock(pCtx->dbPath, pFile, RESERVED_BYTE, 1, 0); + rc = afpSetLock(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); + rc = afpSetLock(pCtx->dbPath, pFile, sharedLockByte, 1, 0); } if( rc==SQLITE_OK ){ if( locktype==NO_LOCK ){ struct unixOpenCnt *pOpen = pFile->pOpen; @@ -2606,18 +2729,22 @@ /* ** Proxy locking is only available on MacOSX */ #if defined(__DARWIN__) && SQLITE_ENABLE_LOCKING_STYLE - -static int getDbPathForUnixFile(unixFile *pFile, char *dbPath); -static int getLockPath(const char *dbPath, char *lPath, size_t maxLen); -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); +/* +** Forward reference +*/ +static int fillInUnixFile( + sqlite3_vfs *pVfs, + int h, + int dirfd, + sqlite3_file *pId, + const char *zFilename, + int noLock, + int isDelete +); #ifdef SQLITE_TEST /* simulate multiple hosts by creating unique hostid file paths */ int sqlite3_hostid_num = 0; @@ -2627,95 +2754,20 @@ ** 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 */ + unixFile *conchFile; /* Open conch file */ + char *conchFilePath; /* Name of the conch file */ + unixFile *lockProxy; /* Open proxy lock file */ + char *lockProxyPath; /* Name of the proxy lock file */ + char *dbPath; /* Name of the open file */ + int conchHeld; /* True if the conch is currently held */ + void *oldLockingContext; /* Original lockingcontext to restore on close */ + sqlite3_io_methods const *pOldMethod; /* Original I/O methods for close */ }; - -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) @@ -2723,11 +2775,11 @@ # define HOSTIDPATH "/Library/Caches/.com.apple.sqliteConchHostId" #endif /* basically a copy of unixRandomness with different ** test behavior built in */ -static int genHostID(char *pHostID){ +static int proxyGenerateHostID(char *pHostID){ int pid, fd, i, len; unsigned char *key = (unsigned char *)pHostID; memset(key, 0, HOSTIDLEN); len = 0; @@ -2758,12 +2810,13 @@ #endif return SQLITE_OK; } /* 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){ +** with enough space for a path +*/ +static int proxyGetHostIDPath(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; @@ -2774,17 +2827,17 @@ } /* 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){ +static int proxyGetHostID(char *pHostID, int *pError){ int fd; char path[MAXPATHLEN]; size_t len; - int rc=SQLITE_OK; + int rc=SQLITE_OK; - getHostIDPath(path, MAXPATHLEN); + proxyGetHostIDPath(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; @@ -2817,11 +2870,11 @@ 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); + proxyGenerateHostID(pHostID); len = pwrite(fd, pHostID, HOSTIDLEN, 0); if( len<0 ){ *pError = errno; rc = SQLITE_IOERR_WRITE; }else if( lenlockingContext; if( pCtx->conchHeld>0 ){ return SQLITE_OK; }else{ @@ -2858,16 +2995,16 @@ 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; + int pError = 0; memset(testValue, 0, CONCHLEN); // conch is fixed size - rc = getHostID(testValue, &pError); - if( rc&SQLITE_IOERR==SQLITE_IOERR ){ - pFile->lastErrno = pError; - } + rc = proxyGetHostID(testValue, &pError); + if( rc&SQLITE_IOERR==SQLITE_IOERR ){ + pFile->lastErrno = pError; + } if( pCtx->lockProxyPath ){ strlcpy(&testValue[HOSTIDLEN], pCtx->lockProxyPath, MAXPATHLEN); } } if( rc!=SQLITE_OK ){ @@ -2906,11 +3043,11 @@ syncPerms = 1; } /* either conch was emtpy or didn't match */ if( !pCtx->lockProxyPath ){ - getLockPath(pCtx->dbPath, lockPath, MAXPATHLEN); + proxyGetLockPath(pCtx->dbPath, lockPath, MAXPATHLEN); tLockPath = lockPath; strlcpy(&testValue[HOSTIDLEN], lockPath, MAXPATHLEN); } /* update conch with host and path (this will fail if other process @@ -2962,17 +3099,17 @@ 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 + rc=SQLITE_CANTOPEN; // SQLITE_BUSY? proxyTakeConch 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); + rc = proxyCreateUnixFile(path, &pCtx->lockProxy); } if( rc==SQLITE_OK ){ pCtx->conchHeld = 1; if( tLockPath ){ @@ -2987,16 +3124,21 @@ } 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; +/* +** If pFile holds a lock on a conch file, then release that lock. +*/ +static int proxyReleaseConch(unixFile *pFile){ + int rc; /* Subroutine return code */ + proxyLockingContext *pCtx; /* The locking context for the proxy lock */ + unixFile *conchFile; /* Name of the conch file */ + + pCtx = (proxyLockingContext *)pFile->lockingContext; + 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); @@ -3003,20 +3145,33 @@ 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); +/* +** Given the name of a database file, compute the name of its conch file. +** Store the conch filename in memory obtained from sqlite3_malloc(). +** Make *pConchPath point to the new name. Return SQLITE_OK on success +** or SQLITE_NOMEM if unable to obtain memory. +** +** The caller is responsible for ensuring that the allocated memory +** space is eventually freed. +** +** *pConchPath is set to NULL if a memory allocation error occurs. +*/ +static int proxyCreateConchPathname(char *dbPath, char **pConchPath){ + int i; /* Loop counter */ + int len = strlen(dbPath); /* Length of database filename - dbPath */ + char *conchPath; /* buffer in which to construct conch name */ + + /* Allocate space for the conch filename and initialize the name to + ** the name of the original database file. */ + *pConchPath = conchPath = (char *)sqlite3_malloc(len + 8); if( conchPath==0 ){ return SQLITE_NOMEM; } - strlcpy(conchPath, dbPath, len+1); + memcpy(conchPath, dbPath, len+1); /* now insert a "." before the last / character */ for( i=(len-1); i>=0; i-- ){ if( conchPath[i]=='/' ){ i++; @@ -3026,64 +3181,18 @@ conchPath[i]='.'; while ( ilockProxyPath = sqlite3DbStrDup(0, path); } return rc; } + +/* +** pFile is a file that has been opened by a prior xOpen call. dbPath +** is a string buffer at least MAXPATHLEN+1 characters in size. +** +** This routine find the filename associated with pFile and writes it +** int dbPath. +*/ +static int proxyGetDbPathForUnixFile(unixFile *pFile, char *dbPath){ +#if defined(__DARWIN__) + if( pFile->pMethod == &afpIoMethods ){ + /* afp style keeps a reference to the db path in the filePath field + ** of the struct */ + assert( strlen((char*)pFile->lockingContext)<=MAXPATHLEN ); + strcpy(dbPath, ((afpLockingContext *)pFile->lockingContext)->dbPath) + }else +#endif + if( pFile->pMethod == &dotlockIoMethods ){ + /* dot lock style uses the locking context to store the dot lock + ** file path */ + int len = strlen((char *)pFile->lockingContext) - strlen(DOTLOCK_SUFFIX); + memcpy(dbPath, (char *)pFile->lockingContext, len + 1); + }else{ + /* all other styles use the locking context to store the db file path */ + assert( strlen((char*)pFile->lockingContext)<=MAXPATHLEN ); + strcpy(dbPath, (char *)pFile->lockingContext); + } + return SQLITE_OK; +} /* ** 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 @@ -3124,18 +3262,18 @@ ** ->lockingContext ** ->pMethod */ static int transformUnixFileForLockProxy(unixFile *pFile, const char *path) { proxyLockingContext *pCtx; - char dbPath[MAXPATHLEN]; + char dbPath[MAXPATHLEN+1]; /* Name of the database file */ char *lockPath=NULL; int rc = SQLITE_OK; if( pFile->locktype!=NO_LOCK ){ return SQLITE_BUSY; } - getDbPathForUnixFile(pFile, dbPath); + proxyGetDbPathForUnixFile(pFile, dbPath); if( !path || path[0]=='\0' || !strcmp(path, ":auto:") ){ lockPath=NULL; }else{ lockPath=(char *)path; } @@ -3147,13 +3285,13 @@ if( pCtx==0 ){ return SQLITE_NOMEM; } memset(pCtx, 0, sizeof(*pCtx)); - rc = getConchPathFromDBPath(dbPath, &pCtx->conchFilePath); + rc = proxyCreateConchPathname(dbPath, &pCtx->conchFilePath); if( rc==SQLITE_OK ){ - rc = createProxyUnixFile(pCtx->conchFilePath, &pCtx->conchFile); + rc = proxyCreateUnixFile(pCtx->conchFilePath, &pCtx->conchFile); } if( rc==SQLITE_OK && lockPath ){ pCtx->lockProxyPath = sqlite3DbStrDup(0, lockPath); } @@ -3177,40 +3315,132 @@ 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); +} + +/* +** Within this division (the proxying locking implementation) the procedures +** above this point are all utilities. The lock-related methods of the +** proxy-locking sqlite3_io_method object follow. +*/ + + +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, set *pResOut +** to a non-zero value otherwise *pResOut is set to zero. The return value +** is set to SQLITE_OK unless an I/O error occurs during lock checking. +*/ +static int proxyCheckReservedLock(sqlite3_file *id, int *pResOut) { + unixFile *pFile = (unixFile*)id; + int rc = proxyTakeConch(pFile); + if( rc==SQLITE_OK ){ + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + unixFile *proxy = pCtx->lockProxy; + return proxy->pMethod->xCheckReservedLock((sqlite3_file*)proxy, pResOut); + } + return rc; +} + +/* +** Lock the file with the lock specified by parameter locktype - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> (PENDING) -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** This routine will only increase a lock. Use the sqlite3OsUnlock() +** routine to lower a locking level. +*/ +static int proxyLock(sqlite3_file *id, int locktype) { + unixFile *pFile = (unixFile*)id; + int rc = proxyTakeConch(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; +} + + +/* +** Lower the locking level on file descriptor pFile to locktype. locktype +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +*/ +static int proxyUnlock(sqlite3_file *id, int locktype) { + unixFile *pFile = (unixFile*)id; + int rc = proxyTakeConch(pFile); if( rc==SQLITE_OK ){ - *ppFile = pNew; - return SQLITE_OK; + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + unixFile *proxy = pCtx->lockProxy; + rc = proxy->pMethod->xUnlock((sqlite3_file*)proxy, locktype); + pFile->locktype = proxy->locktype; } -end_create_proxy: - close(fd); /* silently leak fd if error, we're already in error */ - sqlite3_free(pNew); return rc; } + +/* +** Close a file that uses proxy locks. +*/ +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); + pCtx->lockProxy = 0; + } + if( conchFile ){ + if( pCtx->conchHeld ){ + rc = proxyReleaseConch(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; +} + #endif /* defined(__DARWIN__) && SQLITE_ENABLE_LOCKING_STYLE */ /* ** The proxy locking style is intended for use with AFP filesystems. @@ -3373,11 +3603,11 @@ } #ifdef SQLITE_TEST /* ** Count the number of fullsyncs and normal syncs. This is used to test -** that syncs and fullsyncs are occuring at the right times. +** that syncs and fullsyncs are occurring at the right times. */ int sqlite3_sync_count = 0; int sqlite3_fullsync_count = 0; #endif @@ -3448,17 +3678,17 @@ rc = fcntl(fd, F_FULLFSYNC, 0); }else{ rc = 1; } /* If the FULLFSYNC failed, fall back to attempting an fsync(). - * It shouldn't be possible for fullfsync to fail on the local - * file system (on OSX), so failure indicates that FULLFSYNC - * isn't supported for this file system. So, attempt an fsync - * and (for now) ignore the overhead of a superfluous fcntl call. - * It'd be better to detect fullfsync support once and avoid - * the fcntl call every time sync is called. - */ + ** It shouldn't be possible for fullfsync to fail on the local + ** file system (on OSX), so failure indicates that FULLFSYNC + ** isn't supported for this file system. So, attempt an fsync + ** and (for now) ignore the overhead of a superfluous fcntl call. + ** It'd be better to detect fullfsync support once and avoid + ** the fcntl call every time sync is called. + */ if( rc ) rc = fsync(fd); #else if( dataOnly ){ rc = fdatasync(fd); @@ -3607,11 +3837,11 @@ #if SQLITE_ENABLE_LOCKING_STYLE && defined(__DARWIN__) case SQLITE_GET_LOCKPROXYFILE: { unixFile *pFile = (unixFile*)id; if( pFile->pMethod == &proxyIoMethods ){ proxyLockingContext *pCtx = (proxyLockingContext*)pFile->lockingContext; - takeConch(pFile); + proxyTakeConch(pFile); if( pCtx->lockProxyPath ){ *(const char **)pArg = pCtx->lockProxyPath; }else{ *(const char **)pArg = ":auto: (not held)"; } @@ -3650,11 +3880,11 @@ rc = transformUnixFileForLockProxy(pFile, proxyPath); } } return rc; } -#endif +#endif /* SQLITE_ENABLE_LOCKING_STYLE && defined(__DARWIN__) */ } return SQLITE_ERROR; } /* @@ -3685,10 +3915,23 @@ ** ********************** End sqlite3_file Methods ******************************* ******************************************************************************/ /* +** This division contains definitions of sqlite3_io_methods objects that +** implement various file locking strategies. It also contains definitions +** of "finder" functions. A finder-function is used to locate the appropriate +** sqlite3_io_methods object for a particular database file. The pAppData +** field of the sqlite3_vfs VFS objects are initialized to be pointers to +** the correct finder-function for that VFS. +** +** Most finder functions return a pointer to a fixed sqlite3_io_methods +** object. The only interesting finder-function is autolockIoFinder, which +** looks at the filesystem type and tries to guess the best locking +** strategy from that. +** +** ** Each instance of this macro generates two objects: ** ** * A constant sqlite3_io_methods object call METHOD that has locking ** methods CLOSE, LOCK, UNLOCK, CKRESLOCK. ** @@ -3788,23 +4031,23 @@ #endif #if defined(__DARWIN__) && SQLITE_ENABLE_LOCKING_STYLE /* -** This procedure attempts to determine the best locking strategy for -** the given database file. It then returns the sqlite3_io_methods +** This "finder" function attempts to determine the best locking strategy +** for the database file "filePath". It then returns the sqlite3_io_methods ** object that implements that strategy. ** ** This is for MacOSX only. */ static const sqlite3_io_methods *autolockIoFinder( const char *filePath, /* name of the database file */ int fd /* file descriptor open on the database file */ ){ static const struct Mapping { - const char *zFilesystem; - const sqlite3_io_methods *pMethods; + const char *zFilesystem; /* Filesystem type name */ + const sqlite3_io_methods *pMethods; /* Appropriate locking method */ } aMap[] = { { "hfs", &posixIoMethods }, { "ufs", &posixIoMethods }, { "afpfs", &afpIoMethods }, #ifdef SQLITE_ENABLE_AFP_LOCKING_SMB @@ -3818,10 +4061,12 @@ int i; struct statfs fsInfo; struct flock lockInfo; if( !filePath ){ + /* If filePath==NULL that means we are dealing with a transient file + ** that does not need to be locked. */ return &nolockIoMethods; } if( statfs(filePath, &fsInfo) != -1 ){ if( fsInfo.f_flags & MNT_RDONLY ){ return &nolockIoMethods; @@ -4006,34 +4251,10 @@ OpenCounter(+1); } return rc; } -#if SQLITE_ENABLE_LOCKING_STYLE -static int getDbPathForUnixFile(unixFile *pFile, char *dbPath){ -#if defined(__DARWIN__) - if( pFile->pMethod == &afpIoMethods ){ - /* afp style keeps a reference to the db path in the filePath field - ** of the struct */ - assert( strlen((char*)pFile->lockingContext)<=MAXPATHLEN ); - strcpy(dbPath, ((afpLockingContext *)pFile->lockingContext)->dbPath) - }else -#endif - if( pFile->pMethod == &dotlockIoMethods ){ - /* dot lock style uses the locking context to store the dot lock - ** file path */ - int len = strlen((char *)pFile->lockingContext) - strlen(DOTLOCK_SUFFIX); - memcpy(dbPath, (char *)pFile->lockingContext, len + 1); - }else{ - /* all other styles use the locking context to store the db file path */ - assert( strlen((char*)pFile->lockingContext)<=MAXPATHLEN ); - strcpy(dbPath, (char *)pFile->lockingContext); - } - 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 ** SQLITE_OK is returned. If an error occurs, either SQLITE_NOMEM ** or SQLITE_CANTOPEN is returned and *pFd is set to an undefined @@ -4146,19 +4367,19 @@ ** file handle closed. To achieve the same effect using this new ** interface, add the DELETEONCLOSE flag to those specified above for ** OpenExclusive(). */ static int unixOpen( - sqlite3_vfs *pVfs, - const char *zPath, - sqlite3_file *pFile, - int flags, - int *pOutFlags + sqlite3_vfs *pVfs, /* The VFS for which this is the xOpen method */ + const char *zPath, /* Pathname of file to be opened */ + sqlite3_file *pFile, /* The file descriptor to be filled in */ + int flags, /* Input flags to control the opening */ + int *pOutFlags /* Output flags returned to SQLite core */ ){ int fd = 0; /* File descriptor returned by open() */ int dirfd = -1; /* Directory file descriptor */ - int openFlags = 0; /* Flags to pass to open() */ + int openFlags = 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); @@ -4302,11 +4523,15 @@ /* ** Delete the file at zPath. If the dirSync argument is true, fsync() ** the directory after deleting the file. */ -static int unixDelete(sqlite3_vfs *NotUsed, const char *zPath, int dirSync){ +static int unixDelete( + sqlite3_vfs *NotUsed, /* VFS containing this as the xDelete method */ + const char *zPath, /* Name of file to be deleted */ + int dirSync /* If true, fsync() directory after deleting file */ +){ int rc = SQLITE_OK; UNUSED_PARAMETER(NotUsed); SimulateIOError(return SQLITE_IOERR_DELETE); unlink(zPath); #ifndef SQLITE_DISABLE_DIRSYNC @@ -4340,14 +4565,14 @@ ** SQLITE_ACCESS_READONLY: Return 1 if the file is readable. ** ** Otherwise return 0. */ static int unixAccess( - sqlite3_vfs *NotUsed, - const char *zPath, - int flags, - int *pResOut + sqlite3_vfs *NotUsed, /* The VFS containing this xAccess method */ + const char *zPath, /* Path of the file to examine */ + int flags, /* What do we want to learn about the zPath file? */ + int *pResOut /* Write result boolean here */ ){ int amode = 0; UNUSED_PARAMETER(NotUsed); SimulateIOError( return SQLITE_IOERR_ACCESS; ); switch( flags ){ @@ -4386,11 +4611,11 @@ ){ /* It's odd to simulate an io-error here, but really this is just ** using the io-error infrastructure to test that SQLite handles this ** function failing. This function could fail if, for example, the - ** current working directly has been unlinked. + ** current working directory has been unlinked. */ SimulateIOError( return SQLITE_ERROR ); assert( pVfs->mxPathname==MAX_PATHNAME ); UNUSED_PARAMETER(pVfs); @@ -4521,15 +4746,16 @@ #endif UNUSED_PARAMETER(NotUsed); } /* -** The following variable, if set to a non-zero value, becomes the result -** returned from sqlite3OsCurrentTime(). This is used for testing. +** The following variable, if set to a non-zero value, is interpreted as +** the number of seconds since 1970 and is used to set the result of +** sqlite3OsCurrentTime() during testing. */ #ifdef SQLITE_TEST -int sqlite3_current_time = 0; +int sqlite3_current_time = 0; /* Fake system time in seconds since 1970. */ #endif /* ** Find the current time (in Universal Coordinated Time). Write the ** current time and date as a Julian Day number into *prNow and @@ -4557,10 +4783,17 @@ #endif UNUSED_PARAMETER(NotUsed); return 0; } +/* +** We added the xGetLastError() method with the intention of providing +** better low-level error messages when operating-system problems come up +** during SQLite operation. But so far, none of that has been implemented +** in the core. So this routine is never called. For now, it is merely +** a place-holder. +*/ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ UNUSED_PARAMETER(NotUsed); UNUSED_PARAMETER(NotUsed2); UNUSED_PARAMETER(NotUsed3); return 0; @@ -4575,16 +4808,31 @@ ** ** This routine registers all VFS implementations for unix-like operating ** systems. This routine, and the sqlite3_os_end() routine that follows, ** should be the only routines in this file that are visible from other ** files. +** +** This routine is called once during SQLite initialization and by a +** single thread. The memory allocation and mutex subsystems have not +** necessarily been initialized when this routine is called, and so they +** should not be used. */ int sqlite3_os_init(void){ - /* Macro to define the static contents of an sqlite3_vfs structure for - ** the unix backend. The two parameters are the values to use for - ** the sqlite3_vfs.zName and sqlite3_vfs.pAppData fields, respectively. - ** + /* + ** The following macro defines an initializer for an sqlite3_vfs object. + ** The name of the VFS is NAME. The pAppData is a pointer to a "finder" + ** function. The FINDER parameter to this macro is the name of the + ** finder-function. The finder-function returns a pointer to the + ** sqlite_io_methods object that implements the desired locking + ** behaviors. See the division above that contains the IOMETHODS + ** macro for addition information on finder-functions. + ** + ** Most finders simply return a pointer to a fixed sqlite3_io_methods + ** object. But the "autolockIoFinder" available on MacOSX does a little + ** more than that; it looks at the filesystem type that hosts the + ** database file and tries to choose an locking method appropriate for + ** that filesystem time. */ #define UNIXVFS(VFSNAME, FINDER) { \ 1, /* iVersion */ \ sizeof(unixFile), /* szOsFile */ \ MAX_PATHNAME, /* mxPathname */ \ @@ -4603,11 +4851,17 @@ unixSleep, /* xSleep */ \ unixCurrentTime, /* xCurrentTime */ \ unixGetLastError /* xGetLastError */ \ } - unsigned int i; + /* + ** All default VFSes for unix are contained in the following array. + ** + ** Note that the sqlite3_vfs.pNext field of the VFS object is modified + ** by the SQLite core when the VFS is registered. So the following + ** array cannot be const. + */ static sqlite3_vfs aVfs[] = { #if SQLITE_ENABLE_LOCKING_STYLE && defined(__DARWIN__) UNIXVFS("unix", autolockIoFinder ), #else UNIXVFS("unix", posixIoFinder ), @@ -4624,19 +4878,26 @@ #if SQLITE_ENABLE_LOCKING_STYLE && defined(__DARWIN__) UNIXVFS("unix-afp", afpIoFinder ), UNIXVFS("unix-proxy", proxyIoFinder ), #endif }; + unsigned int i; /* Loop counter */ + + /* Register all VFSes defined in the aVfs[] array */ for(i=0; i<(sizeof(aVfs)/sizeof(sqlite3_vfs)); i++){ sqlite3_vfs_register(&aVfs[i], i==0); } return SQLITE_OK; } /* -** Shutdown the operating system interface. This is a no-op for unix. +** Shutdown the operating system interface. +** +** Some operating systems might need to do some cleanup in this routine, +** to release dynamically allocated objects. But not on unix. +** This routine is a no-op for unix. */ int sqlite3_os_end(void){ return SQLITE_OK; } #endif /* SQLITE_OS_UNIX */