diff options
Diffstat (limited to 'Source/WebCore/Modules/webdatabase/DatabaseBackendBase.cpp')
-rw-r--r-- | Source/WebCore/Modules/webdatabase/DatabaseBackendBase.cpp | 626 |
1 files changed, 0 insertions, 626 deletions
diff --git a/Source/WebCore/Modules/webdatabase/DatabaseBackendBase.cpp b/Source/WebCore/Modules/webdatabase/DatabaseBackendBase.cpp deleted file mode 100644 index ea3e6112c..000000000 --- a/Source/WebCore/Modules/webdatabase/DatabaseBackendBase.cpp +++ /dev/null @@ -1,626 +0,0 @@ -/* - * Copyright (C) 2011 Google Inc. All rights reserved. - * Copyright (C) 2013 Apple Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "config.h" -#include "DatabaseBackendBase.h" - -#if ENABLE(SQL_DATABASE) - -#include "DatabaseAuthorizer.h" -#include "DatabaseBackendContext.h" -#include "DatabaseBase.h" -#include "DatabaseContext.h" -#include "DatabaseManager.h" -#include "DatabaseTracker.h" -#include "ExceptionCode.h" -#include "Logging.h" -#include "SQLiteDatabaseTracker.h" -#include "SQLiteStatement.h" -#include "SQLiteTransaction.h" -#include "SecurityOrigin.h" -#include <wtf/HashMap.h> -#include <wtf/HashSet.h> -#include <wtf/NeverDestroyed.h> -#include <wtf/PassRefPtr.h> -#include <wtf/RefPtr.h> -#include <wtf/StdLibExtras.h> -#include <wtf/text/CString.h> -#include <wtf/text/StringHash.h> - -// Registering "opened" databases with the DatabaseTracker -// ======================================================= -// The DatabaseTracker maintains a list of databases that have been -// "opened" so that the client can call interrupt or delete on every database -// associated with a DatabaseBackendContext. -// -// We will only call DatabaseTracker::addOpenDatabase() to add the database -// to the tracker as opened when we've succeeded in opening the database, -// and will set m_opened to true. Similarly, we only call -// DatabaseTracker::removeOpenDatabase() to remove the database from the -// tracker when we set m_opened to false in closeDatabase(). This sets up -// a simple symmetry between open and close operations, and a direct -// correlation to adding and removing databases from the tracker's list, -// thus ensuring that we have a correct list for the interrupt and -// delete operations to work on. -// -// The only databases instances not tracked by the tracker's open database -// list are the ones that have not been added yet, or the ones that we -// attempted an open on but failed to. Such instances only exist in the -// DatabaseServer's factory methods for creating database backends. -// -// The factory methods will either call openAndVerifyVersion() or -// performOpenAndVerify(). These methods will add the newly instantiated -// database backend if they succeed in opening the requested database. -// In the case of failure to open the database, the factory methods will -// simply discard the newly instantiated database backend when they return. -// The ref counting mechanims will automatically destruct the un-added -// (and un-returned) databases instances. - -namespace WebCore { - -static const char versionKey[] = "WebKitDatabaseVersionKey"; -static const char unqualifiedInfoTableName[] = "__WebKitDatabaseInfoTable__"; - -const char* DatabaseBackendBase::databaseInfoTableName() -{ - return unqualifiedInfoTableName; -} - -static const char* fullyQualifiedInfoTableName() -{ - static const char qualifier[] = "main."; - static char qualifiedName[sizeof(qualifier) + sizeof(unqualifiedInfoTableName) - 1]; - - static std::once_flag onceFlag; - std::call_once(onceFlag, []{ - strcpy(qualifiedName, qualifier); - strcpy(qualifiedName + strlen(qualifier), unqualifiedInfoTableName); - }); - - return qualifiedName; -} - -static String formatErrorMessage(const char* message, int sqliteErrorCode, const char* sqliteErrorMessage) -{ - return String::format("%s (%d %s)", message, sqliteErrorCode, sqliteErrorMessage); -} - -static bool retrieveTextResultFromDatabase(SQLiteDatabase& db, const String& query, String& resultString) -{ - SQLiteStatement statement(db, query); - int result = statement.prepare(); - - if (result != SQLResultOk) { - LOG_ERROR("Error (%i) preparing statement to read text result from database (%s)", result, query.ascii().data()); - return false; - } - - result = statement.step(); - if (result == SQLResultRow) { - resultString = statement.getColumnText(0); - return true; - } - if (result == SQLResultDone) { - resultString = String(); - return true; - } - - LOG_ERROR("Error (%i) reading text result from database (%s)", result, query.ascii().data()); - return false; -} - -static bool setTextValueInDatabase(SQLiteDatabase& db, const String& query, const String& value) -{ - SQLiteStatement statement(db, query); - int result = statement.prepare(); - - if (result != SQLResultOk) { - LOG_ERROR("Failed to prepare statement to set value in database (%s)", query.ascii().data()); - return false; - } - - statement.bindText(1, value); - - result = statement.step(); - if (result != SQLResultDone) { - LOG_ERROR("Failed to step statement to set value in database (%s)", query.ascii().data()); - return false; - } - - return true; -} - -// FIXME: move all guid-related functions to a DatabaseVersionTracker class. -static std::mutex& guidMutex() -{ - static std::once_flag onceFlag; - static std::mutex* mutex; - - std::call_once(onceFlag, []{ - mutex = std::make_unique<std::mutex>().release(); - }); - - return *mutex; -} - -typedef HashMap<DatabaseGuid, String> GuidVersionMap; -static GuidVersionMap& guidToVersionMap() -{ - // Ensure the the mutex is locked. - ASSERT(!guidMutex().try_lock()); - - static NeverDestroyed<GuidVersionMap> map; - return map; -} - -// NOTE: Caller must lock guidMutex(). -static inline void updateGuidVersionMap(DatabaseGuid guid, String newVersion) -{ - // Ensure the the mutex is locked. - ASSERT(!guidMutex().try_lock()); - - // Note: It is not safe to put an empty string into the guidToVersionMap() map. - // That's because the map is cross-thread, but empty strings are per-thread. - // The copy() function makes a version of the string you can use on the current - // thread, but we need a string we can keep in a cross-thread data structure. - // FIXME: This is a quite-awkward restriction to have to program with. - - // Map null string to empty string (see comment above). - guidToVersionMap().set(guid, newVersion.isEmpty() ? String() : newVersion.isolatedCopy()); -} - -typedef HashMap<DatabaseGuid, std::unique_ptr<HashSet<DatabaseBackendBase*>>> GuidDatabaseMap; - -static GuidDatabaseMap& guidToDatabaseMap() -{ - // Ensure the the mutex is locked. - ASSERT(!guidMutex().try_lock()); - - static NeverDestroyed<GuidDatabaseMap> map; - return map; -} - -static DatabaseGuid guidForOriginAndName(const String& origin, const String& name) -{ - // Ensure the the mutex is locked. - ASSERT(!guidMutex().try_lock()); - - String stringID = origin + "/" + name; - - typedef HashMap<String, int> IDGuidMap; - static NeverDestroyed<HashMap<String, int>> map; - DatabaseGuid guid = map.get().get(stringID); - if (!guid) { - static int currentNewGUID = 1; - guid = currentNewGUID++; - map.get().set(stringID, guid); - } - - return guid; -} - -#if !LOG_DISABLED || !ERROR_DISABLED -String DatabaseBackendBase::databaseDebugName() const -{ - return m_contextThreadSecurityOrigin->toString() + "::" + m_name; -} -#endif - -DatabaseBackendBase::DatabaseBackendBase(PassRefPtr<DatabaseBackendContext> databaseContext, const String& name, - const String& expectedVersion, const String& displayName, unsigned long estimatedSize, DatabaseType databaseType) - : m_databaseContext(databaseContext) - , m_name(name.isolatedCopy()) - , m_expectedVersion(expectedVersion.isolatedCopy()) - , m_displayName(displayName.isolatedCopy()) - , m_estimatedSize(estimatedSize) - , m_opened(false) - , m_new(false) - , m_isSyncDatabase(databaseType == DatabaseType::Sync) -{ - m_contextThreadSecurityOrigin = m_databaseContext->securityOrigin()->isolatedCopy(); - - m_databaseAuthorizer = DatabaseAuthorizer::create(unqualifiedInfoTableName); - - if (m_name.isNull()) - m_name = emptyString(); - - { - std::lock_guard<std::mutex> locker(guidMutex()); - - m_guid = guidForOriginAndName(securityOrigin()->toString(), name); - std::unique_ptr<HashSet<DatabaseBackendBase*>>& hashSet = guidToDatabaseMap().add(m_guid, nullptr).iterator->value; - if (!hashSet) - hashSet = std::make_unique<HashSet<DatabaseBackendBase*>>(); - hashSet->add(this); - } - - m_filename = DatabaseManager::manager().fullPathForDatabase(securityOrigin(), m_name); -} - -DatabaseBackendBase::~DatabaseBackendBase() -{ - // SQLite is "multi-thread safe", but each database handle can only be used - // on a single thread at a time. - // - // For DatabaseBackend, we open the SQLite database on the DatabaseThread, - // and hence we should also close it on that same thread. This means that the - // SQLite database need to be closed by another mechanism (see - // DatabaseContext::stopDatabases()). By the time we get here, the SQLite - // database should have already been closed. - - ASSERT(!m_opened); -} - -void DatabaseBackendBase::closeDatabase() -{ - if (!m_opened) - return; - - m_sqliteDatabase.close(); - m_opened = false; - // See comment at the top this file regarding calling removeOpenDatabase(). - DatabaseTracker::tracker().removeOpenDatabase(this); - { - std::lock_guard<std::mutex> locker(guidMutex()); - - auto it = guidToDatabaseMap().find(m_guid); - ASSERT(it != guidToDatabaseMap().end()); - ASSERT(it->value); - ASSERT(it->value->contains(this)); - it->value->remove(this); - if (it->value->isEmpty()) { - guidToDatabaseMap().remove(it); - guidToVersionMap().remove(m_guid); - } - } -} - -String DatabaseBackendBase::version() const -{ - // Note: In multi-process browsers the cached value may be accurate, but we cannot read the - // actual version from the database without potentially inducing a deadlock. - // FIXME: Add an async version getter to the DatabaseAPI. - return getCachedVersion(); -} - -class DoneCreatingDatabaseOnExitCaller { -public: - DoneCreatingDatabaseOnExitCaller(DatabaseBackendBase* database) - : m_database(database) - , m_openSucceeded(false) - { - } - ~DoneCreatingDatabaseOnExitCaller() - { - DatabaseTracker::tracker().doneCreatingDatabase(m_database); - } - - void setOpenSucceeded() { m_openSucceeded = true; } - -private: - DatabaseBackendBase* m_database; - bool m_openSucceeded; -}; - -bool DatabaseBackendBase::performOpenAndVerify(bool shouldSetVersionInNewDatabase, DatabaseError& error, String& errorMessage) -{ - DoneCreatingDatabaseOnExitCaller onExitCaller(this); - ASSERT(errorMessage.isEmpty()); - ASSERT(error == DatabaseError::None); // Better not have any errors already. - error = DatabaseError::InvalidDatabaseState; // Presumed failure. We'll clear it if we succeed below. - - const int maxSqliteBusyWaitTime = 30000; - -#if PLATFORM(IOS) - { - // Make sure we wait till the background removal of the empty database files finished before trying to open any database. - MutexLocker locker(DatabaseTracker::openDatabaseMutex()); - } -#endif - - SQLiteTransactionInProgressAutoCounter transactionCounter; - - if (!m_sqliteDatabase.open(m_filename, true)) { - errorMessage = formatErrorMessage("unable to open database", m_sqliteDatabase.lastError(), m_sqliteDatabase.lastErrorMsg()); - return false; - } - if (!m_sqliteDatabase.turnOnIncrementalAutoVacuum()) - LOG_ERROR("Unable to turn on incremental auto-vacuum (%d %s)", m_sqliteDatabase.lastError(), m_sqliteDatabase.lastErrorMsg()); - - m_sqliteDatabase.setBusyTimeout(maxSqliteBusyWaitTime); - - String currentVersion; - { - std::lock_guard<std::mutex> locker(guidMutex()); - - auto entry = guidToVersionMap().find(m_guid); - if (entry != guidToVersionMap().end()) { - // Map null string to empty string (see updateGuidVersionMap()). - currentVersion = entry->value.isNull() ? emptyString() : entry->value.isolatedCopy(); - LOG(StorageAPI, "Current cached version for guid %i is %s", m_guid, currentVersion.ascii().data()); - } else { - LOG(StorageAPI, "No cached version for guid %i", m_guid); - - SQLiteTransaction transaction(m_sqliteDatabase); - transaction.begin(); - if (!transaction.inProgress()) { - errorMessage = formatErrorMessage("unable to open database, failed to start transaction", m_sqliteDatabase.lastError(), m_sqliteDatabase.lastErrorMsg()); - m_sqliteDatabase.close(); - return false; - } - - String tableName(unqualifiedInfoTableName); - if (!m_sqliteDatabase.tableExists(tableName)) { - m_new = true; - - if (!m_sqliteDatabase.executeCommand("CREATE TABLE " + tableName + " (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) { - errorMessage = formatErrorMessage("unable to open database, failed to create 'info' table", m_sqliteDatabase.lastError(), m_sqliteDatabase.lastErrorMsg()); - transaction.rollback(); - m_sqliteDatabase.close(); - return false; - } - } else if (!getVersionFromDatabase(currentVersion, false)) { - errorMessage = formatErrorMessage("unable to open database, failed to read current version", m_sqliteDatabase.lastError(), m_sqliteDatabase.lastErrorMsg()); - transaction.rollback(); - m_sqliteDatabase.close(); - return false; - } - - if (currentVersion.length()) { - LOG(StorageAPI, "Retrieved current version %s from database %s", currentVersion.ascii().data(), databaseDebugName().ascii().data()); - } else if (!m_new || shouldSetVersionInNewDatabase) { - LOG(StorageAPI, "Setting version %s in database %s that was just created", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data()); - if (!setVersionInDatabase(m_expectedVersion, false)) { - errorMessage = formatErrorMessage("unable to open database, failed to write current version", m_sqliteDatabase.lastError(), m_sqliteDatabase.lastErrorMsg()); - transaction.rollback(); - m_sqliteDatabase.close(); - return false; - } - currentVersion = m_expectedVersion; - } - updateGuidVersionMap(m_guid, currentVersion); - transaction.commit(); - } - } - - if (currentVersion.isNull()) { - LOG(StorageAPI, "Database %s does not have its version set", databaseDebugName().ascii().data()); - currentVersion = ""; - } - - // If the expected version isn't the empty string, ensure that the current database version we have matches that version. Otherwise, set an exception. - // If the expected version is the empty string, then we always return with whatever version of the database we have. - if ((!m_new || shouldSetVersionInNewDatabase) && m_expectedVersion.length() && m_expectedVersion != currentVersion) { - errorMessage = "unable to open database, version mismatch, '" + m_expectedVersion + "' does not match the currentVersion of '" + currentVersion + "'"; - m_sqliteDatabase.close(); - return false; - } - - ASSERT(m_databaseAuthorizer); - m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer); - - // See comment at the top this file regarding calling addOpenDatabase(). - DatabaseTracker::tracker().addOpenDatabase(this); - m_opened = true; - - // Declare success: - error = DatabaseError::None; // Clear the presumed error from above. - onExitCaller.setOpenSucceeded(); - - if (m_new && !shouldSetVersionInNewDatabase) - m_expectedVersion = ""; // The caller provided a creationCallback which will set the expected version. - return true; -} - -SecurityOrigin* DatabaseBackendBase::securityOrigin() const -{ - return m_contextThreadSecurityOrigin.get(); -} - -String DatabaseBackendBase::stringIdentifier() const -{ - // Return a deep copy for ref counting thread safety - return m_name.isolatedCopy(); -} - -String DatabaseBackendBase::displayName() const -{ - // Return a deep copy for ref counting thread safety - return m_displayName.isolatedCopy(); -} - -unsigned long DatabaseBackendBase::estimatedSize() const -{ - return m_estimatedSize; -} - -String DatabaseBackendBase::fileName() const -{ - // Return a deep copy for ref counting thread safety - return m_filename.isolatedCopy(); -} - -DatabaseDetails DatabaseBackendBase::details() const -{ - // This code path is only used for database quota delegate calls, so file dates are irrelevant and left uninitialized. - return DatabaseDetails(stringIdentifier(), displayName(), estimatedSize(), 0, 0, 0); -} - -bool DatabaseBackendBase::getVersionFromDatabase(String& version, bool shouldCacheVersion) -{ - String query(String("SELECT value FROM ") + fullyQualifiedInfoTableName() + " WHERE key = '" + versionKey + "';"); - - m_databaseAuthorizer->disable(); - - bool result = retrieveTextResultFromDatabase(m_sqliteDatabase, query, version); - if (result) { - if (shouldCacheVersion) - setCachedVersion(version); - } else - LOG_ERROR("Failed to retrieve version from database %s", databaseDebugName().ascii().data()); - - m_databaseAuthorizer->enable(); - - return result; -} - -bool DatabaseBackendBase::setVersionInDatabase(const String& version, bool shouldCacheVersion) -{ - // The INSERT will replace an existing entry for the database with the new version number, due to the UNIQUE ON CONFLICT REPLACE - // clause in the CREATE statement (see Database::performOpenAndVerify()). - String query(String("INSERT INTO ") + fullyQualifiedInfoTableName() + " (key, value) VALUES ('" + versionKey + "', ?);"); - - m_databaseAuthorizer->disable(); - - bool result = setTextValueInDatabase(m_sqliteDatabase, query, version); - if (result) { - if (shouldCacheVersion) - setCachedVersion(version); - } else - LOG_ERROR("Failed to set version %s in database (%s)", version.ascii().data(), query.ascii().data()); - - m_databaseAuthorizer->enable(); - - return result; -} - -void DatabaseBackendBase::setExpectedVersion(const String& version) -{ - m_expectedVersion = version.isolatedCopy(); -} - -String DatabaseBackendBase::getCachedVersion() const -{ - std::lock_guard<std::mutex> locker(guidMutex()); - - return guidToVersionMap().get(m_guid).isolatedCopy(); -} - -void DatabaseBackendBase::setCachedVersion(const String& actualVersion) -{ - // Update the in memory database version map. - std::lock_guard<std::mutex> locker(guidMutex()); - - updateGuidVersionMap(m_guid, actualVersion); -} - -bool DatabaseBackendBase::getActualVersionForTransaction(String &actualVersion) -{ - ASSERT(m_sqliteDatabase.transactionInProgress()); - // Note: In multi-process browsers the cached value may be inaccurate. - // So we retrieve the value from the database and update the cached value here. - return getVersionFromDatabase(actualVersion, true); -} - -void DatabaseBackendBase::disableAuthorizer() -{ - ASSERT(m_databaseAuthorizer); - m_databaseAuthorizer->disable(); -} - -void DatabaseBackendBase::enableAuthorizer() -{ - ASSERT(m_databaseAuthorizer); - m_databaseAuthorizer->enable(); -} - -void DatabaseBackendBase::setAuthorizerReadOnly() -{ - ASSERT(m_databaseAuthorizer); - m_databaseAuthorizer->setReadOnly(); -} - -void DatabaseBackendBase::setAuthorizerPermissions(int permissions) -{ - ASSERT(m_databaseAuthorizer); - m_databaseAuthorizer->setPermissions(permissions); -} - -bool DatabaseBackendBase::lastActionChangedDatabase() -{ - ASSERT(m_databaseAuthorizer); - return m_databaseAuthorizer->lastActionChangedDatabase(); -} - -bool DatabaseBackendBase::lastActionWasInsert() -{ - ASSERT(m_databaseAuthorizer); - return m_databaseAuthorizer->lastActionWasInsert(); -} - -void DatabaseBackendBase::resetDeletes() -{ - ASSERT(m_databaseAuthorizer); - m_databaseAuthorizer->resetDeletes(); -} - -bool DatabaseBackendBase::hadDeletes() -{ - ASSERT(m_databaseAuthorizer); - return m_databaseAuthorizer->hadDeletes(); -} - -void DatabaseBackendBase::resetAuthorizer() -{ - if (m_databaseAuthorizer) - m_databaseAuthorizer->reset(); -} - -unsigned long long DatabaseBackendBase::maximumSize() const -{ - return DatabaseTracker::tracker().getMaxSizeForDatabase(this); -} - -void DatabaseBackendBase::incrementalVacuumIfNeeded() -{ - SQLiteTransactionInProgressAutoCounter transactionCounter; - - int64_t freeSpaceSize = m_sqliteDatabase.freeSpaceSize(); - int64_t totalSize = m_sqliteDatabase.totalSize(); - if (totalSize <= 10 * freeSpaceSize) { - int result = m_sqliteDatabase.runIncrementalVacuumCommand(); - if (result != SQLResultOk) - m_frontend->logErrorMessage(formatErrorMessage("error vacuuming database", result, m_sqliteDatabase.lastErrorMsg())); - } -} - -void DatabaseBackendBase::interrupt() -{ - m_sqliteDatabase.interrupt(); -} - -bool DatabaseBackendBase::isInterrupted() -{ - MutexLocker locker(m_sqliteDatabase.databaseMutex()); - return m_sqliteDatabase.isInterrupted(); -} - -} // namespace WebCore - -#endif // ENABLE(SQL_DATABASE) |