From: adam Date: Tue, 3 Nov 2009 22:34:35 +0000 (+0000) Subject: Merging in OSX customizations: X-Git-Tag: mountain-lion~72 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e6c3c72c1b1070d5e118a04fec790c915fec2231;p=thirdparty%2Fsqlite.git Merging in OSX customizations: A ext/sqlrr/sqlrr.[ch] -- add sql recording/replay support (very experimental extension -- pre alpha) M ext/rtree/rtree.c -- fix cast compiler warning M src/func.c -- substring backward compatiblity M src/legacy.c -- replay recorder calls M src/main.c -- if SQLITE_ENABLE_AUTO_PROFILE, call sqlite3_profile when env(SQLITE_AUTO_PROFILE) -- replay recorder calls M src/mem1.c -- use custom malloc zone on single core systems M src/os_unix.c -- hostid via gethostuuid -- read-only file system support for proxy locking -- improved handling of simulated shared lock on AFP/SMB on 64 bit systems -- disable whole-file-lock NFS file locking -- fix for downgrading from exclusive to shared lock on NFS -- proxy lock breaking & recreating local lock files -- support SQLITE_OPEN_AUTOPROXY flag -- only write 1st byte into new zero-length files if fs is DOS -- cache fs type info in unixFile struct -- force use of fsync() even if fdatasync() is available -- remove flock locking style support for SMB -- replace strcpy with strlcpy M src/prepare.c -- replay recorder calls M src/sqlite.h.in -- added SQLITE_OPEN_AUTOPROXY M src/sqliteInt.h -- include replay recorder header M src/vdbeapi.c -- checking for NULL statement for -- replay recorder calls FossilOrigin-Name: 941a01eb868815f566539e9ab21f807d9e798e40 --- diff --git a/Makefile.in b/Makefile.in index 27687f927c..e54834d81a 100644 --- a/Makefile.in +++ b/Makefile.in @@ -326,6 +326,9 @@ SRC += \ SRC += \ $(TOP)/ext/rtree/rtree.h \ $(TOP)/ext/rtree/rtree.c +SRC += \ + $(TOP)/ext/sqlrr/sqlrr.h \ + $(TOP)/ext/sqlrr/sqlrr.c # Source code to the library files needed by the test fixture # @@ -434,7 +437,9 @@ HDR += \ $(TOP)/ext/rtree/rtree.h HDR += \ $(TOP)/ext/icu/sqliteicu.h - +HDR += \ + $(TOP)/ext/sqlrr/sqlrr.h + # If using the amalgamation, use sqlite3.c directly to build the test # fixture. Otherwise link against libsqlite3.la. (This distinction is # necessary because the test fixture requires non-API symbols which are diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index f4d74bf0e7..8b4cd43294 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -65,6 +65,7 @@ #include #include +#include #ifndef SQLITE_AMALGAMATION typedef sqlite3_int64 i64; @@ -2670,7 +2671,7 @@ static int rtreeInit( Rtree *pRtree; int nDb; /* Length of string argv[1] */ int nName; /* Length of string argv[2] */ - int eCoordType = (int)pAux; + int eCoordType = (int)(intptr_t)pAux; const char *aErrMsg[] = { 0, /* 0 */ diff --git a/ext/sqlrr/README.txt b/ext/sqlrr/README.txt new file mode 100644 index 0000000000..6d39063e6a --- /dev/null +++ b/ext/sqlrr/README.txt @@ -0,0 +1,54 @@ + +sqlrr - 10/19/2009 + +SQL Replay Recording + +SUMMARY +------------------------------------------------------- +This extension enables recording sqlite API calls that access the database so that they can be replayed or examined. + +USAGE +------------------------------------------------------ +Recording is enabled by compiling sqlite with symbolic constant SQLITE_ENABLE_SQLRR defined. +By default logs are written to /tmp/__.sqlrr, to choose another directory, set the environment variable SQLITE_REPLAY_RECORD_DIR to that path. + +FILE FORMAT +----------------------------------------------------- + file:
[]* + + header: + signature: SQLRR (5 bytes) + format-version: n (1 byte) + + sql-command: + timestamp: n (16 bytes) + type: n (1 byte) + open 0 + close 1 + exec 8 + bind-text 16 + bind-double 17 + bind-int 18 + bind-null 19 + bind-value 20 + bind-clear 21 + prep 32 + step 33 + reset 34 + finalize 35 + + open-arg-data: + close-arg-data: + exec-arg-data: + bind-text-arg-data: + bind-double-arg-data: + bind-int-arg-data: + bind-null-arg-data: + bind-value-arg-data: ??? + bind-clear-arg-data: + prep-arg-data: + step-arg-data: + reset-arg-data: + finalize-arg-data: + +NOTES diff --git a/ext/sqlrr/sqlrr.c b/ext/sqlrr/sqlrr.c new file mode 100644 index 0000000000..7550a642e5 --- /dev/null +++ b/ext/sqlrr/sqlrr.c @@ -0,0 +1,581 @@ +/* + * sqlrr.c + */ + +#include "sqlrr.h" + +#if defined(SQLITE_ENABLE_SQLRR) + +#include +#include +#include +#include +#include +#include +#include + +#include "sqliteInt.h" +#include "vdbeInt.h" + +#define LOGSUFFIXLEN 48 + +/* + * Data types + */ +typedef struct SRRLogRef SRRLogRef; +struct SRRLogRef { + int fd; + sqlite3 *db; + const char *dbPath; + char *logPath; + int connection; + int depth; + SRRLogRef *nextRef; +}; + +/* + * Globals + */ +SRRLogRef *logRefHead = NULL; +int dbLogCount = 0; +static int srr_enabled = 1; +pthread_mutex_t srr_log_mutex; +static volatile int32_t srr_initialized = 0; + +/* + * Log management + */ +extern void SRRecInitialize() { + int go = OSAtomicCompareAndSwap32Barrier(0, 1, &srr_initialized); + if( go ){ + pthread_mutex_init(&srr_log_mutex, NULL); + } +} + +static SRRLogRef *createLog(sqlite3 *db, const char *dbPath) { + SRRLogRef *ref = NULL; + char *baseDir = getenv("SQLITE_REPLAY_RECORD_DIR"); + char logPath[MAXPATHLEN] = ""; + char suffix[LOGSUFFIXLEN] = ""; + const char *dbName = dbPath; + int len = 0; + int index = 0; + int fd = -1; + size_t out; + unsigned char version = SRR_FILE_VERSION; + + SRRecInitialize(); + + /* construct the path for the log file + * ${SQLITE_REPLAY_DIR}/__.sqlrr + */ + if (baseDir == NULL) { + baseDir = "/tmp"; /* getenv(TMPDIR) */ + } + len = strlen(baseDir); + strlcat(logPath, baseDir, MAXPATHLEN); + if ((len>0) && (baseDir[len-1] != '/')) { + strlcat(logPath, "/", MAXPATHLEN); + } + len = strlen(dbPath); + for (index = len-2; index >= 0; index --){ + if (dbPath[index] == '/') { + dbName = &dbPath[index+1]; + break; + } + } + strlcat(logPath, dbName, MAXPATHLEN); + int cNum = ++dbLogCount; + snprintf(suffix, sizeof(suffix), "_%d_%d_XXXX.sqlrr", getpid(), cNum); + len = strlcat(logPath, suffix, MAXPATHLEN); + /* make it unique if we have the space */ + if ((len + 1) < MAXPATHLEN) { + fd = mkstemps(logPath, 6); + } else { + fprintf(stderr, "Failed to create sqlite replay log path for %s [%s]\n", dbPath, logPath); + return NULL; + } + if (fd == -1) { + fprintf(stderr, "Failed to create sqlite replay log file for %s with path %s [%s]\n", dbPath, logPath, strerror(errno)); + return NULL; + } + fprintf(stdout, "Writing sqlite replay log file %s\n", logPath); + out = write(fd, SRR_FILE_SIGNATURE, SRR_FILE_SIGNATURE_LEN); + if (out!=-1) { + out = write(fd, &version, 1); + } + if (out == -1){ + fprintf(stderr, "Write failure on log [%s]: %s\n", logPath, strerror(errno)); + close(fd); + return NULL; + } + + len = strlen(logPath) + 1; + ref = (SRRLogRef *)malloc(sizeof(SRRLogRef)); + + ref->db = db; + ref->dbPath = dbPath; + ref->logPath = (char *)malloc(len * sizeof(char)); + strlcpy(ref->logPath, logPath, len); + ref->fd = fd; + ref->connection = cNum; + ref->depth = 0; + + pthread_mutex_lock(&srr_log_mutex); + ref->nextRef = logRefHead; + logRefHead = ref; + pthread_mutex_unlock(&srr_log_mutex); + return ref; +} + +static void closeLog(sqlite3 *db) { + SRRLogRef *ref = NULL; + SRRLogRef *lastRef = NULL; + + pthread_mutex_lock(&srr_log_mutex); + for (ref = logRefHead; ref != NULL; ref = ref->nextRef) { + if (ref->db == db) { + if (lastRef == NULL) { + logRefHead = ref->nextRef; + } else { + lastRef->nextRef = ref->nextRef; + } + } + } + pthread_mutex_unlock(&srr_log_mutex); + + if (ref != NULL) { + fprintf(stdout, "Closing sqlite replay log file %s\n", ref->logPath); + close(ref->fd); + free(ref->logPath); + free(ref); + } +} + +static SRRLogRef *getLog(sqlite3 *db) { + pthread_mutex_lock(&srr_log_mutex); + SRRLogRef *ref = logRefHead; + for (ref = logRefHead; ref != NULL; ref = ref->nextRef) { + if (ref->db == db) { + pthread_mutex_unlock(&srr_log_mutex); + return ref; + } + } + pthread_mutex_unlock(&srr_log_mutex); + return NULL; +} + + +/* + * SQLite recording API + */ +void SQLiteReplayRecorder(int flag) { + srr_enabled = flag; +} + +// open-arg-data: +void _SRRecOpen(sqlite3 *db, const char *path, int flags) { + if (!srr_enabled) return; + if (db) { + SRRLogRef *ref = createLog(db, path); + if (ref) { + SRRCommand code = SRROpen; + int len = strlen(path); + struct timeval tv; + size_t out; + + gettimeofday(&tv, NULL); + out = write(ref->fd, &tv, sizeof(tv)); + if (out!=-1) { out=write(ref->fd, &code, sizeof(SRRCommand)); } + if (out!=-1) { out=write(ref->fd, &(ref->connection), sizeof(ref->connection)); } + if (out!=-1) { out=write(ref->fd, &len, sizeof(len)); } + if (out!=-1) { out=write(ref->fd, path, len); } + if (out!=-1) { out=write(ref->fd, &flags, sizeof(flags)); } + if (out==-1) { + fprintf(stderr, "Error writing open to log file [%s]: %s\n", ref->logPath, strerror(errno)); + closeLog(db); + } + } + } +} + +//close-arg-data: +void SRRecClose(sqlite3 *db) { + if (!srr_enabled) return; + if (db) { + SRRLogRef *ref = getLog(db); + if (ref) { + SRRCommand code = SRRClose; + struct timeval tv; + size_t out; + + gettimeofday(&tv, NULL); + out = write(ref->fd, &tv, sizeof(tv)); + if (out!=-1) { out = write(ref->fd, &code, sizeof(SRRCommand)); } + if (out!=-1) { out = write(ref->fd, &(ref->connection), sizeof(ref->connection)); } + if (out==-1) { + fprintf(stderr, "Error writing close to log file [%s]: %s\n", ref->logPath, strerror(errno)); + } + closeLog(db); + } + } +} + +// exec-arg-data: +void SRRecExec(sqlite3 *db, const char *sql) { + if (!srr_enabled) return; + if (db) { + SRRLogRef *ref = getLog(db); + if (ref) { + if (ref->depth == 0) { + SRRCommand code = SRRExec; + int len = strlen(sql); + struct timeval tv; + size_t out; + + ref->depth = 1; + gettimeofday(&tv, NULL); + out = write(ref->fd, &tv, sizeof(tv)); + if (out!=-1) { out = write(ref->fd, &code, sizeof(SRRCommand)); } + if (out!=-1) { out = write(ref->fd, &(ref->connection), sizeof(ref->connection)); } + if (out!=-1) { out = write(ref->fd, &len, sizeof(len)); } + if (out!=-1) { out = write(ref->fd, sql, len); } + if (out==-1) { + fprintf(stderr, "Error writing exec to log file [%s]: %s\n", ref->logPath, strerror(errno)); + closeLog(db); + } + } else { + ref->depth ++; + } + } + } +} + +void SRRecExecEnd(sqlite3 *db) { + if (!srr_enabled) return; + if (db) { + SRRLogRef *ref = getLog(db); + if (ref) { + ref->depth --; + } + } +} + +// prep-arg-data: +void _SRRecPrepare(sqlite3 *db, const char *sql, int nBytes, int saveSql, sqlite3_stmt *pStmt) { + if (!srr_enabled) return; + if ((db!=NULL)&&(pStmt!=NULL)) { + SRRLogRef *ref = getLog(db); + if (ref && (ref->depth == 0)) { + SRRCommand code = SRRPrepare; + struct timeval tv; + size_t out; + int sqlLen = nBytes; + + if (sqlLen == -1) { + sqlLen = strlen(sql); + } + + gettimeofday(&tv, NULL); + out = write(ref->fd, &tv, sizeof(tv)); + if (out!=-1) { out = write(ref->fd, &code, sizeof(SRRCommand)); } + if (out!=-1) { out = write(ref->fd, &(ref->connection), sizeof(ref->connection)); } + if (out!=-1) { out = write(ref->fd, &sqlLen, sizeof(sqlLen)); } + if (out!=-1) { out = write(ref->fd, sql, sqlLen); } + if (out!=-1) { out = write(ref->fd, &saveSql, sizeof(saveSql)); } + if (out!=-1) { + int64_t stmtInt = (int64_t)((intptr_t)(pStmt)); + out = write(ref->fd, &stmtInt, sizeof(int64_t)); + } + if (out==-1) { + fprintf(stderr, "Error writing prepare to log file [%s]: %s\n", ref->logPath, strerror(errno)); + closeLog(db); + } + } + } +} + +//step-arg-data: +void SRRecStep(sqlite3_stmt *pStmt) { + if (!srr_enabled) return; + if(pStmt!=NULL) { + Vdbe *v = (Vdbe *)pStmt; + SRRLogRef *ref = getLog(v->db); + if (ref) { + if (ref->depth == 0) { + SRRCommand code = SRRStep; + struct timeval tv; + size_t out; + + ref->depth = 1; + gettimeofday(&tv, NULL); + out = write(ref->fd, &tv, sizeof(tv)); + if (out!=-1) { out = write(ref->fd, &code, sizeof(SRRCommand)); } + if (out!=-1) { + int64_t stmtInt = (int64_t)((intptr_t)(pStmt)); + out = write(ref->fd, &stmtInt, sizeof(int64_t)); + } + if (out==-1) { + fprintf(stderr, "Error writing step to log file [%s]: %s\n", ref->logPath, strerror(errno)); + closeLog(ref->db); + } + } else { + ref->depth ++; + } + } + } +} + +void SRRecStepEnd(sqlite3_stmt *pStmt) { + if (!srr_enabled) return; + if(pStmt!=NULL) { + Vdbe *v = (Vdbe *)pStmt; + SRRLogRef *ref = getLog(v->db); + if (ref) { + ref->depth --; + } + } +} + +// reset-arg-data: +void SRRecReset(sqlite3_stmt *pStmt) { + if (!srr_enabled) return; + if(pStmt!=NULL) { + Vdbe *v = (Vdbe *)pStmt; + SRRLogRef *ref = getLog(v->db); + if (ref && (ref->depth == 0)) { + SRRCommand code = SRRReset; + struct timeval tv; + size_t out; + + gettimeofday(&tv, NULL); + out = write(ref->fd, &tv, sizeof(tv)); + if (out!=-1) { out = write(ref->fd, &code, sizeof(SRRCommand)); } + if (out!=-1) { + int64_t stmtInt = (int64_t)((intptr_t)(pStmt)); + out = write(ref->fd, &stmtInt, sizeof(int64_t)); + } + if (out==-1) { + fprintf(stderr, "Error writing reset to log file [%s]: %s\n", ref->logPath, strerror(errno)); + closeLog(ref->db); + } + } + } +} + +// finalize-arg-data: +void SRRecFinalize(sqlite3_stmt *pStmt) { + if (!srr_enabled) return; + if(pStmt!=NULL) { + Vdbe *v = (Vdbe *)pStmt; + SRRLogRef *ref = getLog(v->db); + if (ref && (ref->depth == 0)) { + SRRCommand code = SRRFinalize; + struct timeval tv; + size_t out; + + gettimeofday(&tv, NULL); + out = write(ref->fd, &tv, sizeof(tv)); + if (out!=-1) { out = write(ref->fd, &code, sizeof(SRRCommand)); } + if (out!=-1) { + int64_t stmtInt = (int64_t)((intptr_t)(pStmt)); + out = write(ref->fd, &stmtInt, sizeof(int64_t)); + } + if (out==-1) { + fprintf(stderr, "Error writing finalize to log file [%s]: %s\n", ref->logPath, strerror(errno)); + closeLog(ref->db); + } + } + } +} + +// bind-text-arg-data: +void SRRecBindText(sqlite3_stmt *pStmt, int i, const char *zData, int64_t nData) { + if (!srr_enabled) return; + if(pStmt!=NULL) { + Vdbe *v = (Vdbe *)pStmt; + SRRLogRef *ref = getLog(v->db); + if (ref && (ref->depth == 0)) { + SRRCommand code = SRRBindText; + struct timeval tv; + size_t out; + int64_t textLen = nData; + if (textLen == -1) { + textLen = strlen(zData); + } + + gettimeofday(&tv, NULL); + out = write(ref->fd, &tv, sizeof(tv)); + if (out!=-1) { out = write(ref->fd, &code, sizeof(SRRCommand)); } + if (out!=-1) { + int64_t stmtInt = (int64_t)((intptr_t)(pStmt)); + out = write(ref->fd, &stmtInt, sizeof(int64_t)); + } + if (out!=-1) { out = write(ref->fd, &i, sizeof(i)); } + if (out!=-1) { out = write(ref->fd, &textLen, sizeof(textLen)); } + if (out!=-1) { out = write(ref->fd, zData, textLen); } + if (out==-1) { + fprintf(stderr, "Error writing bind text to log file [%s]: %s\n", ref->logPath, strerror(errno)); + closeLog(ref->db); + } + } + } +} + +// bind-blob-arg-data: [] +void SRRecBindBlob(sqlite3_stmt *pStmt, int i, const char *zData, int64_t nData) { + if (!srr_enabled) return; + if(pStmt!=NULL) { + Vdbe *v = (Vdbe *)pStmt; + SRRLogRef *ref = getLog(v->db); + if (ref && (ref->depth == 0)) { + SRRCommand code = SRRBindBlob; + struct timeval tv; + size_t out; + + gettimeofday(&tv, NULL); + out = write(ref->fd, &tv, sizeof(tv)); + if (out!=-1) { out = write(ref->fd, &code, sizeof(SRRCommand)); } + if (out!=-1) { + int64_t stmtInt = (int64_t)((intptr_t)(pStmt)); + out = write(ref->fd, &stmtInt, sizeof(int64_t)); + } + if (out!=-1) { out = write(ref->fd, &i, sizeof(i)); } + if (zData == NULL) { + int64_t negNData = -nData; + if (out!=-1) { out = write(ref->fd, &negNData, sizeof(negNData)); } + } else { + if (out!=-1) { out = write(ref->fd, &nData, sizeof(nData)); } + if (out!=-1) { out = write(ref->fd, zData, nData); } + } + if (out==-1) { + fprintf(stderr, "Error writing bind blob to log file [%s]: %s\n", ref->logPath, strerror(errno)); + closeLog(ref->db); + } + } + } +} + +// bind-double-arg-data: +void SRRecBindDouble(sqlite3_stmt *pStmt, int i, double value) { + if (!srr_enabled) return; + if(pStmt!=NULL) { + Vdbe *v = (Vdbe *)pStmt; + SRRLogRef *ref = getLog(v->db); + if (ref && (ref->depth == 0)) { + SRRCommand code = SRRBindDouble; + struct timeval tv; + size_t out; + + gettimeofday(&tv, NULL); + out = write(ref->fd, &tv, sizeof(tv)); + if (out!=-1) { out = write(ref->fd, &code, sizeof(SRRCommand)); } + if (out!=-1) { + int64_t stmtInt = (int64_t)((intptr_t)(pStmt)); + out = write(ref->fd, &stmtInt, sizeof(int64_t)); + } + if (out!=-1) { out = write(ref->fd, &i, sizeof(i)); } + if (out!=-1) { out = write(ref->fd, &value, sizeof(value)); } + if (out==-1) { + fprintf(stderr, "Error writing bind double to log file [%s]: %s\n", ref->logPath, strerror(errno)); + closeLog(ref->db); + } + } + } +} + +// bind-int-arg-data: +void SRRecBindInt64(sqlite3_stmt *pStmt, int i, int64_t value) { + if (!srr_enabled) return; + if(pStmt!=NULL) { + Vdbe *v = (Vdbe *)pStmt; + SRRLogRef *ref = getLog(v->db); + if (ref && (ref->depth == 0)) { + SRRCommand code = SRRBindInt; + struct timeval tv; + size_t out; + + gettimeofday(&tv, NULL); + out = write(ref->fd, &tv, sizeof(tv)); + if (out!=-1) { out = write(ref->fd, &code, sizeof(SRRCommand)); } + if (out!=-1) { + int64_t stmtInt = (int64_t)((intptr_t)(pStmt)); + out = write(ref->fd, &stmtInt, sizeof(int64_t)); + } + if (out!=-1) { out = write(ref->fd, &i, sizeof(i)); } + if (out!=-1) { out = write(ref->fd, &value, sizeof(value)); } + if (out==-1) { + fprintf(stderr, "Error writing bind int to log file [%s]: %s\n", ref->logPath, strerror(errno)); + closeLog(ref->db); + } + } + } +} + +// bind-null-arg-data: +void SRRecBindNull(sqlite3_stmt *pStmt, int i) { + if (!srr_enabled) return; + if(pStmt!=NULL) { + Vdbe *v = (Vdbe *)pStmt; + SRRLogRef *ref = getLog(v->db); + if (ref && (ref->depth == 0)) { + SRRCommand code = SRRBindNull; + struct timeval tv; + size_t out; + + gettimeofday(&tv, NULL); + out = write(ref->fd, &tv, sizeof(tv)); + if (out!=-1) { out = write(ref->fd, &code, sizeof(SRRCommand)); } + if (out!=-1) { + int64_t stmtInt = (int64_t)((intptr_t)(pStmt)); + out = write(ref->fd, &stmtInt, sizeof(int64_t)); + } + if (out!=-1) { out = write(ref->fd, &i, sizeof(i)); } + if (out==-1) { + fprintf(stderr, "Error writing bind null to log file [%s]: %s\n", ref->logPath, strerror(errno)); + closeLog(ref->db); + } + } + } +} + +// bind-value-arg-data: ??? +void SRRecBindValue(sqlite3_stmt *pStmt, int i, const sqlite3_value *value) { + if (!srr_enabled) return; + if(pStmt!=NULL) { + Vdbe *v = (Vdbe *)pStmt; + SRRLogRef *ref = getLog(v->db); + if (ref && (ref->depth == 0)) { + fprintf(stderr, "SRRecBindValue(sqlite3_bind_value) is not yet supported, closing [%s]: %s\n", ref->logPath, strerror(errno)); + closeLog(ref->db); + } + } +} + +// bind-clear-arg-data: +void SRRecClearBindings(sqlite3_stmt *pStmt) { + if (!srr_enabled) return; + if(pStmt!=NULL) { + Vdbe *v = (Vdbe *)pStmt; + SRRLogRef *ref = getLog(v->db); + if (ref && (ref->depth == 0)) { + SRRCommand code = SRRBindClear; + struct timeval tv; + size_t out; + + gettimeofday(&tv, NULL); + out = write(ref->fd, &tv, sizeof(tv)); + if (out!=-1) { out = write(ref->fd, &code, sizeof(SRRCommand)); } + if (out!=-1) { + int64_t stmtInt = (int64_t)((intptr_t)(pStmt)); + out = write(ref->fd, &stmtInt, sizeof(int64_t)); + } + if (out==-1) { + fprintf(stderr, "Error writing clear bindings to log file [%s]: %s\n", ref->logPath, strerror(errno)); + closeLog(ref->db); + } + } + } +} + +#endif /* SQLITE_ENABLE_SQLRR */ diff --git a/ext/sqlrr/sqlrr.h b/ext/sqlrr/sqlrr.h new file mode 100644 index 0000000000..2dfbe07a67 --- /dev/null +++ b/ext/sqlrr/sqlrr.h @@ -0,0 +1,61 @@ +/* + * sqlrr.h + */ + +#ifndef _SQLRR_H_ +#define _SQLRR_H_ + +/* +** Header constants +*/ +#define SRR_FILE_SIGNATURE "SQLRR" +#define SRR_FILE_SIGNATURE_LEN 5 +#define SRR_FILE_VERSION 0x1 +#define SRR_FILE_VERSION_LEN 1 + +#if defined(SQLITE_ENABLE_SQLRR) + +#include "sqlite3.h" +#include + +#define SRRecOpen(A,B,C) if(!rc){_SRRecOpen(A,B,C);} +#define SRRecPrepare(A,B,C,D,E) if(!rc){_SRRecPrepare(A,B,C,D,E);} + +typedef enum { + SRROpen = 0, + SRRClose = 1, + SRRExec = 8, + SRRBindText = 16, + SRRBindBlob = 17, + SRRBindDouble = 18, + SRRBindInt = 19, + SRRBindNull = 20, + SRRBindValue = 21, + SRRBindClear = 22, + SRRPrepare = 32, + SRRStep = 33, + SRRReset = 34, + SRRFinalize = 35 +} SRRCommand; + +extern void SQLiteReplayRecorder(int flag); +extern void _SRRecOpen(sqlite3 *db, const char *path, int flags); +extern void SRRecClose(sqlite3 *db); +extern void SRRecExec(sqlite3 *db, const char *sql); +extern void SRRecExecEnd(sqlite3 *db); +extern void _SRRecPrepare(sqlite3 *db, const char *sql, int nBytes, int saveSql, sqlite3_stmt *stmt); +extern void SRRecStep(sqlite3_stmt *pStmt); +extern void SRRecStepEnd(sqlite3_stmt *pStmt); +extern void SRRecReset(sqlite3_stmt *pStmt); +extern void SRRecFinalize(sqlite3_stmt *pStmt); +extern void SRRecBindText(sqlite3_stmt *pStmt, int i, const char *zData, int64_t nData); +extern void SRRecBindBlob(sqlite3_stmt *pStmt, int i, const char *zData, int64_t nData); +extern void SRRecBindDouble(sqlite3_stmt *pStmt, int i, double value); +extern void SRRecBindInt64(sqlite3_stmt *pStmt, int i, int64_t value); +extern void SRRecBindNull(sqlite3_stmt *pStmt, int i); +extern void SRRecBindValue(sqlite3_stmt *pStmt, int i, const sqlite3_value *value); +extern void SRRecClearBindings(sqlite3_stmt *pStmt); + +#endif /* defined(SQLITE_ENABLE_SQLRR) */ + +#endif /* _SQLRR_H_ */ \ No newline at end of file diff --git a/manifest b/manifest index b37336fcee..f916af6f5b 100644 --- a/manifest +++ b/manifest @@ -1,7 +1,7 @@ -C Fix\scompiler\swarnings\son\sMSVC\sbuild. -D 2009-11-03T19:42:31 +C Merging\sin\sOSX\scustomizations:\n\nA\sext/sqlrr/sqlrr.[ch]\n--\sadd\ssql\srecording/replay\ssupport\s(very\sexperimental\sextension\s--\spre\salpha)\n\nM\sext/rtree/rtree.c\n--\sfix\scast\scompiler\swarning\n\nM\ssrc/func.c\n--\ssubstring\sbackward\scompatiblity\s\s\n\nM\ssrc/legacy.c\n--\sreplay\srecorder\scalls\n\nM\ssrc/main.c\n--\sif\sSQLITE_ENABLE_AUTO_PROFILE,\scall\ssqlite3_profile\swhen\senv(SQLITE_AUTO_PROFILE)\s\s\s\n--\sreplay\srecorder\scalls\n\nM\ssrc/mem1.c\n--\suse\scustom\smalloc\szone\son\ssingle\score\ssystems\n\nM\ssrc/os_unix.c\n--\shostid\svia\sgethostuuid\n--\sread-only\sfile\ssystem\ssupport\sfor\sproxy\slocking\n--\simproved\shandling\sof\ssimulated\sshared\slock\son\sAFP/SMB\son\s64\sbit\ssystems\n--\sdisable\swhole-file-lock\sNFS\sfile\slocking\n--\sfix\sfor\sdowngrading\sfrom\sexclusive\sto\sshared\slock\son\sNFS\n--\sproxy\slock\sbreaking\s&\srecreating\slocal\slock\sfiles\n--\ssupport\sSQLITE_OPEN_AUTOPROXY\sflag\n--\sonly\swrite\s1st\sbyte\sinto\snew\szero-length\sfiles\sif\sfs\sis\sDOS\n--\scache\sfs\stype\sinfo\sin\sunixFile\sstruct\n--\sforce\suse\sof\sfsync()\seven\sif\sfdatasync()\sis\savailable\n--\sremove\sflock\slocking\sstyle\ssupport\sfor\sSMB\n--\sreplace\sstrcpy\swith\sstrlcpy\n\nM\ssrc/prepare.c\n--\sreplay\srecorder\scalls\n\nM\ssrc/sqlite.h.in\n--\sadded\sSQLITE_OPEN_AUTOPROXY\n\nM\ssrc/sqliteInt.h\n--\sinclude\sreplay\srecorder\sheader\n\nM\ssrc/vdbeapi.c\n--\schecking\sfor\sNULL\sstatement\sfor\s\s\n--\sreplay\srecorder\scalls +D 2009-11-03T22:34:36 F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0 -F Makefile.in a77dfde96ad86aafd3f71651a4333a104debe86a +F Makefile.in 3400e494a10756968f8fcd8c3d553d279a063436 F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654 F Makefile.vxworks 10010ddbf52e2503c7c49c7c0b7c7a096f8638a6 F README cd04a36fbc7ea56932a4052d7d0b7f09f27c33d6 @@ -72,7 +72,7 @@ F ext/icu/README.txt 3b130aa66e7a681136f6add198b076a2f90d1e33 F ext/icu/icu.c 12e763d288d23b5a49de37caa30737b971a2f1e2 F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37 F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 -F ext/rtree/rtree.c 5275d8f851c366f4a01e8a0c63aa0af492567f28 +F ext/rtree/rtree.c 375a762b17e307d1f9574b2563a0144ced58b021 F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e F ext/rtree/rtree1.test 207041aba07fdcdd93aa797a745839d305181857 F ext/rtree/rtree2.test 9ac9d28fa948779df66916c67a5dcf9704c3cb74 @@ -84,6 +84,9 @@ F ext/rtree/rtree_perf.tcl 0fabb6d5c48cb8024e042ce5d4bb88998b6ec1cb F ext/rtree/rtree_util.tcl ee0a0311eb12175319d78bfb37302320496cee6e F ext/rtree/tkt3363.test 6662237ea75bb431cd5d262dfc9535e1023315fc F ext/rtree/viewrtree.tcl 09526398dae87a5a87c5aac2b3854dbaf8376869 +F ext/sqlrr/README.txt 4239030e73023e72a2e727808cd433577d5bf730 +F ext/sqlrr/sqlrr.c 8d1e6571cd6a6beabdb5bcdfe3a0e723b914db41 +F ext/sqlrr/sqlrr.h 09e4f8929ad9bc2638732c0cc0db5eef8c417824 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F main.mk d588eab5d51b0bfe924a1cccdfdd2cbb4cbe40b4 @@ -117,20 +120,20 @@ F src/delete.c 308e300d599d2d11b838687e2cf7309d42f29a1a F src/expr.c 501269f7598cd7f39664c2ed6c360a6d48956396 F src/fault.c dc88c821842157460750d2d61a8a8b4197d047ff F src/fkey.c 41219cba186bcf0a053e42327dfa23aaba4f834a -F src/func.c e536218d193b8d326aab91120bc4c6f28aa2b606 +F src/func.c 1ddc1c93a0bb350977361e60e6067b3827ea9e81 F src/global.c 271952d199a8cc59d4ce840b3bbbfd2f30c8ba32 F src/hash.c ebcaa921ffd9d86f7ea5ae16a0a29d1c871130a7 F src/hash.h 35b216c13343d0b4f87d9f21969ac55ad72174e1 F src/hwtime.h 4a1d45f4cae1f402ea19686acf24acf4f0cb53cb F src/insert.c 2fe2ef7bd03d6e0120e4525727c4ae7de5a2d571 F src/journal.c e00df0c0da8413ab6e1bb7d7cab5665d4a9000d0 -F src/legacy.c 303b4ffcf1ae652fcf5ef635846c563c254564f6 +F src/legacy.c 3ec1880541904c6135fcca548a272a6301bdc951 F src/lempar.c 7f026423f4d71d989e719a743f98a1cbd4e6d99e F src/loadext.c 0e88a335665db0b2fb4cece3e49dcb65d832635a -F src/main.c 5e12af46a6e6149b755830f9c035be6eb77995a6 +F src/main.c a1c50e3bf1c07f02542de6390f75638e2621d8be F src/malloc.c 685561d2f1602042e349aea5d7a88c3f10a63454 F src/mem0.c f2f84062d1f35814d6535c9f9e33de3bfb3b132c -F src/mem1.c e6d5c23941288df8191b8a98c28e3f57771e2270 +F src/mem1.c b46d8dab40d9fdcde5048d62753f6787acebb806 F src/mem2.c d02bd6a5b34f2d59012a852615621939d9c09548 F src/mem3.c 805ab642adfafa171781a5d8ab112119dfaef118 F src/mem5.c 4837b795ebdecc0cfe1522cd0c8b2c5d84ea490d @@ -146,7 +149,7 @@ F src/os.c 8d62d8d98ad7909cb0dd294c1e5f3835c887ccb6 F src/os.h 00a1334a4eecee7f7bef79ac606b88d325119f21 F src/os_common.h 8c61457df58f1a4bd5f5adc3e90e01b37bf7afbc F src/os_os2.c bed77dc26e3a95ce4a204936b9a1ca6fe612fcc5 -F src/os_unix.c a4b4ea928ce31ed34cb8f90ed36a35df19312fad +F src/os_unix.c cb7a1418fb41307a9fc97b9e4e772e36d4623a9f F src/os_win.c 5ffab20249a61e0625f869efe157fa009747039b F src/pager.c 729f73feeb33355ae1f0982a74f112ce190c74aa F src/pager.h 11852d044c86cf5a9d6e34171fb0c4fcf1f6265f @@ -154,22 +157,22 @@ F src/parse.y 0204f0dfe8974dc2a0d46eb9ab98a433a1f963d6 F src/pcache.c c92ffd4f3e1279b3766854c6d18b5bf4aac0d1fa F src/pcache.h 435ef324197f79391f9c92b71d7f92b548ad7a36 F src/pcache1.c 211295a9ff6a5b30f1ca50516731a5cf3e9bf82c -F src/pragma.c c25d0d15dd0bbc5ec34e9760629353358705a447 -F src/prepare.c 665d52303135833c53b9be03e68533e249e1de54 +F src/pragma.c 840143b9e0cadfbd744a24b0b918a057f0aba7ff +F src/prepare.c 6ea9df8369f12f50b3ca92fdd0bd8f2037c48115 F src/printf.c 508a1c59433353552b6553cba175eaa7331f8fc1 F src/random.c 676b9d7ac820fe81e6fb2394ac8c10cff7f38628 F src/resolve.c 1166b41dd6b0859bf31a26a8855bcddc819e6c46 F src/rowset.c c64dafba1f9fd876836c8db8682966b9d197eb1f F src/select.c cbe366a0ce114856e66f5daf0f848d7c48a88298 F src/shell.c f66531a57fff927f95c98d99c28237d88e400c86 -F src/sqlite.h.in 9106176cf206c36f01f8b761ba62671818bbe6ff +F src/sqlite.h.in b000e2bc16eb429b4cd8f47faf0047094967a0e9 F src/sqlite3ext.h 1db7d63ab5de4b3e6b83dd03d1a4e64fef6d2a17 F src/sqliteInt.h b0661039d644645e5c5e60e179cb9c3f502805a0 F src/sqliteLimit.h 38b2fffcd01faeaeaadea71b2b47695a81580c8b F src/status.c 237b193efae0cf6ac3f0817a208de6c6c6ef6d76 F src/table.c cc86ad3d6ad54df7c63a3e807b5783c90411a08d F src/tclsqlite.c c2c4047177213baf485d4401c7dbb30a3c2ba322 -F src/test1.c 2232a39540a6b72f3be8f84b34d1ca0714f92aee +F src/test1.c 9a4ee438435acfcf6418991a33e08bfe99e602bf F src/test2.c 0de743ec8890ca4f09e0bce5d6d5a681f5957fec F src/test3.c 2445c2beb5e7a0c91fd8136dc1339ec369a24898 F src/test4.c b5fd530f02a6a0dbffb23be202168a690985dedd @@ -180,7 +183,7 @@ F src/test8.c 34719910286a0a6ca233f10ba66558be938494dd F src/test9.c 963d380922f25c1c323712d05db01b19197ee6f7 F src/test_async.c 731d23f953ece5bf40ce87810cfb7607218953c5 F src/test_autoext.c f53b0cdf7bf5f08100009572a5d65cdb540bd0ad -F src/test_backup.c 1384a18985a5a2d275c2662e48473bf1542ebd08 +F src/test_backup.c c775b759a7aedd48e9e6651b44b16e54ad11218c F src/test_btree.c 5adbba9b138988a3cf4d3b5424dbc7c85651da02 F src/test_config.c 4ac1e6257dcf926a71b7934410b71c5c326e68f2 F src/test_devsym.c 9f4bc2551e267ce7aeda195f3897d0f30c5228f4 @@ -208,7 +211,7 @@ F src/vacuum.c 48e1282bbd5eac4b461587c51658378658c00770 F src/vdbe.c a435ffcf6bfc7f14eb40998062ccbd7dfa482319 F src/vdbe.h 449323a21c02226790acb6189dae78af17b92b78 F src/vdbeInt.h aa08465efa812288688a72613a2584079d294c62 -F src/vdbeapi.c 44b5f387459d5faa158aa8d3a26967f0c8596efd +F src/vdbeapi.c de488c6311de23a68c1c4499eb76144f4b2ec8dd F src/vdbeaux.c 7cb0daeb128fff205183ce3efb10a94ed75ae705 F src/vdbeblob.c 9bfaeab22e261a6a7b6df04e7faaf7d6dfdbef5a F src/vdbemem.c 7055a2941a7802094f4704cedc7a28cc88a23749 @@ -231,7 +234,7 @@ F test/async2.test bf5e2ca2c96763b4cba3d016249ad7259a5603b6 F test/async3.test 93edaa9122f498e56ea98c36c72abc407f4fb11e F test/async4.test aafa6328c559d3e4bb587de770cbdecfca06f0da F test/async5.test f3592d79c84d6e83a5f50d3fd500445f7d97dfdf -F test/attach.test 1d1be27b9e4c654f9bb14d011a4a87753c0b197a +F test/attach.test 5519fbeb99b77d342642e9b990a644149c3ae9d1 F test/attach2.test a295d2d7061adcee5884ef4a93c7c96a82765437 F test/attach3.test bd9830bc3a0d22ed1310c9bff6896927937017dc F test/attachmalloc.test cf8cf17d183de357b1147a9baacbdfc85b940b61 @@ -453,8 +456,9 @@ F test/lock4.test f4f36271aa5ae1da449646bf43c7341f6b2b4c4e F test/lock5.test 6b1f78f09ad1522843dad571b76b321e6f439bf7 F test/lock6.test 862aa71e97b288d6b3f92ba3313f51bd0b003776 F test/lock7.test 64006c84c1c616657e237c7ad6532b765611cf64 +F test/lock_proxy.test 364b6c6220c023b57fc37eb52ec97c1f5c682c47 F test/lookaside.test 1dd350dc6dff015c47c07fcc5a727a72fc5bae02 -F test/main.test 347ab987f16167858781383427476b33dc69fdb7 +F test/main.test 0fdc4b9b1510d522f2ff39d02569bffb2a8132aa F test/make-where7.tcl 05c16b5d4f5d6512881dfec560cb793915932ef9 F test/malloc.test d23580e15c33ee0353717129421b077541e910dc F test/malloc3.test 4bc57f850b212f706f3e1b37c4eced1d5a727cd1 @@ -476,11 +480,11 @@ F test/mallocH.test 79b65aed612c9b3ed2dcdaa727c85895fd1bfbdb F test/mallocI.test e3ea401904d010cb7c1e4b2ee8803f4a9f5b999d F test/mallocJ.test b5d1839da331d96223e5f458856f8ffe1366f62e F test/mallocK.test d79968641d1b70d88f6c01bdb9a7eb4a55582cc9 -F test/malloc_common.tcl 984baeb6c6b185e798827d1187d426acc2bc4962 +F test/malloc_common.tcl a0b9f24aff8987f965a0d3e91cecdbb8b67e7916 F test/manydb.test b3d3bc4c25657e7f68d157f031eb4db7b3df0d3c -F test/memdb.test 0825155b2290e900264daaaf0334b6dfe69ea498 +F test/memdb.test f773146f66ee2c635854a8264317f39a6cc3e18c F test/memleak.test d2d2a1ff7105d32dc3fdf691458cf6cba58c7217 -F test/memsubsys1.test fd8a33046b6e758e3eb93747dc4eec21fe56bf64 +F test/memsubsys1.test d7d3266a56e69bb3e6b8dd64822fcaa1c4f3e399 F test/memsubsys2.test 72a731225997ad5e8df89fdbeae9224616b6aecc F test/minmax.test 722d80816f7e096bf2c04f4111f1a6c1ba65453d F test/minmax2.test 33504c01a03bd99226144e4b03f7631a274d66e0 @@ -506,12 +510,12 @@ F test/pagesize.test 0d9ff3fedfce6e5ffe8fa7aca9b6d3433a2e843b F test/pcache.test eebc4420b37cb07733ae9b6e99c9da7c40dd6d58 F test/pcache2.test 0d85f2ab6963aee28c671d4c71bec038c00a1d16 F test/permutations.test 1ce2874df8fec876d0b963c7a3ef61c4e9df8827 -F test/pragma.test 5aeb48a442dba3c3e8e38773b121371814ab3b17 +F test/pragma.test 648a1760925d7d39003b3fd29e1b050c983e7553 F test/pragma2.test 5364893491b9231dd170e3459bfc2e2342658b47 F test/printf.test 47e9e5bbec8509023479d54ceb71c9d05a95308a F test/progress.test 5b075c3c790c7b2a61419bc199db87aaf48b8301 F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc -F test/quick.test 12fdc7656b4d20a537a686fb223eb99b5fe54483 +F test/quick.test 5ac8be9487553d9e38433ad70d6906b87d558de3 F test/quote.test 215897dbe8de1a6f701265836d6601cc6ed103e6 F test/randexpr1.tcl 40dec52119ed3a2b8b2a773bce24b63a3a746459 F test/randexpr1.test 1084050991e9ba22c1c10edd8d84673b501cc25a @@ -575,7 +579,7 @@ F test/tclsqlite.test bf4227eb236a4c097aa7974a2bf7d3225acf34be F test/tempdb.test 1bf52da28a9c24e29717362a87722dff08feb72b F test/temptable.test f42121a0d29a62f00f93274464164177ab1cc24a F test/temptrigger.test b0273db072ce5f37cf19140ceb1f0d524bbe9f05 -F test/tester.tcl 2caf7980d7dbb99dab9507ae0646802bc4d12c79 +F test/tester.tcl 0fbba238815429131181774ba2b169e198838150 F test/thread001.test a3e6a7254d1cb057836cb3145b60c10bf5b7e60f F test/thread002.test afd20095e6e845b405df4f2c920cb93301ca69db F test/thread003.test b824d4f52b870ae39fc5bae4d8070eca73085dca @@ -745,7 +749,7 @@ F tool/lempar.c 01ca97f87610d1dac6d8cd96ab109ab1130e76dc F tool/mkkeywordhash.c 9216336085e7a7c226a35c0bd780239968f8304f F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 -F tool/mksqlite3c.tcl a7e87ce780cbf30782bca3fd1197e86f92ae6f24 +F tool/mksqlite3c.tcl 1ac077b7bf1cca4e4922b5e72a5e898274fb174b F tool/mksqlite3h.tcl eb100dce83f24b501b325b340f8b5eb8e5106b3b F tool/mksqlite3internalh.tcl 7b43894e21bcb1bb39e11547ce7e38a063357e87 F tool/omittest.tcl 27d6f6e3b1e95aeb26a1c140e6eb57771c6d794a @@ -761,7 +765,10 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f -P 27d8e684db4651155c4bcb8bb44bf1c692b8c48b -R 87d07c1d512d6da4f67b4891197ec4ad -U shane -Z 269e8905230698d3c4544fc066424d81 +P 01c4b5b84ec7ce589e20ea66e80011f092ab32f0 +R 320cf17d3a0d32e6b3087d1f5155c40f +T *branch * apple-osx +T *sym-apple-osx * +T -sym-trunk * +U adam +Z 409690f7ddbce583c414b998978aa931 diff --git a/manifest.uuid b/manifest.uuid index 7904d57167..f3c6cd9c49 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -01c4b5b84ec7ce589e20ea66e80011f092ab32f0 \ No newline at end of file +941a01eb868815f566539e9ab21f807d9e798e40 \ No newline at end of file diff --git a/src/func.c b/src/func.c index 2766fa3b1b..0d93b1a781 100644 --- a/src/func.c +++ b/src/func.c @@ -191,6 +191,9 @@ static void substrFunc( } } p1 = sqlite3_value_int(argv[1]); +#ifdef SQLITE_SUBSTR_COMPATIBILITY + if( p1==0 ) p1 = 1; /* */ +#endif if( argc==3 ){ p2 = sqlite3_value_int(argv[2]); if( p2<0 ){ diff --git a/src/legacy.c b/src/legacy.c index 73421032cb..c28ee95b58 100644 --- a/src/legacy.c +++ b/src/legacy.c @@ -18,6 +18,9 @@ */ #include "sqliteInt.h" +#ifdef SQLITE_ENABLE_SQLRR +# include "sqlrr.h" +#endif /* ** Execute SQL code. Return one of the SQLITE_ success/failure @@ -44,7 +47,9 @@ int sqlite3_exec( int callbackIsInit; /* True if callback data is initialized */ if( zSql==0 ) zSql = ""; - +#ifdef SQLITE_ENABLE_SQLRR + SRRecExec(db, zSql); +#endif sqlite3_mutex_enter(db->mutex); sqlite3Error(db, SQLITE_OK, 0); while( (rc==SQLITE_OK || (rc==SQLITE_SCHEMA && (++nRetry)<2)) && zSql[0] ){ @@ -125,6 +130,9 @@ int sqlite3_exec( exec_out: if( pStmt ) sqlite3VdbeFinalize((Vdbe *)pStmt); sqlite3DbFree(db, azCols); +#ifdef SQLITE_ENABLE_SQLRR + SRRecExecEnd(db); +#endif rc = sqlite3ApiExit(db, rc); if( rc!=SQLITE_OK && ALWAYS(rc==sqlite3_errcode(db)) && pzErrMsg ){ diff --git a/src/main.c b/src/main.c index a0e765db86..042a59a2f9 100644 --- a/src/main.c +++ b/src/main.c @@ -16,6 +16,9 @@ */ #include "sqliteInt.h" +#ifdef SQLITE_ENABLE_SQLRR +# include "sqlrr.h" +#endif #ifdef SQLITE_ENABLE_FTS3 # include "fts3.h" #endif @@ -702,6 +705,10 @@ int sqlite3_close(sqlite3 *db){ if( db->lookaside.bMalloced ){ sqlite3_free(db->lookaside.pStart); } +#ifdef SQLITE_ENABLE_SQLRR + SRRecClose(db); +#endif + sqlite3_free(db); return SQLITE_OK; } @@ -1513,6 +1520,12 @@ int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ } return oldLimit; } +#if defined(SQLITE_ENABLE_AUTO_PROFILE) +static void profile_sql(void *aux, const char *sql, uint64_t ns) { +#pragma unused(aux) + fprintf(stderr, "Query: %s\n Execution Time: %llu ms\n", sql, ns / 1000000); +} +#endif /* ** This routine does the work of opening a database on behalf of @@ -1743,7 +1756,19 @@ opendb_out: }else if( rc!=SQLITE_OK ){ db->magic = SQLITE_MAGIC_SICK; } +#if defined(SQLITE_ENABLE_AUTO_PROFILE) + if( db && !rc ){ + char *envprofile = getenv("SQLITE_AUTO_PROFILE"); + + if( envprofile!=NULL ){ + sqlite3_profile(db, profile_sql, NULL); + } + } +#endif *ppDb = db; +#ifdef SQLITE_ENABLE_SQLRR + SRRecOpen(db, zFilename, flags); +#endif return sqlite3ApiExit(0, rc); } diff --git a/src/mem1.c b/src/mem1.c index e3b886341c..14866165c4 100644 --- a/src/mem1.c +++ b/src/mem1.c @@ -28,6 +28,28 @@ */ #ifdef SQLITE_SYSTEM_MALLOC +#if (!defined(__APPLE__)) + +#define SQLITE_MALLOC(x) malloc(x) +#define SQLITE_FREE(x) free(x) +#define SQLITE_REALLOC(x,y) realloc((x),(y)) + + +#else + + +#include +#include +#include + +static malloc_zone_t* _sqliteZone_; + +#define SQLITE_MALLOC(x) malloc_zone_malloc(_sqliteZone_, (x)) +#define SQLITE_FREE(x) malloc_zone_free(_sqliteZone_, (x)); +#define SQLITE_REALLOC(x,y) malloc_zone_realloc(_sqliteZone_, (x), (y)) + +#endif + /* ** Like malloc(), but remember the size of the allocation ** so that we can find it later using sqlite3MemSize(). @@ -40,7 +62,7 @@ static void *sqlite3MemMalloc(int nByte){ sqlite3_int64 *p; assert( nByte>0 ); nByte = ROUND8(nByte); - p = malloc( nByte+8 ); + p = SQLITE_MALLOC( nByte+8 ); if( p ){ p[0] = nByte; p++; @@ -60,7 +82,7 @@ static void sqlite3MemFree(void *pPrior){ sqlite3_int64 *p = (sqlite3_int64*)pPrior; assert( pPrior!=0 ); p--; - free(p); + SQLITE_FREE(p); } /* @@ -79,7 +101,7 @@ static void *sqlite3MemRealloc(void *pPrior, int nByte){ nByte = ROUND8(nByte); p = (sqlite3_int64*)pPrior; p--; - p = realloc(p, nByte+8 ); + p = SQLITE_REALLOC(p, nByte+8 ); if( p ){ p[0] = nByte; p++; @@ -110,6 +132,37 @@ static int sqlite3MemRoundup(int n){ ** Initialize this module. */ static int sqlite3MemInit(void *NotUsed){ +#if defined(__APPLE__) + if (_sqliteZone_) { + return SQLITE_OK; + } + int cpuCount; + size_t len; + + len = sizeof(cpuCount); + sysctlbyname("hw.ncpu", &cpuCount, &len, NULL, 0); // one almost always wants to use "hw.activecpu" for MT decisions, but not here. + + if (cpuCount > 1) { + // defer MT decisions to system malloc + _sqliteZone_ = malloc_default_zone(); + } else { + // only 1 core, use our own zone to contention over global locks, + // e.g. we have our own dedicated locks + malloc_zone_t* newzone = malloc_create_zone(4096, 0); + malloc_set_zone_name(newzone, "Sqlite_Heap"); + + bool success; + do { + success = OSAtomicCompareAndSwapPtrBarrier(NULL, newzone, (void * volatile *)&_sqliteZone_); + } while (!_sqliteZone_); + + if (!success) { + // somebody registered a zone first + malloc_destroy_zone(newzone); + } + } +#endif + UNUSED_PARAMETER(NotUsed); return SQLITE_OK; } @@ -118,6 +171,17 @@ static int sqlite3MemInit(void *NotUsed){ ** Deinitialize this module. */ static void sqlite3MemShutdown(void *NotUsed){ +#if (0 && defined(__APPLE__)) + if (_sqliteZone_ && (_sqliteZone_ != malloc_default_zone())) { + malloc_zone_t* oldzone = _sqliteZone_; + + bool success = OSAtomicCompareAndSwapPtrBarrier(oldzone, NULL, (void * volatile *)&_sqliteZone_); + if (success) { + malloc_destroy_zone(oldzone); + } + } +#endif + UNUSED_PARAMETER(NotUsed); return; } @@ -142,4 +206,8 @@ void sqlite3MemSetDefault(void){ sqlite3_config(SQLITE_CONFIG_MALLOC, &defaultMethods); } +#undef SQLITE_MALLOC +#undef SQLITE_FREE +#undef SQLITE_REALLOC + #endif /* SQLITE_SYSTEM_MALLOC */ diff --git a/src/os_unix.c b/src/os_unix.c index b31fbab215..6ba26a6e91 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -132,6 +132,13 @@ # endif #endif /* SQLITE_ENABLE_LOCKING_STYLE */ +#define SQLITE_FSFLAGS_IS_READONLY 0x1 +#define SQLITE_FSFLAGS_IS_LOCAL 0x2 +#define SQLITE_FSFLAGS_IS_HFSPLUS 0x4 +#define SQLITE_FSFLAGS_IS_MSDOS 0x8 +#define SQLITE_FSFLAGS_INITIALIZED 0x10000 + + /* ** If we are to be thread-safe, include the pthreads header and define ** the SQLITE_UNIX_THREADS macro. @@ -199,6 +206,9 @@ struct unixFile { #if SQLITE_ENABLE_LOCKING_STYLE int openFlags; /* The flags specified at open() */ #endif +#if SQLITE_ENABLE_LOCKING_STYLE || defined(__APPLE__) + unsigned fsFlags; /* cached details from statfs() */ +#endif #if SQLITE_THREADSAFE && defined(__linux__) pthread_t tid; /* The thread that "owns" this unixFile */ #endif @@ -990,17 +1000,18 @@ static int findLockInfo( ** is a race condition such that another thread has already populated ** the first page of the database, no damage is done. */ - if( statbuf.st_size==0 ){ - rc = write(fd, "S", 1); - if( rc!=1 ){ - return SQLITE_IOERR; - } - rc = fstat(fd, &statbuf); - if( rc!=0 ){ - pFile->lastErrno = errno; - return SQLITE_IOERR; + if (( statbuf.st_size==0 ) && (0 != (pFile->fsFlags & SQLITE_FSFLAGS_IS_MSDOS))) { + rc = write(fd, "S", 1); + if( rc!=1 ){ + pFile->lastErrno = errno; + return SQLITE_IOERR; + } + rc = fstat(fd, &statbuf); + if( rc!=0 ){ + pFile->lastErrno = errno; + return SQLITE_IOERR; + } } - } #endif memset(&lockKey, 0, sizeof(lockKey)); @@ -1165,6 +1176,7 @@ static int unixCheckReservedLock(sqlite3_file *id, int *pResOut){ return rc; } +#ifdef SQLITE_ENABLE_NFS_RANGELOCK /* ** Perform a file locking operation on a range of bytes in a file. ** The "op" parameter should be one of F_RDLCK, F_WRLCK, or F_UNLCK. @@ -1221,6 +1233,7 @@ static int rangeLock(unixFile *pFile, int op, int *pErrcode){ return rc; } +#endif /* SQLITE_ENABLE_NFS_RANGELOCK */ /* ** Lock the file with the lock specified by parameter locktype - one ** of the following: @@ -1385,8 +1398,11 @@ static int unixLock(sqlite3_file *id, int locktype){ assert( pLock->locktype==0 ); /* Now get the read-lock */ - s = rangeLock(pFile, F_RDLCK, &tErrno); - + lock.l_start = SHARED_FIRST; + lock.l_len = SHARED_SIZE; + if( (s = fcntl(pFile->h, F_SETLK, &lock))==(-1) ){ + tErrno = errno; + } /* Drop the temporary PENDING lock */ lock.l_start = PENDING_BYTE; lock.l_len = 1L; @@ -1426,16 +1442,17 @@ static int unixLock(sqlite3_file *id, int locktype){ switch( locktype ){ case RESERVED_LOCK: lock.l_start = RESERVED_BYTE; - s = fcntl(pFile->h, F_SETLK, &lock); - tErrno = errno; break; case EXCLUSIVE_LOCK: - s = rangeLock(pFile, F_WRLCK, &tErrno); + lock.l_start = SHARED_FIRST; + lock.l_len = SHARED_SIZE; break; default: assert(0); } + s = fcntl(pFile->h, F_SETLK, &lock); if( s==(-1) ){ + tErrno = errno; rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); if( IS_LOCK_ERROR(rc) ){ pFile->lastErrno = tErrno; @@ -1525,13 +1542,19 @@ static void setPendingFd(unixFile *pFile){ ** ** If the locking level of the file descriptor is already at or below ** the requested locking level, this routine is a no-op. +** +** If handleNFSUnlock is true, then on downgrading an EXCLUSIVE_LOCK to SHARED +** the byte range is divided into 2 parts and the first part is unlocked then +** set to a read lock, then the other part is simply unlocked. This works +** around a bug in BSD NFS lockd (also seen on MacOSX 10.3+) that fails to +** remove the write lock on a region when a read lock is set. */ -static int unixUnlock(sqlite3_file *id, int locktype){ - unixFile *pFile = (unixFile*)id; /* The open file */ - struct unixLockInfo *pLock; /* Structure describing current lock state */ - struct flock lock; /* Information passed into fcntl() */ - int rc = SQLITE_OK; /* Return code from this interface */ - int h; /* The underlying file descriptor */ +static int _posixUnlock(sqlite3_file *id, int locktype, int handleNFSUnlock){ + unixFile *pFile = (unixFile*)id; + struct unixLockInfo *pLock; + struct flock lock; + int rc = SQLITE_OK; + int h; int tErrno; /* Error code from system call errors */ assert( pFile ); @@ -1570,14 +1593,68 @@ static int unixUnlock(sqlite3_file *id, int locktype){ pFile->inNormalWrite = 0; #endif - + /* downgrading to a shared lock on NFS involves clearing the write lock + ** before establishing the readlock - to avoid a race condition we downgrade + ** the lock in 2 blocks, so that part of the range will be covered by a + ** write lock until the rest is covered by a read lock: + ** 1: [WWWWW] + ** 2: [....W] + ** 3: [RRRRW] + ** 4: [RRRR.] + */ if( locktype==SHARED_LOCK ){ - if( rangeLock(pFile, F_RDLCK, &tErrno)==(-1) ){ - rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_RDLOCK); - if( IS_LOCK_ERROR(rc) ){ - pFile->lastErrno = tErrno; + if( handleNFSUnlock ){ + off_t divSize = SHARED_SIZE - 1; + + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = SHARED_FIRST; + lock.l_len = divSize; + if( fcntl(h, F_SETLK, &lock)==(-1) ){ + int tErrno = errno; + rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_UNLOCK); + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + goto end_unlock; + } + lock.l_type = F_RDLCK; + lock.l_whence = SEEK_SET; + lock.l_start = SHARED_FIRST; + lock.l_len = divSize; + if( fcntl(h, F_SETLK, &lock)==(-1) ){ + int tErrno = errno; + rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_RDLOCK); + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + goto end_unlock; + } + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = SHARED_FIRST+divSize; + lock.l_len = SHARED_SIZE-divSize; + if( fcntl(h, F_SETLK, &lock)==(-1) ){ + int tErrno = errno; + rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_UNLOCK); + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + goto end_unlock; + } + }else{ + lock.l_type = F_RDLCK; + lock.l_whence = SEEK_SET; + lock.l_start = SHARED_FIRST; + lock.l_len = SHARED_SIZE; + if( fcntl(h, F_SETLK, &lock)==(-1) ){ + int tErrno = errno; + rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_RDLOCK); + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + goto end_unlock; } - goto end_unlock; } } lock.l_type = F_UNLCK; @@ -1644,6 +1721,17 @@ end_unlock: 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 unixUnlock(sqlite3_file *id, int locktype){ + return _posixUnlock(id, locktype, 0); +} + /* ** This function performs the parts of the "close file" operation ** common to all locking schemes. It closes the directory and file @@ -2356,6 +2444,7 @@ static int semClose(sqlite3_file *id) { typedef struct afpLockingContext afpLockingContext; struct afpLockingContext { unsigned long long sharedByte; + int reserved; const char *dbPath; /* Name of the open file */ }; @@ -2432,9 +2521,14 @@ static int afpCheckReservedLock(sqlite3_file *id, int *pResOut){ assert( pFile ); afpLockingContext *context = (afpLockingContext *) pFile->lockingContext; + if( context->reserved ){ + *pResOut = 1; + return SQLITE_OK; + } + unixEnterMutex(); /* Because pFile->pLock is shared across threads */ /* Check if a thread in this process holds such a lock */ - if( pFile->locktype>SHARED_LOCK ){ + if( pFile->pLock->locktype>SHARED_LOCK ){ reserved = 1; } @@ -2456,6 +2550,7 @@ static int afpCheckReservedLock(sqlite3_file *id, int *pResOut){ } } + unixLeaveMutex(); OSTRACE4("TEST WR-LOCK %d %d %d\n", pFile->h, rc, reserved); *pResOut = reserved; @@ -2489,11 +2584,13 @@ static int afpCheckReservedLock(sqlite3_file *id, int *pResOut){ static int afpLock(sqlite3_file *id, int locktype){ int rc = SQLITE_OK; unixFile *pFile = (unixFile*)id; + struct unixLockInfo *pLock = pFile->pLock; afpLockingContext *context = (afpLockingContext *) pFile->lockingContext; assert( pFile ); - OSTRACE5("LOCK %d %s was %s pid=%d\n", pFile->h, - locktypeName(locktype), locktypeName(pFile->locktype), getpid()); + OSTRACE7("LOCK %d %s was %s(%s,%d) pid=%d\n", pFile->h, + locktypeName(locktype), locktypeName(pFile->locktype), + locktypeName(pLock->locktype), pLock->cnt , getpid()); /* If there is already a lock of this type or more restrictive on the ** unixFile, do nothing. Don't use the afp_end_lock: exit path, as @@ -2506,6 +2603,9 @@ static int afpLock(sqlite3_file *id, int locktype){ } /* Make sure the locking sequence is correct + ** (1) We never move from unlocked to anything higher than shared lock. + ** (2) SQLite never explicitly requests a pendig lock. + ** (3) A shared lock is always held when a reserve lock is requested. */ assert( pFile->locktype!=NO_LOCK || locktype==SHARED_LOCK ); assert( locktype!=PENDING_LOCK ); @@ -2522,7 +2622,33 @@ static int afpLock(sqlite3_file *id, int locktype){ unixLeaveMutex(); return rc; } - + pLock = pFile->pLock; + + /* If some thread using this PID has a lock via a different unixFile* + ** handle that precludes the requested lock, return BUSY. + */ + if( (pFile->locktype!=pLock->locktype && + (pLock->locktype>=PENDING_LOCK || locktype>SHARED_LOCK)) + ){ + rc = SQLITE_BUSY; + goto afp_end_lock; + } + + /* If a SHARED lock is requested, and some thread using this PID already + ** has a SHARED or RESERVED lock, then increment reference counts and + ** return SQLITE_OK. + */ + if( locktype==SHARED_LOCK && + (pLock->locktype==SHARED_LOCK || pLock->locktype==RESERVED_LOCK) ){ + assert( locktype==SHARED_LOCK ); + assert( pFile->locktype==0 ); + assert( pLock->cnt>0 ); + pFile->locktype = SHARED_LOCK; + pLock->cnt++; + pFile->pOpen->nLock++; + goto afp_end_lock; + } + /* A PENDING lock is needed before acquiring a SHARED lock and before ** acquiring an EXCLUSIVE lock. For the SHARED lock, the PENDING will ** be released. @@ -2542,13 +2668,17 @@ static int afpLock(sqlite3_file *id, int locktype){ ** operating system calls for the specified lock. */ if( locktype==SHARED_LOCK ){ - int lk, lrc1, lrc2; - int lrc1Errno = 0; + int lrc1, lrc2, lrc1Errno; + long lk, mask; + assert( pLock->cnt==0 ); + assert( pLock->locktype==0 ); + + mask = (sizeof(long)==8) ? LARGEST_INT64 : 0x7fffffff; /* 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); + lk = random(); + context->sharedByte = (lk & mask)%(SHARED_SIZE - 1); lrc1 = afpSetLock(context->dbPath, pFile, SHARED_FIRST+context->sharedByte, 1, 1); if( IS_LOCK_ERROR(lrc1) ){ @@ -2569,7 +2699,12 @@ static int afpLock(sqlite3_file *id, int locktype){ } else { pFile->locktype = SHARED_LOCK; pFile->pOpen->nLock++; + pLock->cnt = 1; } + }else if( locktype==EXCLUSIVE_LOCK && pLock->cnt>1 ){ + /* We are trying for an exclusive lock but another thread in this + ** same process is still holding a shared lock. */ + rc = SQLITE_BUSY; }else{ /* The request was for a RESERVED or EXCLUSIVE lock. It is ** assumed that there is a SHARED or greater lock on the file @@ -2580,6 +2715,9 @@ static int afpLock(sqlite3_file *id, int locktype){ if (locktype >= RESERVED_LOCK && pFile->locktype < RESERVED_LOCK) { /* Acquire a RESERVED lock */ failed = afpSetLock(context->dbPath, pFile, RESERVED_BYTE, 1,1); + if( !failed ){ + context->reserved = 1; + } } if (!failed && locktype == EXCLUSIVE_LOCK) { /* Acquire an EXCLUSIVE lock */ @@ -2613,8 +2751,10 @@ static int afpLock(sqlite3_file *id, int locktype){ if( rc==SQLITE_OK ){ pFile->locktype = locktype; + pLock->locktype = locktype; }else if( locktype==EXCLUSIVE_LOCK ){ pFile->locktype = PENDING_LOCK; + pLock->locktype = PENDING_LOCK; } afp_end_lock: @@ -2634,11 +2774,15 @@ afp_end_lock: static int afpUnlock(sqlite3_file *id, int locktype) { int rc = SQLITE_OK; unixFile *pFile = (unixFile*)id; - afpLockingContext *pCtx = (afpLockingContext *) pFile->lockingContext; - + struct unixLockInfo *pLock; + afpLockingContext *context = (afpLockingContext *) pFile->lockingContext; +#ifdef SQLITE_TEST + int h = pFile->h; +#endif + assert( pFile ); - OSTRACE5("UNLOCK %d %d was %d pid=%d\n", pFile->h, locktype, - pFile->locktype, getpid()); + OSTRACE7("UNLOCK %d %d was %d(%d,%d) pid=%d\n", pFile->h, locktype, + pFile->locktype, pFile->pLock->locktype, pFile->pLock->cnt, getpid()); assert( locktype<=SHARED_LOCK ); if( pFile->locktype<=locktype ){ @@ -2648,31 +2792,73 @@ static int afpUnlock(sqlite3_file *id, int locktype) { return SQLITE_MISUSE; } unixEnterMutex(); + pLock = pFile->pLock; + assert( pLock->cnt!=0 ); if( pFile->locktype>SHARED_LOCK ){ + assert( pLock->locktype==pFile->locktype ); + SimulateIOErrorBenign(1); + SimulateIOError( h=(-1) ) + SimulateIOErrorBenign(0); + +#ifndef NDEBUG + /* When reducing a lock such that other processes can start + ** reading the database file again, make sure that the + ** transaction counter was updated if any part of the database + ** file changed. If the transaction counter is not updated, + ** other connections to the same file might not realize that + ** the file has changed and hence might not know to flush their + ** cache. The use of a stale cache can lead to database corruption. + */ + assert( pFile->inNormalWrite==0 + || pFile->dbUpdate==0 + || pFile->transCntrChng==1 ); + pFile->inNormalWrite = 0; +#endif if( pFile->locktype==EXCLUSIVE_LOCK ){ - rc = afpSetLock(pCtx->dbPath, pFile, SHARED_FIRST, SHARED_SIZE, 0); + rc = afpSetLock(context->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 = afpSetLock(pCtx->dbPath, pFile, sharedLockByte, 1, 1); + int sharedLockByte = SHARED_FIRST+context->sharedByte; + rc = afpSetLock(context->dbPath, pFile, sharedLockByte, 1, 1); } } if( rc==SQLITE_OK && pFile->locktype>=PENDING_LOCK ){ - rc = afpSetLock(pCtx->dbPath, pFile, PENDING_BYTE, 1, 0); + rc = afpSetLock(context->dbPath, pFile, PENDING_BYTE, 1, 0); } - if( rc==SQLITE_OK && pFile->locktype>=RESERVED_LOCK ){ - rc = afpSetLock(pCtx->dbPath, pFile, RESERVED_BYTE, 1, 0); + if( rc==SQLITE_OK && pFile->locktype>=RESERVED_LOCK && context->reserved ){ + rc = afpSetLock(context->dbPath, pFile, RESERVED_BYTE, 1, 0); + if( !rc ){ + context->reserved = 0; + } + } + if( rc==SQLITE_OK && locktype==SHARED_LOCK){ + pLock->locktype = SHARED_LOCK; } }else if( locktype==NO_LOCK ){ - /* clear the shared lock */ - int sharedLockByte = SHARED_FIRST+pCtx->sharedByte; - rc = afpSetLock(pCtx->dbPath, pFile, sharedLockByte, 1, 0); + + /* Decrement the shared lock counter. Release the lock using an + ** OS call only when all threads in this same process have released + ** the lock. + */ + int sharedLockByte = SHARED_FIRST+context->sharedByte; + pLock->cnt--; + if( pLock->cnt==0 ){ + SimulateIOErrorBenign(1); + SimulateIOError( h=(-1) ) + SimulateIOErrorBenign(0); + rc = afpSetLock(context->dbPath, pFile, sharedLockByte, 1, 0); + pLock->locktype = NO_LOCK; + if( !rc ){ + pFile->locktype = NO_LOCK; + } + } } if( rc==SQLITE_OK ){ if( locktype==NO_LOCK ){ struct unixOpenCnt *pOpen = pFile->pOpen; + pOpen->nLock--; assert( pOpen->nLock>=0 ); if( pOpen->nLock==0 ){ @@ -2680,10 +2866,9 @@ static int afpUnlock(sqlite3_file *id, int locktype) { } } } + unixLeaveMutex(); - if( rc==SQLITE_OK ){ - pFile->locktype = locktype; - } + if( rc==SQLITE_OK ) pFile->locktype = locktype; return rc; } @@ -2691,6 +2876,7 @@ static int afpUnlock(sqlite3_file *id, int locktype) { ** Close a file & cleanup AFP specific locking context */ static int afpClose(sqlite3_file *id) { + int rc = SQLITE_OK; if( id ){ unixFile *pFile = (unixFile*)id; afpUnlock(id, NO_LOCK); @@ -2703,12 +2889,13 @@ static int afpClose(sqlite3_file *id) { */ setPendingFd(pFile); } + releaseLockInfo(pFile->pLock); releaseOpenCnt(pFile->pOpen); sqlite3_free(pFile->lockingContext); - closeUnixFile(id); + rc = closeUnixFile(id); unixLeaveMutex(); } - return SQLITE_OK; + return rc; } #endif /* defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE */ @@ -2721,6 +2908,29 @@ static int afpClose(sqlite3_file *id) { ********************* End of the AFP lock implementation ********************** ******************************************************************************/ +/****************************************************************************** +*************************** Begin NFS Locking ********************************/ + +#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE +/* + ** 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 nfsUnlock(sqlite3_file *id, int locktype){ + return _posixUnlock(id, locktype, 1); +} + +#endif /* defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE */ +/* +** The code above is the NFS lock implementation. The code is specific +** to MacOSX and does not work on other unix platforms. No alternative +** is available. +** +********************* End of the NFS lock implementation ********************** +******************************************************************************/ /****************************************************************************** **************** Non-locking sqlite3_file methods ***************************** @@ -2747,7 +2957,9 @@ static int afpClose(sqlite3_file *id) { */ static int seekAndRead(unixFile *id, sqlite3_int64 offset, void *pBuf, int cnt){ int got; +#if (!defined(USE_PREAD) && !defined(USE_PREAD64)) i64 newOffset; +#endif TIMER_START; #if defined(USE_PREAD) got = pread(id->h, pBuf, cnt, offset); @@ -2821,7 +3033,9 @@ static int unixRead( */ static int seekAndWrite(unixFile *id, i64 offset, const void *pBuf, int cnt){ int got; +#if (!defined(USE_PREAD) && !defined(USE_PREAD64)) i64 newOffset; +#endif TIMER_START; #if defined(USE_PREAD) got = pwrite(id->h, pBuf, cnt, offset); @@ -3015,7 +3229,11 @@ static int full_fsync(int fd, int fullSync, int dataOnly){ */ if( rc ) rc = fsync(fd); -#else +#elif defined(__APPLE__) + // fdatasync() on HFS+ doesn't yet flush the file size if it changed correctly + // so currently we default to the macro that redefines fdatasync to fsync + rc = fsync(fd); +#else rc = fdatasync(fd); #if OS_VXWORKS if( rc==-1 && errno==ENOTSUP ){ @@ -3336,23 +3554,6 @@ IOMETHODS( ) #endif -/* -** The "Whole File Locking" finder returns the same set of methods as -** the posix locking finder. But it also sets the SQLITE_WHOLE_FILE_LOCKING -** flag to force the posix advisory locks to cover the whole file instead -** of just a small span of bytes near the 1GiB boundary. Whole File Locking -** is useful on NFS-mounted files since it helps NFS to maintain cache -** coherency. But it is a detriment to other filesystems since it runs -** slower. -*/ -static const sqlite3_io_methods *posixWflIoFinderImpl(const char*z, unixFile*p){ - UNUSED_PARAMETER(z); - p->fileFlags = SQLITE_WHOLE_FILE_LOCKING; - return &posixIoMethods; -} -static const sqlite3_io_methods - *(*const posixWflIoFinder)(const char*,unixFile *p) = posixWflIoFinderImpl; - /* ** The proxy locking method is a "super-method" in the sense that it ** opens secondary file descriptors for the conch and lock files and @@ -3377,6 +3578,17 @@ IOMETHODS( ) #endif +/* nfs lockd on OSX 10.3+ doesn't clear write locks when a read lock is set */ +#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE +IOMETHODS( + nfsIoFinder, /* Finder function name */ + nfsIoMethods, /* sqlite3_io_methods object name */ + unixClose, /* xClose method */ + unixLock, /* xLock method */ + nfsUnlock, /* xUnlock method */ + unixCheckReservedLock /* xCheckReservedLock method */ +) +#endif #if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE /* @@ -3397,11 +3609,7 @@ static const sqlite3_io_methods *autolockIoFinderImpl( { "hfs", &posixIoMethods }, { "ufs", &posixIoMethods }, { "afpfs", &afpIoMethods }, -#ifdef SQLITE_ENABLE_AFP_LOCKING_SMB { "smbfs", &afpIoMethods }, -#else - { "smbfs", &flockIoMethods }, -#endif { "webdav", &nolockIoMethods }, { 0, 0 } }; @@ -3434,8 +3642,11 @@ static const sqlite3_io_methods *autolockIoFinderImpl( lockInfo.l_whence = SEEK_SET; lockInfo.l_type = F_RDLCK; if( fcntl(pNew->h, F_GETLK, &lockInfo)!=-1 ) { - pNew->fileFlags = SQLITE_WHOLE_FILE_LOCKING; - return &posixIoMethods; + if( strcmp(fsInfo.f_fstypename, "nfs")==0 ){ + return &nfsIoMethods; + } else { + return &posixIoMethods; + } }else{ return &dotlockIoMethods; } @@ -3546,7 +3757,7 @@ static int fillInUnixFile( #endif } - if( pLockingStyle == &posixIoMethods ){ + if( pLockingStyle == &posixIoMethods || pLockingStyle == &nfsIoMethods ){ unixEnterMutex(); rc = findLockInfo(pNew, &pNew->pLock, &pNew->pOpen); if( rc!=SQLITE_OK ){ @@ -3588,9 +3799,15 @@ static int fillInUnixFile( ** according to requirement F11141. So we do not need to make a ** copy of the filename. */ pCtx->dbPath = zFilename; + pCtx->reserved = 0; srandomdev(); unixEnterMutex(); - rc = findLockInfo(pNew, NULL, &pNew->pOpen); + rc = findLockInfo(pNew, &pNew->pLock, &pNew->pOpen); + if( rc!=SQLITE_OK ){ + sqlite3_free(pNew->lockingContext); + close(h); + h = -1; + } unixLeaveMutex(); } } @@ -3854,7 +4071,8 @@ static int unixOpen( int isCreate = (flags & SQLITE_OPEN_CREATE); int isReadonly = (flags & SQLITE_OPEN_READONLY); int isReadWrite = (flags & SQLITE_OPEN_READWRITE); - + int isAutoProxy = (flags & SQLITE_OPEN_AUTOPROXY); + /* If creating a master or main-file journal, this function will open ** a file-descriptor on the directory too. The first time unixSync() ** is called the directory file descriptor will be fsync()ed and close()d. @@ -3988,7 +4206,36 @@ static int unixOpen( noLock = eType!=SQLITE_OPEN_MAIN_DB; #if SQLITE_PREFER_PROXY_LOCKING - if( zPath!=NULL && !noLock && pVfs->xOpen ){ + isAutoProxy = 1; +#endif + +#if defined(__APPLE__) || SQLITE_ENABLE_LOCKING_STYLE + + struct statfs fsInfo; + if( fstatfs(fd, &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; + } + + ((unixFile*)pFile)->fsFlags = SQLITE_FSFLAGS_INITIALIZED; + + if (fsInfo.f_flags & MNT_RDONLY) { + ((unixFile*)pFile)->fsFlags |= SQLITE_FSFLAGS_IS_READONLY; + } + if (fsInfo.f_flags & MNT_LOCAL) { + ((unixFile*)pFile)->fsFlags |= SQLITE_FSFLAGS_IS_LOCAL; + } + if (0 == strncmp("hfs", fsInfo.f_fstypename, 3)) { + ((unixFile*)pFile)->fsFlags |= SQLITE_FSFLAGS_IS_HFSPLUS; + } else if (0 == strncmp("msdos", fsInfo.f_fstypename, 5)) { + ((unixFile*)pFile)->fsFlags |= SQLITE_FSFLAGS_IS_MSDOS; + } +#endif + +#if SQLITE_ENABLE_LOCKING_STYLE + if( isAutoProxy && (zPath!=NULL) && (!noLock) && pVfs->xOpen ){ char *envforce = getenv("SQLITE_FORCE_PROXY_LOCKING"); int useProxy = 0; @@ -4020,6 +4267,14 @@ static int unixOpen( rc = fillInUnixFile(pVfs, fd, dirfd, pFile, zPath, noLock, isDelete); if( rc==SQLITE_OK ){ rc = proxyTransformUnixFile((unixFile*)pFile, ":auto:"); + if( rc!=SQLITE_OK ){ + /* Use unixClose to clean up the resources added in fillInUnixFile + ** and clear all the structure's references. Specifically, + ** pFile->pMethods will be NULL so sqlite3OsClose will be a no-op + */ + unixClose(pFile); + return rc; + } } goto open_finished; } @@ -4447,11 +4702,6 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ ** of the database file for multiple readers and writers on the same ** host (the conch ensures that they all use the same local lock file). ** -** There is a third file - the host ID file - used as a persistent record -** of a unique identifier for the host, a 128-byte unique host id file -** in the path defined by the HOSTIDPATH macro (default value is -** /Library/Caches/.com.apple.sqliteConchHostId). -** ** Requesting the lock proxy does not immediately take the conch, it is ** only taken when the first request to lock database file is made. ** This matches the semantics of the traditional locking behavior, where @@ -4477,10 +4727,6 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ ** Enables the logging of error messages during host id file ** retrieval and creation ** -** HOSTIDPATH -** -** Overrides the default host ID file path location -** ** LOCKPROXYDIR ** ** Overrides the default directory used for lock proxy files that @@ -4505,11 +4751,6 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ */ #if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE -#ifdef SQLITE_TEST -/* simulate multiple hosts by creating unique hostid file paths */ -int sqlite3_hostid_num = 0; -#endif - /* ** The proxyLockingContext has the path and file structures for the remote ** and local proxy files in it @@ -4521,134 +4762,16 @@ struct proxyLockingContext { 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 */ + int conchHeld; /* 1 if the conch is held, -1 if lockless */ void *oldLockingContext; /* Original lockingcontext to restore on close */ sqlite3_io_methods const *pOldMethod; /* Original I/O methods for close */ }; -/* 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 proxyGenerateHostID(char *pHostID){ - int pid, fd, 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 - { - int i; - /* 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; -} - -/* writes the host id path to path, path should be an pre-allocated buffer -** with enough space for a path -*/ -static void 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; - 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 +/* +** The proxy lock file path for the database at dbPath is written into lPath, +** which must point to valid, writable memory large enough for a maxLen length +** file path. */ -static int proxyGetHostID(char *pHostID, int *pError){ - int fd; - char path[MAXPATHLEN]; - size_t len; - int rc=SQLITE_OK; - - 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; - - 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 ){ -#ifdef SQLITE_PROXY_DEBUG /* set the sqlite error message instead */ - int err = errno; - 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 0) ){ + /* only mkdir if leaf dir != "." or "/" or ".." */ + if( i-start>2 || (i-start==1 && buf[start] != '.' && buf[start] != '/') + || (i-start==2 && buf[start] != '.' && buf[start+1] != '.') ){ + buf[i]='\0'; + if( mkdir(buf, SQLITE_DEFAULT_PROXYDIR_PERMISSIONS) ){ + int err=errno; + if( err!=EEXIST ) { + OSTRACE5("CREATELOCKPATH FAILED creating %s, '%s' proxy lock path=%s pid=%d\n", buf, strerror(err), lockPath, getpid()); + return err; + } + } + } + start=i+1; + } + buf[i] = lockPath[i]; + } + OSTRACE3("CREATELOCKPATH proxy lock path=%s pid=%d\n", lockPath, getpid()); + return 0; +} + /* ** Create a new VFS file descriptor (stored in memory obtained from ** sqlite3_malloc) and open the file named "path" in the file descriptor. @@ -4702,48 +4850,263 @@ static int proxyGetLockPath(const char *dbPath, char *lPath, size_t maxLen){ ** The caller is responsible not only for closing the file descriptor ** but also for freeing the memory associated with the file descriptor. */ -static int proxyCreateUnixFile(const char *path, unixFile **ppFile) { +static int proxyCreateUnixFile( + const char *path, /* path for the new unixFile */ + unixFile **ppFile, /* unixFile created and returned by ref */ + int islockfile /* if non zero missing dirs will be created */ +) { + int fd = -1; + int dirfd = -1; unixFile *pNew; - int flags = SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE; int rc = SQLITE_OK; + int openFlags = O_RDWR | O_CREAT; sqlite3_vfs dummyVfs; - - pNew = (unixFile *)sqlite3_malloc(sizeof(unixFile)); - if( !pNew ){ - return SQLITE_NOMEM; + int terrno = 0; + UnixUnusedFd *pUnused = NULL; + + /* 1. first try to open/create the file + ** 2. if that fails, and this is a lock file (not-conch), try creating + ** the parent directories and then try again. + ** 3. if that fails, try to open the file read-only + ** otherwise return BUSY (if lock file) or CANTOPEN for the conch file + */ + pUnused = findReusableFd(path, openFlags); + if( pUnused ){ + fd = pUnused->fd; + }else{ + pUnused = sqlite3_malloc(sizeof(*pUnused)); + if( !pUnused ){ + return SQLITE_NOMEM; + } + } + if( fd<0 ){ + fd = open(path, openFlags, SQLITE_DEFAULT_FILE_PERMISSIONS); + terrno = errno; + if( fd<0 && errno==ENOENT && islockfile ){ + if( proxyCreateLockPath(path) == SQLITE_OK ){ + fd = open(path, openFlags, SQLITE_DEFAULT_FILE_PERMISSIONS); + } + } + } + if( fd<0 ){ + openFlags = O_RDONLY; + fd = open(path, openFlags, SQLITE_DEFAULT_FILE_PERMISSIONS); + terrno = errno; + } + if( fd<0 ){ + if( islockfile ){ + return SQLITE_BUSY; + } + switch (terrno) { + case EACCES: + return SQLITE_PERM; + case EIO: + return SQLITE_IOERR_LOCK; /* even though it is the conch */ + default: + return SQLITE_CANTOPEN; + } + } + + pNew = (unixFile *)sqlite3_malloc(sizeof(*pNew)); + if( pNew==NULL ){ + rc = SQLITE_NOMEM; + goto end_create_proxy; } memset(pNew, 0, sizeof(unixFile)); - - /* Call unixOpen() to open the proxy file. The flags passed to unixOpen() - ** suggest that the file being opened is a "main database". This is - ** necessary as other file types do not necessarily support locking. It - ** is better to use unixOpen() instead of opening the file directly with - ** open(), as unixOpen() sets up the various mechanisms required to - ** make sure a call to close() does not cause the system to discard - ** POSIX locks prematurely. - ** - ** It is important that the xOpen member of the VFS object passed to - ** unixOpen() is NULL. This tells unixOpen() may try to open a proxy-file - ** for the proxy-file (creating a potential infinite loop). - */ + pNew->openFlags = openFlags; dummyVfs.pAppData = (void*)&autolockIoFinder; - dummyVfs.xOpen = 0; - rc = unixOpen(&dummyVfs, path, (sqlite3_file *)pNew, flags, &flags); - if( rc==SQLITE_OK && (flags&SQLITE_OPEN_READONLY) ){ - pNew->pMethod->xClose((sqlite3_file *)pNew); - rc = SQLITE_CANTOPEN; + pUnused->fd = fd; + pUnused->flags = openFlags; + pNew->pUnused = pUnused; + + rc = fillInUnixFile(&dummyVfs, 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); + sqlite3_free(pUnused); + return rc; +} - if( rc!=SQLITE_OK ){ - sqlite3_free(pNew); - pNew = 0; +#ifdef SQLITE_TEST +/* simulate multiple hosts by creating unique hostid file paths */ +int sqlite3_hostid_num = 0; +#endif + +#define PROXY_HOSTIDLEN 16 /* conch file host id length */ + +/* get the host ID via gethostuuid(), pHostID must point to PROXY_HOSTIDLEN +** bytes of writable memory. +*/ +static int proxyGetHostID(unsigned char *pHostID, int *pError){ + struct timespec timeout = {1, 0}; /* 1 sec timeout */ + + assert(PROXY_HOSTIDLEN == sizeof(uuid_t)); + memset(pHostID, 0, PROXY_HOSTIDLEN); + if( gethostuuid(pHostID, &timeout) ){ + int err = errno; + if( pError ){ + *pError = err; + } + return SQLITE_IOERR; + } +#ifdef SQLITE_TEST + /* simulate multiple hosts by creating unique hostid file paths */ + if( sqlite3_hostid_num != 0){ + pHostID[0] = (char)(pHostID[0] + (char)(sqlite3_hostid_num & 0xFF)); + } +#endif + + return SQLITE_OK; +} + +/* The conch file contains the header, host id and lock file path + */ +#define PROXY_CONCHVERSION 2 /* 1-byte header, 16-byte host id, path */ +#define PROXY_HEADERLEN 1 /* conch file header length */ +#define PROXY_PATHINDEX (PROXY_HEADERLEN+PROXY_HOSTIDLEN) +#define PROXY_MAXCONCHLEN (PROXY_HEADERLEN+PROXY_HOSTIDLEN+MAXPATHLEN) + +/* +** Takes an open conch file, copies the contents to a new path and then moves +** it back. The newly created file's file descriptor is assigned to the +** conch file structure and finally the original conch file descriptor is +** closed. Returns zero if successful. +*/ +static int proxyBreakConchLock(unixFile *pFile, uuid_t myHostID){ + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + unixFile *conchFile = pCtx->conchFile; + char tPath[MAXPATHLEN]; + char buf[PROXY_MAXCONCHLEN]; + char *cPath = pCtx->conchFilePath; + size_t readLen = 0; + size_t pathLen = 0; + char errmsg[64] = ""; + int fd = -1; + int rc = -1; + + /* create a new path by replace the trailing '-conch' with '-break' */ + pathLen = strlcpy(tPath, cPath, MAXPATHLEN); + if( pathLen>MAXPATHLEN || pathLen<6 || + (strlcpy(&tPath[pathLen-5], "break", 6) != 5) ){ + sprintf(errmsg, "path error (len %d)", (int)pathLen); + goto end_breaklock; + } + /* read the conch content */ + readLen = pread(conchFile->h, buf, PROXY_MAXCONCHLEN, 0); + if( readLenh); + conchFile->h = fd; + conchFile->openFlags = O_RDWR | O_CREAT; - *ppFile = pNew; +end_breaklock: + if( rc ){ + if( fd>=0 ){ + unlink(tPath); + close(fd); + } + fprintf(stderr, "failed to break stale lock on %s, %s\n", cPath, errmsg); + } return rc; } -/* takes the conch by taking a shared lock and read the contents conch, if +/* Take the requested lock on the conch file and break a stale lock if the +** host id matches. +*/ +static int proxyConchLock(unixFile *pFile, uuid_t myHostID, int lockType){ + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + unixFile *conchFile = pCtx->conchFile; + int rc = SQLITE_OK; + int nTries = 0; + struct timespec conchModTime; + + do { + rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, lockType); + nTries ++; + if( rc==SQLITE_BUSY ){ + /* If the lock failed (busy): + * 1st try: get the mod time of the conch, wait 0.5s and try again. + * 2nd try: fail if the mod time changed or host id is different, wait + * 10 sec and try again + * 3rd try: break the lock unless the mod time has changed. + */ + struct stat buf; + if( fstat(conchFile->h, &buf) ){ + pFile->lastErrno = errno; + return SQLITE_IOERR_LOCK; + } + + if( nTries==1 ){ + conchModTime = buf.st_mtimespec; + usleep(500000); /* wait 0.5 sec and try the lock again*/ + continue; + } + + assert( nTries>1 ); + if( conchModTime.tv_sec != buf.st_mtimespec.tv_sec || + conchModTime.tv_nsec != buf.st_mtimespec.tv_nsec ){ + return SQLITE_BUSY; + } + + if( nTries==2 ){ + char tBuf[PROXY_MAXCONCHLEN]; + int len = pread(conchFile->h, tBuf, PROXY_MAXCONCHLEN, 0); + if( len<0 ){ + pFile->lastErrno = errno; + return SQLITE_IOERR_LOCK; + } + if( len>PROXY_PATHINDEX && tBuf[0]==(char)PROXY_CONCHVERSION){ + /* don't break the lock if the host id doesn't match */ + if( 0!=memcmp(&tBuf[PROXY_HEADERLEN], myHostID, PROXY_HOSTIDLEN) ){ + return SQLITE_BUSY; + } + }else{ + /* don't break the lock on short read or a version mismatch */ + return SQLITE_BUSY; + } + usleep(10000000); /* wait 10 sec and try the lock again */ + continue; + } + + assert( nTries==3 ); + if( 0==proxyBreakConchLock(pFile, myHostID) ){ + rc = SQLITE_OK; + if( lockType==EXCLUSIVE_LOCK ){ + rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, SHARED_LOCK); + } + if( !rc ){ + rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, lockType); + } + } + } + } while( rc==SQLITE_BUSY && nTries<3 ); + + 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 @@ -4752,150 +5115,218 @@ static int proxyCreateUnixFile(const char *path, unixFile **ppFile) { static int proxyTakeConch(unixFile *pFile){ proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; - if( pCtx->conchHeld>0 ){ + if( pCtx->conchHeld!=0 ){ return SQLITE_OK; }else{ unixFile *conchFile = pCtx->conchFile; - char testValue[CONCHLEN]; - char conchValue[CONCHLEN]; + uuid_t myHostID; + int pError = 0; + char readBuf[PROXY_MAXCONCHLEN]; char lockPath[MAXPATHLEN]; - char *tLockPath = NULL; + char *tempLockPath = NULL; int rc = SQLITE_OK; - int readRc = SQLITE_OK; - int syncPerms = 0; - + int createConch = 0; + int hostIdMatch = 0; + int readLen = 0; + int tryOldLockPath = 0; + int forceNewLockPath = 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 = proxyGetHostID(testValue, &pError); - if( (rc&0xff)==SQLITE_IOERR ){ - pFile->lastErrno = pError; - } - if( pCtx->lockProxyPath ){ - strlcpy(&testValue[HOSTIDLEN], pCtx->lockProxyPath, MAXPATHLEN); - } + rc = proxyGetHostID(myHostID, &pError); + if( (rc&0xff)==SQLITE_IOERR ){ + pFile->lastErrno = pError; + goto end_takeconch; } + rc = proxyConchLock(pFile, myHostID, SHARED_LOCK); if( rc!=SQLITE_OK ){ goto end_takeconch; } - - readRc = unixRead((sqlite3_file *)conchFile, conchValue, CONCHLEN, 0); - if( readRc!=SQLITE_IOERR_SHORT_READ ){ - if( readRc!=SQLITE_OK ){ - if( (rc&0xff)==SQLITE_IOERR ){ - pFile->lastErrno = conchFile->lastErrno; + /* read the existing conch file */ + readLen = seekAndRead((unixFile*)conchFile, 0, readBuf, PROXY_MAXCONCHLEN); + if( readLen<0 ){ + /* I/O error: lastErrno set by seekAndRead */ + pFile->lastErrno = conchFile->lastErrno; + rc = SQLITE_IOERR_READ; + goto end_takeconch; + }else if( readLen<=(PROXY_HEADERLEN+PROXY_HOSTIDLEN) || + readBuf[0]!=(char)PROXY_CONCHVERSION ){ + /* a short read or version format mismatch means we need to create a new + ** conch file. + */ + createConch = 1; + } + /* if the host id matches and the lock path already exists in the conch + ** we'll try to use the path there, if we can't open that path, we'll + ** retry with a new auto-generated path + */ + do { /* in case we need to try again for an :auto: named lock file */ + + if( !createConch && !forceNewLockPath ){ + hostIdMatch = !memcmp(&readBuf[PROXY_HEADERLEN], myHostID, + PROXY_HOSTIDLEN); + /* 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( hostIdMatch ){ + size_t pathLen = (readLen - PROXY_PATHINDEX); + + if( pathLen>=MAXPATHLEN ){ + pathLen=MAXPATHLEN-1; + } + memcpy(lockPath, &readBuf[PROXY_PATHINDEX], pathLen); + lockPath[pathLen] = 0; + tempLockPath = lockPath; + tryOldLockPath = 1; + /* create a copy of the lock path if the conch is taken */ + goto end_takeconch; + } + }else if( hostIdMatch && !strncmp(pCtx->lockProxyPath, + &readBuf[PROXY_PATHINDEX], readLen-PROXY_PATHINDEX) ){ + /* conch host and lock path match */ + goto end_takeconch; } - rc = readRc; + } + + /* if the conch isn't writable and doesn't match, we can't take it */ + if( (conchFile->openFlags&O_RDWR) == 0 ){ + rc = SQLITE_BUSY; goto end_takeconch; } - /* if the conch has data compare the contents */ + + /* either the conch didn't match or we need to create a new one */ 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; + proxyGetLockPath(pCtx->dbPath, lockPath, MAXPATHLEN); + tempLockPath = lockPath; + /* create a copy of the lock path _only_ if the conch is taken */ + } + + /* update conch with host and path (this will fail if other process + ** has a shared lock already), if the host id matches, use the big + ** stick. + */ + futimes(conchFile->h, NULL); + if( hostIdMatch && !createConch ){ + if( conchFile->pLock && conchFile->pLock->cnt>1 ){ + /* We are trying for an exclusive lock but another thread in this + ** same process is still holding a shared lock. */ + rc = SQLITE_BUSY; + } else { + rc = proxyConchLock(pFile, myHostID, EXCLUSIVE_LOCK); } }else{ - /* we've got the conch if conchValue matches our path and host ID */ - if( !memcmp(testValue, conchValue, CONCHLEN) ){ - goto end_takeconch; - } + rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, EXCLUSIVE_LOCK); } - }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 ){ - proxyGetLockPath(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 ){ - /* try to match the database file permissions, ignore failure */ + if( rc==SQLITE_OK ){ + char writeBuffer[PROXY_MAXCONCHLEN]; + int writeSize = 0; + + writeBuffer[0] = (char)PROXY_CONCHVERSION; + memcpy(&writeBuffer[PROXY_HEADERLEN], myHostID, PROXY_HOSTIDLEN); + if( pCtx->lockProxyPath!=NULL ){ + strlcpy(&writeBuffer[PROXY_PATHINDEX], pCtx->lockProxyPath, MAXPATHLEN); + }else{ + strlcpy(&writeBuffer[PROXY_PATHINDEX], tempLockPath, MAXPATHLEN); + } + writeSize = PROXY_PATHINDEX + strlen(&writeBuffer[PROXY_PATHINDEX]); + ftruncate(conchFile->h, writeSize); + rc = unixWrite((sqlite3_file *)conchFile, writeBuffer, writeSize, 0); + fsync(conchFile->h); + /* If we created a new conch file (not just updated the contents of a + ** valid conch file), try to match the permissions of the database + */ + if( rc==SQLITE_OK && createConch ){ + struct stat buf; + int err = fstat(pFile->h, &buf); + if( err==0 ){ + mode_t cmode = buf.st_mode&(S_IRUSR|S_IWUSR | S_IRGRP|S_IWGRP | + S_IROTH|S_IWOTH); + /* try to match the database file R/W permissions, ignore failure */ #ifndef SQLITE_PROXY_DEBUG - fchmod(conchFile->h, buf.st_mode); + fchmod(conchFile->h, cmode); #else - if( fchmod(conchFile->h, buf.st_mode)!=0 ){ + if( fchmod(conchFile->h, cmode)!=0 ){ + int code = errno; + fprintf(stderr, "fchmod %o FAILED with %d %s\n", + cmode, code, strerror(code)); + } else { + fprintf(stderr, "fchmod %o SUCCEDED\n",cmode); + } + }else{ 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)); + 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->openFlags ){ - if( pFile->h>=0 ){ + conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, SHARED_LOCK); + + end_takeconch: + OSTRACE2("TRANSPROXY: CLOSE %d\n", pFile->h); + if( rc==SQLITE_OK && pFile->openFlags ){ + if( pFile->h>=0 ){ #ifdef STRICT_CLOSE_ERROR - if( close(pFile->h) ){ - pFile->lastErrno = errno; - return SQLITE_IOERR_CLOSE; - } + if( close(pFile->h) ){ + pFile->lastErrno = errno; + return SQLITE_IOERR_CLOSE; + } #else - close(pFile->h); /* silently leak fd if fail */ + close(pFile->h); /* silently leak fd if fail */ #endif + } + pFile->h = -1; + int fd = open(pCtx->dbPath, pFile->openFlags, + SQLITE_DEFAULT_FILE_PERMISSIONS); + OSTRACE2("TRANSPROXY: OPEN %d\n", fd); + if( fd>=0 ){ + pFile->h = fd; + }else{ + rc=SQLITE_CANTOPEN; /* SQLITE_BUSY? proxyTakeConch called + during locking */ + } } - pFile->h = -1; - int fd = open(pCtx->dbPath, pFile->openFlags, - SQLITE_DEFAULT_FILE_PERMISSIONS); - OSTRACE2("TRANSPROXY: OPEN %d\n", fd); - if( fd>=0 ){ - pFile->h = fd; - }else{ - rc=SQLITE_CANTOPEN; /* SQLITE_BUSY? proxyTakeConch called - during locking */ + if( rc==SQLITE_OK && !pCtx->lockProxy ){ + char *path = tempLockPath ? tempLockPath : pCtx->lockProxyPath; + rc = proxyCreateUnixFile(path, &pCtx->lockProxy, 1); + if( rc!=SQLITE_OK && rc!=SQLITE_NOMEM && tryOldLockPath ){ + /* we couldn't create the proxy lock file with the old lock file path + ** so try again via auto-naming + */ + forceNewLockPath = 1; + tryOldLockPath = 0; + continue; // go back to the do {} while start point, try again + } } - } - if( rc==SQLITE_OK && !pCtx->lockProxy ){ - char *path = tLockPath ? tLockPath : pCtx->lockProxyPath; - /* ACS: Need to make a copy of path sometimes */ - rc = proxyCreateUnixFile(path, &pCtx->lockProxy); - } - if( rc==SQLITE_OK ){ - pCtx->conchHeld = 1; - - if( tLockPath ){ - pCtx->lockProxyPath = sqlite3DbStrDup(0, tLockPath); + if( rc==SQLITE_OK ){ + /* Need to make a copy of path if we extracted the value + ** from the conch file or the path was allocated on the stack + */ + if( tempLockPath ){ + pCtx->lockProxyPath = sqlite3DbStrDup(0, tempLockPath); + if( !pCtx->lockProxyPath ){ + rc = SQLITE_NOMEM; + } + } + } + if( rc==SQLITE_OK ){ + pCtx->conchHeld = 1; + if( pCtx->lockProxy->pMethod == &afpIoMethods ){ - ((afpLockingContext *)pCtx->lockProxy->lockingContext)->dbPath = - pCtx->lockProxyPath; + afpLockingContext *afpCtx; + afpCtx = (afpLockingContext *)pCtx->lockProxy->lockingContext; + afpCtx->dbPath = pCtx->lockProxyPath; } + } else { + conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, NO_LOCK); } - } else { - conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, NO_LOCK); - } - OSTRACE3("TAKECONCH %d %s\n", conchFile->h, rc==SQLITE_OK?"ok":"failed"); - return rc; - } + OSTRACE3("TAKECONCH %d %s\n", conchFile->h, rc==SQLITE_OK?"ok":"failed"); + return rc; + } while (1); /* in case we need to retry the :auto: lock file - we should never get here except via the 'continue' call. */ + } } /* @@ -4911,8 +5342,10 @@ static int proxyReleaseConch(unixFile *pFile){ OSTRACE4("RELEASECONCH %d for %s pid=%d\n", conchFile->h, (pCtx->lockProxyPath ? pCtx->lockProxyPath : ":auto:"), getpid()); + if( pCtx->conchHeld>0 ){ + rc = conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, NO_LOCK); + } 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; @@ -5008,8 +5441,8 @@ static int proxyGetDbPathForUnixFile(unixFile *pFile, char *dbPath){ /* afp style keeps a reference to the db path in the filePath field ** of the struct */ assert( (int)strlen((char*)pFile->lockingContext)<=MAXPATHLEN ); - strcpy(dbPath, ((afpLockingContext *)pFile->lockingContext)->dbPath); - }else + strlcpy(dbPath, ((afpLockingContext *)pFile->lockingContext)->dbPath, MAXPATHLEN); + } else #endif if( pFile->pMethod == &dotlockIoMethods ){ /* dot lock style uses the locking context to store the dot lock @@ -5019,7 +5452,7 @@ static int proxyGetDbPathForUnixFile(unixFile *pFile, char *dbPath){ }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); + strlcpy(dbPath, (char *)pFile->lockingContext, MAXPATHLEN); } return SQLITE_OK; } @@ -5059,27 +5492,53 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) { rc = proxyCreateConchPathname(dbPath, &pCtx->conchFilePath); if( rc==SQLITE_OK ){ - rc = proxyCreateUnixFile(pCtx->conchFilePath, &pCtx->conchFile); - } + rc = proxyCreateUnixFile(pCtx->conchFilePath, &pCtx->conchFile, 0); + if( rc==SQLITE_CANTOPEN && ((pFile->openFlags&O_RDWR) == 0) ){ + /* if (a) the open flags are not O_RDWR, (b) the conch isn't there, and + ** (c) the file system is read-only, then enable no-locking access. + ** Ugh, since O_RDONLY==0x0000 we test for !O_RDWR since unixOpen asserts + ** that openFlags will have only one of O_RDONLY or O_RDWR. + */ + struct statfs fsInfo; + struct stat conchInfo; + int goLockless = 0; + + if( stat(pCtx->conchFilePath, &conchInfo) == -1 ) { + int err = errno; + if( (err==ENOENT) && (statfs(dbPath, &fsInfo) != -1) ){ + goLockless = (fsInfo.f_flags&MNT_RDONLY) == MNT_RDONLY; + } + } + if( goLockless ){ + pCtx->conchHeld = -1; /* read only FS/ lockless */ + rc = SQLITE_OK; + } + } + } if( rc==SQLITE_OK && lockPath ){ pCtx->lockProxyPath = sqlite3DbStrDup(0, lockPath); } + if( rc==SQLITE_OK ){ + pCtx->dbPath = sqlite3DbStrDup(0, dbPath); + if( pCtx->dbPath==NULL ){ + rc = SQLITE_NOMEM; + } + } 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 = &proxyIoMethods; }else{ if( pCtx->conchFile ){ - rc = pCtx->conchFile->pMethod->xClose((sqlite3_file *)pCtx->conchFile); - if( rc ) return rc; + pCtx->conchFile->pMethod->xClose((sqlite3_file *)pCtx->conchFile); sqlite3_free(pCtx->conchFile); } + sqlite3_free(pCtx->lockProxyPath); sqlite3_free(pCtx->conchFilePath); sqlite3_free(pCtx); } @@ -5168,8 +5627,12 @@ static int proxyCheckReservedLock(sqlite3_file *id, int *pResOut) { 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); + if( pCtx->conchHeld>0 ){ + unixFile *proxy = pCtx->lockProxy; + return proxy->pMethod->xCheckReservedLock((sqlite3_file*)proxy, pResOut); + }else{ /* conchHeld < 0 is lockless */ + pResOut=0; + } } return rc; } @@ -5203,9 +5666,13 @@ static int proxyLock(sqlite3_file *id, int locktype) { 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; + if( pCtx->conchHeld>0 ){ + unixFile *proxy = pCtx->lockProxy; + rc = proxy->pMethod->xLock((sqlite3_file*)proxy, locktype); + pFile->locktype = proxy->locktype; + }else{ + /* conchHeld < 0 is lockless */ + } } return rc; } @@ -5223,9 +5690,13 @@ static int proxyUnlock(sqlite3_file *id, int locktype) { int rc = proxyTakeConch(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; + if( pCtx->conchHeld>0 ){ + unixFile *proxy = pCtx->lockProxy; + rc = proxy->pMethod->xUnlock((sqlite3_file*)proxy, locktype); + pFile->locktype = proxy->locktype; + }else{ + /* conchHeld < 0 is lockless */ + } } return rc; } @@ -5352,7 +5823,6 @@ int sqlite3_os_init(void){ #endif UNIXVFS("unix-none", nolockIoFinder ), UNIXVFS("unix-dotfile", dotlockIoFinder ), - UNIXVFS("unix-wfl", posixWflIoFinder ), #if OS_VXWORKS UNIXVFS("unix-namedsem", semIoFinder ), #endif @@ -5364,6 +5834,7 @@ int sqlite3_os_init(void){ #endif #if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) UNIXVFS("unix-afp", afpIoFinder ), + UNIXVFS("unix-nfs", nfsIoFinder ), UNIXVFS("unix-proxy", proxyIoFinder ), #endif }; diff --git a/src/pragma.c b/src/pragma.c index 3d779a75fa..14abc544cd 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -144,12 +144,12 @@ static int changeTempStorage(Parse *pParse, const char *zStorageType){ /* ** Generate code to return a single integer value. */ -static void returnSingleInt(Parse *pParse, const char *zLabel, i64 value){ +static void returnSingleInt(Parse *pParse, const char *zLabel, i64 *pValue){ Vdbe *v = sqlite3GetVdbe(pParse); int mem = ++pParse->nMem; - i64 *pI64 = sqlite3DbMallocRaw(pParse->db, sizeof(value)); + i64 *pI64 = sqlite3DbMallocRaw(pParse->db, sizeof(*pValue)); if( pI64 ){ - memcpy(pI64, &value, sizeof(value)); + memcpy(pI64, pValue, sizeof(*pValue)); } sqlite3VdbeAddOp4(v, OP_Int64, 0, mem, 0, (char*)pI64, P4_INT64); sqlite3VdbeSetNumCols(v, 1); @@ -208,7 +208,8 @@ static int flagPragma(Parse *pParse, const char *zLeft, const char *zRight){ assert( v!=0 ); /* Already allocated by sqlite3Pragma() */ if( ALWAYS(v) ){ if( zRight==0 ){ - returnSingleInt(pParse, p->zName, (db->flags & p->mask)!=0 ); + i64 value = ((db->flags & p->mask)!=0 ? 1 : 0); + returnSingleInt(pParse, p->zName, &value); }else{ int mask = p->mask; /* Mask of bits to set or clear. */ if( db->autoCommit==0 ){ @@ -383,8 +384,8 @@ void sqlite3Pragma( Btree *pBt = pDb->pBt; assert( pBt!=0 ); if( !zRight ){ - int size = ALWAYS(pBt) ? sqlite3BtreeGetPageSize(pBt) : 0; - returnSingleInt(pParse, "page_size", size); + i64 size = ALWAYS(pBt) ? sqlite3BtreeGetPageSize(pBt) : 0; + returnSingleInt(pParse, "page_size", &size); }else{ /* Malloc may fail when setting the page-size, as there is an internal ** buffer that the pager module resizes using sqlite3_realloc(). @@ -407,7 +408,7 @@ void sqlite3Pragma( */ if( sqlite3StrICmp(zLeft,"max_page_count")==0 ){ Btree *pBt = pDb->pBt; - int newMax = 0; + i64 newMax = 0; assert( pBt!=0 ); if( zRight ){ newMax = atoi(zRight); @@ -415,7 +416,7 @@ void sqlite3Pragma( if( ALWAYS(pBt) ){ newMax = sqlite3BtreeMaxPageCount(pBt, newMax); } - returnSingleInt(pParse, "max_page_count", newMax); + returnSingleInt(pParse, "max_page_count", &newMax); }else /* @@ -556,7 +557,7 @@ void sqlite3Pragma( if( iLimit<-1 ) iLimit = -1; } iLimit = sqlite3PagerJournalSizeLimit(pPager, iLimit); - returnSingleInt(pParse, "journal_size_limit", iLimit); + returnSingleInt(pParse, "journal_size_limit", &iLimit); }else #endif /* SQLITE_OMIT_PAGER_PRAGMAS */ @@ -576,13 +577,13 @@ void sqlite3Pragma( goto pragma_out; } if( !zRight ){ - int auto_vacuum; + i64 auto_vacuum; if( ALWAYS(pBt) ){ auto_vacuum = sqlite3BtreeGetAutoVacuum(pBt); }else{ auto_vacuum = SQLITE_DEFAULT_AUTOVACUUM; } - returnSingleInt(pParse, "auto_vacuum", auto_vacuum); + returnSingleInt(pParse, "auto_vacuum", &auto_vacuum); }else{ int eAuto = getAutoVacuum(zRight); assert( eAuto>=0 && eAuto<=2 ); @@ -664,7 +665,8 @@ void sqlite3Pragma( if( sqlite3StrICmp(zLeft,"cache_size")==0 ){ if( sqlite3ReadSchema(pParse) ) goto pragma_out; if( !zRight ){ - returnSingleInt(pParse, "cache_size", pDb->pSchema->cache_size); + i64 cacheSize = pDb->pSchema->cache_size; + returnSingleInt(pParse, "cache_size", &cacheSize); }else{ int size = atoi(zRight); if( size<0 ) size = -size; @@ -686,7 +688,8 @@ void sqlite3Pragma( */ if( sqlite3StrICmp(zLeft, "temp_store")==0 ){ if( !zRight ){ - returnSingleInt(pParse, "temp_store", db->temp_store); + i64 tempStore = db->temp_store; + returnSingleInt(pParse, "temp_store", &tempStore); }else{ changeTempStorage(pParse, zRight); } @@ -800,7 +803,8 @@ void sqlite3Pragma( if( sqlite3StrICmp(zLeft,"synchronous")==0 ){ if( sqlite3ReadSchema(pParse) ) goto pragma_out; if( !zRight ){ - returnSingleInt(pParse, "synchronous", pDb->safety_level-1); + i64 safetyLevel = pDb->safety_level-1; + returnSingleInt(pParse, "synchronous", &safetyLevel); }else{ if( !db->autoCommit ){ sqlite3ErrorMsg(pParse, diff --git a/src/prepare.c b/src/prepare.c index 5970b6fcc9..ee963dd2bc 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -16,6 +16,9 @@ ** $Id: prepare.c,v 1.131 2009/08/06 17:43:31 drh Exp $ */ #include "sqliteInt.h" +#ifdef SQLITE_ENABLE_SQLRR +# include "sqlrr.h" +#endif /* ** Fill the InitData structure with an error message that indicates @@ -774,6 +777,9 @@ int sqlite3_prepare( ){ int rc; rc = sqlite3LockAndPrepare(db,zSql,nBytes,0,0,ppStmt,pzTail); +#ifdef SQLITE_ENABLE_SQLRR + SRRecPrepare(db, zSql, nBytes, 0, *ppStmt); +#endif assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */ return rc; } @@ -786,6 +792,9 @@ int sqlite3_prepare_v2( ){ int rc; rc = sqlite3LockAndPrepare(db,zSql,nBytes,1,0,ppStmt,pzTail); +#ifdef SQLITE_ENABLE_SQLRR + SRRecPrepare(db, zSql, nBytes, 1, *ppStmt); +#endif assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */ return rc; } @@ -821,7 +830,9 @@ static int sqlite3Prepare16( if( zSql8 ){ rc = sqlite3LockAndPrepare(db, zSql8, -1, saveSqlFlag, 0, ppStmt, &zTail8); } - +#ifdef SQLITE_ENABLE_SQLRR + SRRecPrepare(db, zSql8, -1, 1, *ppStmt); +#endif if( zTail8 && pzTail ){ /* If sqlite3_prepare returns a tail pointer, we calculate the ** equivalent pointer into the UTF-16 string by counting the unicode diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 967986e4bf..10724e28b1 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -415,6 +415,7 @@ int sqlite3_exec( #define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_DELETEONCLOSE 0x00000008 /* VFS only */ #define SQLITE_OPEN_EXCLUSIVE 0x00000010 /* VFS only */ +#define SQLITE_OPEN_AUTOPROXY 0x00000020 /* VFS only */ #define SQLITE_OPEN_MAIN_DB 0x00000100 /* VFS only */ #define SQLITE_OPEN_TEMP_DB 0x00000200 /* VFS only */ #define SQLITE_OPEN_TRANSIENT_DB 0x00000400 /* VFS only */ diff --git a/src/test1.c b/src/test1.c index 6ea48e6713..0ce69b88ea 100644 --- a/src/test1.c +++ b/src/test1.c @@ -3128,6 +3128,7 @@ static int test_clear_bindings( return TCL_ERROR; } if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR; + sqlite3_clear_bindings(0); /* test for handling NULL */ Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_clear_bindings(pStmt))); return TCL_OK; } diff --git a/src/test_backup.c b/src/test_backup.c index 7c561afb59..8ae3e71d81 100644 --- a/src/test_backup.c +++ b/src/test_backup.c @@ -14,7 +14,7 @@ */ #include "tcl.h" -#include +#include "sqlite3.h" #include /* These functions are implemented in test1.c. */ diff --git a/src/vdbeapi.c b/src/vdbeapi.c index b9ee52b6ce..cb03e1cd0d 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -17,6 +17,9 @@ */ #include "sqliteInt.h" #include "vdbeInt.h" +#ifdef SQLITE_ENABLE_SQLRR +# include "sqlrr.h" +#endif #ifndef SQLITE_OMIT_DEPRECATED /* @@ -48,6 +51,9 @@ int sqlite3_finalize(sqlite3_stmt *pStmt){ rc = SQLITE_OK; }else{ Vdbe *v = (Vdbe*)pStmt; +#ifdef SQLITE_ENABLE_SQLRR + SRRecFinalize(pStmt); +#endif sqlite3 *db = v->db; #if SQLITE_THREADSAFE sqlite3_mutex *mutex = v->db->mutex; @@ -74,6 +80,9 @@ int sqlite3_reset(sqlite3_stmt *pStmt){ rc = SQLITE_OK; }else{ Vdbe *v = (Vdbe*)pStmt; +#ifdef SQLITE_ENABLE_SQLRR + SRRecReset(pStmt); +#endif sqlite3_mutex_enter(v->db->mutex); rc = sqlite3VdbeReset(v); sqlite3VdbeMakeReady(v, -1, 0, 0, 0, 0, 0); @@ -92,7 +101,14 @@ int sqlite3_clear_bindings(sqlite3_stmt *pStmt){ int rc = SQLITE_OK; Vdbe *p = (Vdbe*)pStmt; #if SQLITE_THREADSAFE - sqlite3_mutex *mutex = ((Vdbe*)pStmt)->db->mutex; + sqlite3_mutex *mutex=NULL; +#endif + if( NULL==pStmt ){ return SQLITE_OK; } /* */ +#ifdef SQLITE_ENABLE_SQLRR + SRRecClearBindings(pStmt); +#endif +#if SQLITE_THREADSAFE + mutex = ((Vdbe*)pStmt)->db->mutex; #endif sqlite3_mutex_enter(mutex); for(i=0; inVar; i++){ @@ -404,6 +420,10 @@ int sqlite3_step(sqlite3_stmt *pStmt){ int cnt = 0; Vdbe *v = (Vdbe*)pStmt; sqlite3 *db = v->db; +#ifdef SQLITE_ENABLE_SQLRR + SRRecStep(pStmt); +#endif + sqlite3_mutex_enter(db->mutex); while( (rc = sqlite3Step(v))==SQLITE_SCHEMA && cnt++ < 5 @@ -431,6 +451,9 @@ int sqlite3_step(sqlite3_stmt *pStmt){ } rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); +#ifdef SQLITE_ENABLE_SQLRR + SRRecStepEnd(pStmt); +#endif } return rc; } @@ -971,11 +994,17 @@ int sqlite3_bind_blob( int nData, void (*xDel)(void*) ){ +#ifdef SQLITE_ENABLE_SQLRR + SRRecBindBlob(pStmt, i, zData, nData); +#endif return bindText(pStmt, i, zData, nData, xDel, 0); } int sqlite3_bind_double(sqlite3_stmt *pStmt, int i, double rValue){ int rc; Vdbe *p = (Vdbe *)pStmt; +#ifdef SQLITE_ENABLE_SQLRR + SRRecBindDouble(pStmt, i, rValue); +#endif rc = vdbeUnbind(p, i); if( rc==SQLITE_OK ){ sqlite3VdbeMemSetDouble(&p->aVar[i-1], rValue); @@ -984,11 +1013,17 @@ int sqlite3_bind_double(sqlite3_stmt *pStmt, int i, double rValue){ return rc; } int sqlite3_bind_int(sqlite3_stmt *p, int i, int iValue){ +#ifdef SQLITE_ENABLE_SQLRR + SRRecBindInt64(p, i, (i64)iValue); +#endif return sqlite3_bind_int64(p, i, (i64)iValue); } int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValue){ int rc; Vdbe *p = (Vdbe *)pStmt; +#ifdef SQLITE_ENABLE_SQLRR + SRRecBindInt64(pStmt, i, iValue); +#endif rc = vdbeUnbind(p, i); if( rc==SQLITE_OK ){ sqlite3VdbeMemSetInt64(&p->aVar[i-1], iValue); @@ -999,6 +1034,9 @@ int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValue){ int sqlite3_bind_null(sqlite3_stmt *pStmt, int i){ int rc; Vdbe *p = (Vdbe*)pStmt; +#ifdef SQLITE_ENABLE_SQLRR + SRRecBindNull(pStmt, i); +#endif rc = vdbeUnbind(p, i); if( rc==SQLITE_OK ){ sqlite3_mutex_leave(p->db->mutex); @@ -1012,6 +1050,9 @@ int sqlite3_bind_text( int nData, void (*xDel)(void*) ){ +#ifdef SQLITE_ENABLE_SQLRR + SRRecBindText(pStmt, i, zData, nData); +#endif return bindText(pStmt, i, zData, nData, xDel, SQLITE_UTF8); } #ifndef SQLITE_OMIT_UTF16 @@ -1022,6 +1063,9 @@ int sqlite3_bind_text16( int nData, void (*xDel)(void*) ){ +#ifdef SQLITE_ENABLE_SQLRR + SRRecBindText(pStmt, i, zData, nData); +#endif return bindText(pStmt, i, zData, nData, xDel, SQLITE_UTF16NATIVE); } #endif /* SQLITE_OMIT_UTF16 */ @@ -1045,6 +1089,9 @@ int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_value *pValue){ break; } case SQLITE_TEXT: { +#ifdef SQLITE_ENABLE_SQLRR + SRRecBindText(pStmt, i, zData, nData); +#endif rc = bindText(pStmt,i, pValue->z, pValue->n, SQLITE_TRANSIENT, pValue->enc); break; @@ -1059,6 +1106,9 @@ int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_value *pValue){ int sqlite3_bind_zeroblob(sqlite3_stmt *pStmt, int i, int n){ int rc; Vdbe *p = (Vdbe *)pStmt; +#ifdef SQLITE_ENABLE_SQLRR + SRRecBindBlob(pStmt, i, NULL, n); +#endif rc = vdbeUnbind(p, i); if( rc==SQLITE_OK ){ sqlite3VdbeMemSetZeroBlob(&p->aVar[i-1], n); diff --git a/test/attach.test b/test/attach.test index 7ee659749c..d91a28313d 100644 --- a/test/attach.test +++ b/test/attach.test @@ -714,23 +714,24 @@ do_test attach-6.1 { } } {0 {}} if {$tcl_platform(platform)=="unix"} { - do_test attach-6.2 { - sqlite3 dbx cannot-read - dbx eval {CREATE TABLE t1(a,b,c)} - dbx close - file attributes cannot-read -permission 0000 - if {[file writable cannot-read]} { - puts "\n**** Tests do not work when run as root ****" - file delete -force cannot-read - exit 1 - } - catchsql { - ATTACH DATABASE 'cannot-read' AS noread; - } - } {1 {unable to open database: cannot-read}} - do_test attach-6.2.2 { - db errorcode - } {14} + sqlite3 dbx cannot-read + dbx eval {CREATE TABLE t1(a,b,c)} + dbx close + file attributes cannot-read -permission 0000 + if {[file writable cannot-read]} { + #puts "\n**** Tests do not work when run as root ****" + file delete -force cannot-read + #exit 1 + } else { + do_test attach-6.2 { + catchsql { + ATTACH DATABASE 'cannot-read' AS noread; + } + } {1 {unable to open database: cannot-read}} + do_test attach-6.2.2 { + db errorcode + } {14} + } file delete -force cannot-read } diff --git a/test/lock_proxy.test b/test/lock_proxy.test new file mode 100644 index 0000000000..60ea95b194 --- /dev/null +++ b/test/lock_proxy.test @@ -0,0 +1,120 @@ +# 2008 June 28 +# +# 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 proxy locks. +# +# $Id$ + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# This file is only run if using the unix backend compiled with the +# SQLITE_ENABLE_LOCKING_STYLE macro. +db close +if {[catch {sqlite3 db test.db -vfs unix-none} msg]} { + finish_test + return +} +db close +file delete -force test.db.lock + +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" +} + + +##################################################################### + +# test proxy locking readonly file system handling +# +ifcapable lock_proxy_pragmas { + if {[file exists /usr/bin/hdiutil]} { + + puts "Creating readonly file system for proxy locking tests..." + if {[file exists /Volumes/readonly]} { + exec hdiutil detach /Volumes/readonly + } + if {[file exists readonly.dmg]} { + file delete readonly.dmg + } + exec hdiutil create -megabytes 1 -fs HFS+ readonly.dmg -volname readonly + exec hdiutil attach readonly.dmg + set sqlite_hostid_num 4 + set env(SQLITE_FORCE_PROXY_LOCKING) "1" + + sqlite3 db2 /Volumes/readonly/test1.db + execsql { + create table x(y); + } db2 + db2 close + + set sqlite_hostid_num 5 + sqlite3 db2 /Volumes/readonly/test2.db + execsql { + create table x(y); + } db2 + db2 close + + set env(SQLITE_FORCE_PROXY_LOCKING) "0" + sqlite3 db2 /Volumes/readonly/test3.db + execsql { + create table x(y); + } db2 + db2 close + set env(SQLITE_FORCE_PROXY_LOCKING) "1" + exec hdiutil detach /Volumes/readonly + exec hdiutil attach -readonly readonly.dmg + + do_test lock_proxy2.0 { + sqlite3 db2 /Volumes/readonly/test1.db + catchsql { + select * from sqlite_master; + } db2 + } {1 {database is locked}} + catch { db2 close } + + do_test lock_proxy2.1 { + sqlite3 db2 /Volumes/readonly/test2.db + catchsql { + select * from sqlite_master; + } db2 + } {0 {table x x 2 {CREATE TABLE x(y)}}} + catch { db2 close } + + do_test lock_proxy2.2 { + sqlite3 db2 /Volumes/readonly/test3.db + catchsql { + select * from sqlite_master; + } db2 + } {0 {table x x 2 {CREATE TABLE x(y)}}} + catch { db2 close } + + exec hdiutil detach /Volumes/readonly + file delete readonly.dmg + } + set env(SQLITE_FORCE_PROXY_LOCKING) "0" + set sqlite_hostid_num 0 +} + +##################################################################### + +file delete -force test.db + +ifcapable lock_proxy_pragmas { + set env(SQLITE_FORCE_PROXY_LOCKING) $::using_proxy +} + +finish_test diff --git a/test/main.test b/test/main.test index 12262c1c51..759403fc02 100644 --- a/test/main.test +++ b/test/main.test @@ -293,7 +293,7 @@ do_test main-2.0 { # do_test main-3.1 { catch {db close} - foreach f [glob -nocomplain testdb/*] {file delete -force $f} + catch {foreach f [glob -nocomplain testdb/*] {file delete -force $f}} file delete -force testdb sqlite3 db testdb set v [catch {execsql {SELECT * from T1 where x!!5}} msg] @@ -301,7 +301,7 @@ do_test main-3.1 { } {1 {unrecognized token: "!!"}} do_test main-3.2 { catch {db close} - foreach f [glob -nocomplain testdb/*] {file delete -force $f} + catch {foreach f [glob -nocomplain testdb/*] {file delete -force $f}} file delete -force testdb sqlite3 db testdb set v [catch {execsql {SELECT * from T1 where ^x}} msg] @@ -421,7 +421,7 @@ do_test main-3.2.30 { do_test main-3.3 { catch {db close} - foreach f [glob -nocomplain testdb/*] {file delete -force $f} + catch {foreach f [glob -nocomplain testdb/*] {file delete -force $f}} file delete -force testdb sqlite3 db testdb execsql { diff --git a/test/malloc_common.tcl b/test/malloc_common.tcl index 90f6b06726..c90632b0fb 100644 --- a/test/malloc_common.tcl +++ b/test/malloc_common.tcl @@ -78,7 +78,7 @@ proc do_malloc_test {tn args} { set zRepeat "transient" if {$::iRepeat} {set zRepeat "persistent"} restore_prng_state - foreach file [glob -nocomplain test.db-mj*] {file delete -force $file} + catch {foreach file [glob -nocomplain test.db-mj*] {file delete -force $file}} do_test ${tn}.${zRepeat}.${::n} { diff --git a/test/memdb.test b/test/memdb.test index d987d21e05..7b553432e5 100644 --- a/test/memdb.test +++ b/test/memdb.test @@ -413,24 +413,27 @@ do_test memdb-8.2 { # Test that auto-vacuum works with in-memory databases. # -ifcapable autovacuum { - do_test memdb-9.1 { - db close - sqlite3 db test.db - db cache size 0 - execsql { - PRAGMA auto_vacuum = full; - CREATE TABLE t1(a); - INSERT INTO t1 VALUES(randstr(1000,1000)); - INSERT INTO t1 VALUES(randstr(1000,1000)); - INSERT INTO t1 VALUES(randstr(1000,1000)); - } - set memused [lindex [sqlite3_status SQLITE_STATUS_MEMORY_USED 0] 1] - set pgovfl [lindex [sqlite3_status SQLITE_STATUS_PAGECACHE_OVERFLOW 0] 1] - execsql { DELETE FROM t1 } - set memused2 [lindex [sqlite3_status SQLITE_STATUS_MEMORY_USED 0] 1] - expr {($memused2 + 2048 < $memused) || $pgovfl==0} - } {1} +set msize [sqlite3_status SQLITE_STATUS_MALLOC_SIZE 0] +if {[lindex $msize 2]!=0} { + ifcapable autovacuum { + do_test memdb-9.1 { + db close + sqlite3 db test.db + db cache size 0 + execsql { + PRAGMA auto_vacuum = full; + CREATE TABLE t1(a); + INSERT INTO t1 VALUES(randstr(1000,1000)); + INSERT INTO t1 VALUES(randstr(1000,1000)); + INSERT INTO t1 VALUES(randstr(1000,1000)); + } + set memused [lindex [sqlite3_status SQLITE_STATUS_MEMORY_USED 0] 1] + set pgovfl [lindex [sqlite3_status SQLITE_STATUS_PAGECACHE_OVERFLOW 0] 1] + execsql { DELETE FROM t1 } + set memused2 [lindex [sqlite3_status SQLITE_STATUS_MEMORY_USED 0] 1] + expr {($memused2 + 2048 < $memused) || $pgovfl==0} + } {1} + } } } ;# ifcapable memorydb diff --git a/test/memsubsys1.test b/test/memsubsys1.test index 8524fdeb9b..c717e94905 100644 --- a/test/memsubsys1.test +++ b/test/memsubsys1.test @@ -182,17 +182,20 @@ build_test_db memsubsys1-5 {PRAGMA page_size=4096} do_test memsubsys1-5.3 { set pg_used [lindex [sqlite3_status SQLITE_STATUS_PAGECACHE_USED 0] 2] } 23 -do_test memsubsys1-5.4 { - set maxreq [lindex [sqlite3_status SQLITE_STATUS_MALLOC_SIZE 0] 2] - expr {$maxreq>4096} -} 1 -do_test memsubsys1-5.5 { - set s_used [lindex [sqlite3_status SQLITE_STATUS_SCRATCH_USED 0] 2] -} 0 -do_test memsubsys1-5.6 { - set s_ovfl [lindex [sqlite3_status SQLITE_STATUS_SCRATCH_OVERFLOW 0] 2] - expr {$s_ovfl>6000} -} 1 +set msize [sqlite3_status SQLITE_STATUS_MALLOC_SIZE 0] +if {[lindex $msize 2]!=0} { + do_test memsubsys1-5.4 { + set maxreq [lindex [sqlite3_status SQLITE_STATUS_MALLOC_SIZE 0] 2] + expr {$maxreq>4096} + } 1 + do_test memsubsys1-5.5 { + set s_used [lindex [sqlite3_status SQLITE_STATUS_SCRATCH_USED 0] 2] + } 0 + do_test memsubsys1-5.6 { + set s_ovfl [lindex [sqlite3_status SQLITE_STATUS_SCRATCH_OVERFLOW 0] 2] + expr {$s_ovfl>6000} + } 1 +} # Test 6: Activate both PAGECACHE and SCRATCH with a 4k page size. # Make it so that SCRATCH is large enough @@ -208,10 +211,13 @@ build_test_db memsubsys1-6 {PRAGMA page_size=4096} do_test memsubsys1-6.3 { set pg_used [lindex [sqlite3_status SQLITE_STATUS_PAGECACHE_USED 0] 2] } 23 -do_test memsubsys1-6.4 { - set maxreq [lindex [sqlite3_status SQLITE_STATUS_MALLOC_SIZE 0] 2] - expr {$maxreq>4096 && $maxreq<=(4096+$xtra_size)} -} 1 +set msize [sqlite3_status SQLITE_STATUS_MALLOC_SIZE 0] +if {[lindex $msize 2]!=0} { + do_test memsubsys1-6.4 { + set maxreq [lindex [sqlite3_status SQLITE_STATUS_MALLOC_SIZE 0] 2] + expr {$maxreq>4096 && $maxreq<=(4096+$xtra_size)} + } 1 +} do_test memsubsys1-6.5 { set s_used [lindex [sqlite3_status SQLITE_STATUS_SCRATCH_USED 0] 2] } 1 @@ -268,19 +274,21 @@ do_test memsubsys1-8.1 { do_test memsubsys1-8.2 { set s_ovfl [lindex [sqlite3_status SQLITE_STATUS_SCRATCH_OVERFLOW 0] 2] } 0 -do_test memsubsys1-8.3 { - sqlite3 db :memory: - db eval { - CREATE TABLE t1(x); - INSERT INTO t1 VALUES(zeroblob(400)); - INSERT INTO t1 VALUES(zeroblob(400)); - INSERT INTO t1 SELECT * FROM t1; - INSERT INTO t1 SELECT * FROM t1; - INSERT INTO t1 SELECT * FROM t1; - } - expr {[lindex [sqlite3_status SQLITE_STATUS_SCRATCH_OVERFLOW 0] 2]>0} -} 1 -db close +if {[lindex $msize 2]!=0} { + do_test memsubsys1-8.3 { + sqlite3 db :memory: + db eval { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(zeroblob(400)); + INSERT INTO t1 VALUES(zeroblob(400)); + INSERT INTO t1 SELECT * FROM t1; + INSERT INTO t1 SELECT * FROM t1; + INSERT INTO t1 SELECT * FROM t1; + } + expr {[lindex [sqlite3_status SQLITE_STATUS_SCRATCH_OVERFLOW 0] 2]>0} + } 1 + db close +} sqlite3_shutdown sqlite3_config_memstatus 0 sqlite3_initialize diff --git a/test/pragma.test b/test/pragma.test index 46f7d6d516..8eddbe019e 100644 --- a/test/pragma.test +++ b/test/pragma.test @@ -1298,6 +1298,7 @@ ifcapable lock_proxy_pragmas&&prefer_proxy_locking { set env(SQLITE_FORCE_PROXY_LOCKING) "0" sqlite3 db test.db + # set lock proxy name and then query it via pragma interface do_test pragma-16.1 { execsql { PRAGMA lock_proxy_file="mylittleproxy"; @@ -1308,6 +1309,7 @@ ifcapable lock_proxy_pragmas&&prefer_proxy_locking { } } {mylittleproxy} + # 2 database connections can share a lock proxy file do_test pragma-16.2 { sqlite3 db2 test.db execsql { @@ -1316,6 +1318,7 @@ ifcapable lock_proxy_pragmas&&prefer_proxy_locking { } {} db2 close + # 2nd database connection should auto-name an existing lock proxy file do_test pragma-16.2.1 { sqlite3 db2 test.db execsql { @@ -1328,6 +1331,7 @@ ifcapable lock_proxy_pragmas&&prefer_proxy_locking { } {mylittleproxy} db2 close + # 2nd database connection cannot override the lock proxy file do_test pragma-16.3 { sqlite3 db2 test.db execsql { @@ -1338,6 +1342,7 @@ ifcapable lock_proxy_pragmas&&prefer_proxy_locking { } db2 } {1 {database is locked}} + # lock proxy file can be renamed if no other connections are active do_test pragma-16.4 { db2 close db close @@ -1351,6 +1356,7 @@ ifcapable lock_proxy_pragmas&&prefer_proxy_locking { db2 close set env(SQLITE_FORCE_PROXY_LOCKING) "1" + # auto-naming should reuse the last proxy name when available do_test pragma-16.5 { sqlite3 db2 test.db execsql { @@ -1359,6 +1365,7 @@ ifcapable lock_proxy_pragmas&&prefer_proxy_locking { } db2 } {myotherproxy} + # auto-naming a new proxy should use a predictable & unique name do_test pragma-16.6 { db2 close sqlite3 db2 test2.db @@ -1370,6 +1377,7 @@ ifcapable lock_proxy_pragmas&&prefer_proxy_locking { } {1} set sqlite_hostid_num 2 + # db access should be limited to one host at a time (simulate 2nd host id) do_test pragma-16.7 { sqlite3 db test2.db execsql { @@ -1381,6 +1389,7 @@ ifcapable lock_proxy_pragmas&&prefer_proxy_locking { } {1 {database is locked}} db close + # default to using proxy locking (simulate network file system detection) do_test pragma-16.8 { sqlite3 db test2.db catchsql { @@ -1389,19 +1398,26 @@ ifcapable lock_proxy_pragmas&&prefer_proxy_locking { } {1 {database is locked}} db2 close + # check that db is unlocked after first host connection closes do_test pragma-16.8.1 { execsql { PRAGMA lock_proxy_file="yetanotherproxy"; + select * from sqlite_master; + } + execsql { PRAGMA lock_proxy_file; } } {yetanotherproxy} do_test pragma-16.8.2 { execsql { - create table mine(x); + create table if not exists mine(x); + insert into mine values (1); } } {} db close + file delete -force proxytest.db + file delete -force .proxytest.db-conch do_test pragma-16.9 { sqlite3 db proxytest.db set lockpath2 [execsql { @@ -1411,6 +1427,96 @@ ifcapable lock_proxy_pragmas&&prefer_proxy_locking { string match "*proxytest.db:auto:" $lockpath2 } {1} + # ensure creating directories for a lock proxy file works + db close + file delete -force proxytest + do_test pragma-16.10.1 { + sqlite3 db proxytest.db + set lockpath2 [execsql { + PRAGMA lock_proxy_file="./proxytest/sub/dir/lock"; + PRAGMA lock_proxy_file; + } db] + string match "*proxytest/sub/dir/lock" $lockpath2 + } {1} + + # ensure that after deleting the path, setting ":auto:" works correctly + db close + file delete -force proxytest + do_test pragma-16.10.2 { + sqlite3 db proxytest.db + set lockpath2 [execsql { + PRAGMA lock_proxy_file=":auto:"; + create table if not exists pt(y); + PRAGMA lock_proxy_file; + } db] + string match "*proxytest/sub/dir/lock" $lockpath2 + } {1} + + # ensure that if the path can not be created (file instead of dir) + # setting :auto: deals with it by creating a new autonamed lock file + db close + file delete -force proxytest + close [open "proxytest" a] + do_test pragma-16.10.3 { + sqlite3 db proxytest.db + set lockpath2 [execsql { + PRAGMA lock_proxy_file=":auto:"; + create table if not exists zz(y); + PRAGMA lock_proxy_file; + } db] + string match "*proxytest.db:auto:" $lockpath2 + } {1} + + # make sure we can deal with ugly file paths correctly + db close + file delete -force proxytest + do_test pragma-16.10.4 { + sqlite3 db proxytest.db + set lockpath2 [execsql { + PRAGMA lock_proxy_file="./././////./proxytest/../proxytest/sub/dir/lock"; + create table if not exists aa(bb); + PRAGMA lock_proxy_file; + } db] + string match "*proxytest/sub/dir/lock" $lockpath2 + } {1} + + # ensure that if the path can not be created (perm), setting :auto: deals + db close + file delete -force proxytest + do_test pragma-16.10.5 { + sqlite3 db proxytest.db + execsql { + PRAGMA lock_proxy_file="./proxytest/sub/dir/lock"; + create table if not exists bb(bb); + } + db close + file delete -force proxytest + file mkdir proxytest + file attributes proxytest -permission 0000 + sqlite3 db proxytest.db + set lockpath2 [execsql { + PRAGMA lock_proxy_file=":auto:"; + create table if not exists cc(bb); + PRAGMA lock_proxy_file; + } db] + string match "*proxytest.db:auto:" $lockpath2 + } {1} + + # ensure that if the path can not be created, locking fails + db close + do_test pragma-16.10.6 { + sqlite3 db proxytest.db + catchsql { + PRAGMA lock_proxy_file="./proxytest/sub/dir/lock"; + create table if not exists faily(y); + PRAGMA lock_proxy_file; + } db + } {1 {database is locked}} + db close + + file attributes proxytest -permission 0777 + file delete -force proxytest + set env(SQLITE_FORCE_PROXY_LOCKING) $using_proxy set sqlite_hostid_num 0 } diff --git a/test/quick.test b/test/quick.test index a56455a141..25c7ab7f3d 100644 --- a/test/quick.test +++ b/test/quick.test @@ -64,6 +64,7 @@ set EXCLUDE { fuzz_malloc.test in2.test loadext.test + lock_proxy.test memleak.test misc7.test misuse.test diff --git a/test/tester.tcl b/test/tester.tcl index 7651017a25..f39d781c05 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -361,11 +361,15 @@ proc finalize_testing {} { memdebug_log_sql leaks.sql } } - foreach f [glob -nocomplain test.db-*-journal] { - file delete -force $f + catch { + foreach f [glob -nocomplain test.db-*-journal] { + file delete -force $f + } } - foreach f [glob -nocomplain test.db-mj*] { - file delete -force $f + catch { + foreach f [glob -nocomplain test.db-mj*] { + file delete -force $f + } } exit [expr {$nErr>0}] } diff --git a/tool/mksqlite3c.tcl b/tool/mksqlite3c.tcl index 99dc0d3124..85fc49b907 100644 --- a/tool/mksqlite3c.tcl +++ b/tool/mksqlite3c.tcl @@ -107,6 +107,7 @@ foreach hdr { sqliteicu.h sqliteInt.h sqliteLimit.h + sqlrr.h vdbe.h vdbeInt.h } { @@ -298,7 +299,8 @@ foreach file { rtree.c icu.c - fts3_icu.c + fts3_icu.c + sqlrr.c } { copy_file tsrc/$file }