/* * 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 */