/************************************************************************* * UrBackup - Client/Server backup system * Copyright (C) 2011-2016 Martin Raiber * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . **************************************************************************/ #ifndef NO_SQLITE #if defined(_WIN32) || defined(WIN32) #define _CRT_SECURE_NO_WARNINGS #endif #include "vld.h" #ifndef BDBPLUGIN #include "Server.h" #else #ifdef LINUX #include "bdbplugin/config.h" #include DB_HEADER #else #include #endif #include "Interface/Server.h" #endif #include "Query.h" #ifdef USE_SYSTEM_SQLITE #include #else #include "sqlite/sqlite3.h" #endif #include "Interface/File.h" #include extern "C" { #include "sqlite/shell.h" } #include "Database.h" #include "stringtools.h" namespace { size_t get_sqlite_cache_size() { std::string cache_size_str = Server->getServerParameter("sqlite_cache_size"); if(!cache_size_str.empty()) { return atoi(cache_size_str.c_str()); } else { return 2*1024; //2MB } } void errorLogCallback(void *pArg, int iErrCode, const char *zMsg) { switch (iErrCode) { case SQLITE_LOCKED: case SQLITE_BUSY: case SQLITE_SCHEMA: return; case SQLITE_NOTICE_RECOVER_ROLLBACK: case SQLITE_NOTICE_RECOVER_WAL: Server->Log("SQLite: "+ std::string(zMsg) + " code: " + convert(iErrCode), LL_INFO); break; default: Server->Log("SQLite: " + std::string(zMsg) + " errorcode: " + convert(iErrCode), LL_WARNING); break; } } } struct UnlockNotification { bool fired; ICondition* cond; IMutex *mutex; }; static void unlock_notify_cb(void **apArg, int nArg) { for(int i=0; imutex); p->fired = true; p->cond->notify_all(); } } CDatabase::~CDatabase() { destroyAllQueries(); for(std::map::iterator iter=prepared_queries.begin();iter!=prepared_queries.end();++iter) { CQuery *q=(CQuery*)iter->second; delete q; } prepared_queries.clear(); sqlite3_close(db); } bool CDatabase::Open(std::string pFile, const std::vector > &attach, size_t allocation_chunk_size, ISharedMutex* p_single_user_mutex, IMutex* p_lock_mutex, int* p_lock_count, ICondition *p_unlock_cond, const str_map& p_params) { single_user_mutex = p_single_user_mutex; lock_mutex = p_lock_mutex; lock_count = p_lock_count; unlock_cond = p_unlock_cond; params = p_params; attached_dbs=attach; in_transaction=false; if( sqlite3_open(pFile.c_str(), &db) ) { Server->Log("Could not open db ["+pFile+"]"); sqlite3_close(db); db = NULL; return false; } else { str_map::const_iterator it = params.find("synchronous"); if (it != params.end()) { Write("PRAGMA synchronous="+it->second); } else { Write("PRAGMA synchronous=NORMAL"); } Write("PRAGMA foreign_keys = ON"); Write("PRAGMA threads = 2"); it = params.find("wal_autocheckpoint"); if (it != params.end()) { Write("PRAGMA wal_autocheckpoint=" + it->second); if (watoi(it->second)<=0) { int enable = 1; sqlite3_file_control(db, NULL, SQLITE_FCNTL_PERSIST_WAL, &enable); #ifdef SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE int was_enabled = 0; sqlite3_db_config(db, SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, 1, &was_enabled); #endif } } it = params.find("page_size"); if (it != params.end()) { Write("PRAGMA page_size=" + it->second); } else { Write("PRAGMA page_size=4096"); } it = params.find("mmap_size"); if (it != params.end()) { Write("PRAGMA mmap_size=" + it->second); } if(allocation_chunk_size!=std::string::npos) { int chunk_size = static_cast(allocation_chunk_size); sqlite3_file_control(db, NULL, SQLITE_FCNTL_CHUNK_SIZE, &chunk_size); } static size_t sqlite_cache_size = get_sqlite_cache_size(); Write("PRAGMA cache_size = -"+convert(sqlite_cache_size)); sqlite3_busy_timeout(db, c_sqlite_busy_timeout_default); #if defined(_DEBUG) || (!defined(_WIN32) && !defined(NDEBUG)) if (Server->getRandomNumber() % 2 == 0) { Write("PRAGMA reverse_unordered_selects = ON"); } #endif AttachDBs(); return true; } } void CDatabase::initMutex(void) { sqlite3_config(SQLITE_CONFIG_LOG, errorLogCallback, NULL); } void CDatabase::destroyMutex(void) { } db_results CDatabase::Read(std::string pQuery) { //Server->Log("SQL Query(Read): "+pQuery, LL_DEBUG); IQuery *q=Prepare(pQuery, false); if(q!=NULL) { db_results ret=q->Read(); delete ((CQuery*)q); return ret; } return db_results(); } bool CDatabase::Write(std::string pQuery) { //Server->Log("SQL Query(Write): "+pQuery, LL_DEBUG); IQuery *q=Prepare(pQuery, false); if(q!=NULL) { bool b=q->Write(); delete ((CQuery*)q); return b; } else { return false; } } //ToDo: Cache Writings bool CDatabase::BeginReadTransaction() { if (write_lock.get() == NULL) { transaction_read_lock.reset(new IScopedReadLock(single_user_mutex)); } in_transaction = true; if(Write("BEGIN")) { return true; } else { in_transaction = false; return false; } } bool CDatabase::BeginWriteTransaction() { if (write_lock.get() == NULL) { transaction_read_lock.reset(new IScopedReadLock(single_user_mutex)); } in_transaction = true; if(Write("BEGIN IMMEDIATE;")) { return true; } else { in_transaction = false; return false; } } bool CDatabase::EndTransaction(void) { bool ret = Write("END;"); in_transaction=false; transaction_read_lock.reset(); IScopedLock lock(lock_mutex); bool waited=false; while(*lock_count>0) { unlock_cond->wait(&lock); waited=true; } if(waited) { Server->wait(50); } return ret; } bool CDatabase::RollbackTransaction() { bool ret = Write("ROLLBACK;"); in_transaction = false; transaction_read_lock.reset(); IScopedLock lock(lock_mutex); bool waited = false; while (*lock_count>0) { unlock_cond->wait(&lock); waited = true; } if (waited) { Server->wait(50); } return ret; } IQuery* CDatabase::Prepare(std::string pQuery, bool autodestroy) { IScopedReadLock lock(NULL); if (!in_transaction && write_lock.get()==NULL) { lock.relock(single_user_mutex); } int prepare_tries = 0; #ifdef SQLITE_PREPARE_RETRIES prepare_tries = SQLITE_PREPARE_RETRIES; #endif sqlite3_stmt *prepared_statement; const char* tail; int err; bool transaction_lock=false; while((err=sqlite3_prepare_v2(db, pQuery.c_str(), (int)pQuery.size(), &prepared_statement, &tail) )==SQLITE_LOCKED || err==SQLITE_BUSY || err==SQLITE_PROTOCOL || (err!=SQLITE_OK && prepare_tries>0) ) { --prepare_tries; if(err==SQLITE_LOCKED) { if(!transaction_lock && LockForTransaction()) { transaction_lock=true; if(!WaitForUnlock()) Server->Log("DATABASE DEADLOCKED in CDatabase::Prepare", LL_ERROR); } } else if(err== SQLITE_BUSY || err==SQLITE_PROTOCOL) { if(!transaction_lock) { if(!isInTransaction() && LockForTransaction()) { transaction_lock=true; } sqlite3_busy_timeout(db, 10000); } else { Server->Log("DATABASE BUSY in CDatabase::Prepare", LL_ERROR); } } else { Server->Log("Error preparing Query [" + pQuery + "]: " + sqlite3_errmsg(db)+". Retrying in 1s...", LL_ERROR); Server->wait(1000); } } if(transaction_lock) { UnlockForTransaction(); sqlite3_busy_timeout(db, 50); } if( err!=SQLITE_OK ) { Server->Log("Error preparing Query ["+pQuery+"]: "+sqlite3_errmsg(db),LL_ERROR); if(err==SQLITE_IOERR) { Server->setFailBit(IServer::FAIL_DATABASE_IOERR); } if(err==SQLITE_CORRUPT) { Server->setFailBit(IServer::FAIL_DATABASE_CORRUPTED); } if (err ==SQLITE_FULL) { Server->setFailBit(IServer::FAIL_DATABASE_FULL); } return NULL; } CQuery *q=new CQuery(pQuery, prepared_statement, this); if( autodestroy ) { queries.push_back(q); } return q; } IQuery* CDatabase::Prepare(int id, std::string pQuery) { IScopedReadLock lock(NULL); if (!in_transaction && write_lock.get()==NULL) { lock.relock(single_user_mutex); } std::map::iterator iter=prepared_queries.find(id); if( iter!=prepared_queries.end() ) { iter->second->Reset(); return iter->second; } else { IQuery *q=Prepare(pQuery, false); prepared_queries.insert(std::pair(id, q) ); return q; } } void CDatabase::destroyQuery(IQuery *q) { if(q==NULL) { return; } for(size_t i=0;icreateMutex(); un.cond=Server->createCondition(); rc = sqlite3_unlock_notify(db, unlock_notify_cb, (void *)&un); if( rc==SQLITE_OK ) { IScopedLock lock(un.mutex); if( !un.fired ) { un.cond->wait(&lock); } } Server->destroy(un.mutex); Server->destroy(un.cond); return rc==SQLITE_OK; #else return false; #endif } sqlite3 *CDatabase::getDatabase(void) { return db; } bool CDatabase::LockForTransaction(void) { lock_mutex->Lock(); ++*lock_count; return true; } void CDatabase::UnlockForTransaction(void) { --*lock_count; unlock_cond->notify_all(); lock_mutex->Unlock(); } bool CDatabase::isInTransaction(void) { return in_transaction; } bool CDatabase::Import(const std::string &pFile) { IFile *file=Server->openFile(pFile, MODE_READ); if(file==NULL) return false; unsigned int r; char buf[4096]; std::string query; int state=0; do { r=file->Read(buf, 4096); for(unsigned int i=0;i0); Server->destroy(file); return true; } bool CDatabase::Dump(const std::string &pFile) { const char* db_fn = sqlite3_db_filename(db, NULL); if (db_fn == NULL) return false; ShellState cd = {}; cd.openMode = 1; cd.zDbFilename = db_fn; cd.out=fopen(pFile.c_str(), "wb"); if(cd.out==0) { return false; } std::string cmd = ".dump"; int rc = do_meta_command_r(&cmd[0], &cd); fclose(cd.out); if (cd.db != 0) sqlite3_close(cd.db); return rc == SQLITE_OK; } bool CDatabase::Recover(const std::string & pFile) { const char* db_fn = sqlite3_db_filename(db, NULL); if (db_fn == NULL) return false; ShellState cd = {}; cd.openMode = 1; cd.zDbFilename = db_fn; cd.out = fopen(pFile.c_str(), "wb"); if (cd.out == 0) { return false; } std::string cmd = ".recover"; int rc = do_meta_command_r(&cmd[0], &cd); fclose(cd.out); if (cd.db != 0) sqlite3_close(cd.db); return rc==SQLITE_OK; } std::string CDatabase::getEngineName(void) { #ifndef BDBPLUGIN return "sqlite"; #else return "bdb"; #endif } void CDatabase::AttachDBs(void) { for(size_t i=0;ibackupProgress(done*page_size, total*page_size); } while( rc==SQLITE_OK || rc==SQLITE_BUSY || rc==SQLITE_PROTOCOL || rc==SQLITE_LOCKED ); /* Release resources allocated by backup_init(). */ (void)sqlite3_backup_finish(pBackup); } else { Server->Log("Opening backup connection failed", LL_ERROR); } rc = sqlite3_errcode(pBackupDB); if(rc!=0) { Server->Log("Database backup failed with error code: "+convert(rc)+" err: "+sqlite3_errmsg(pBackupDB), LL_ERROR); } } /* Close the database connection opened on database file zFilename ** and return the result of this function. */ (void)sqlite3_close(pBackupDB); return rc==0; } bool CDatabase::Backup(const std::string &pFile, IBackupProgress* progress) { std::string path=ExtractFilePath(pFile); bool b=backup_db(pFile, "main", progress); if(!b) return false; for(size_t i=0;i