diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2016-04-10 09:28:39 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2016-04-10 09:28:39 +0000 |
commit | 32761a6cee1d0dee366b885b7b9c777e67885688 (patch) | |
tree | d6bec92bebfb216f4126356e55518842c2f476a1 /Source/WebCore/Modules/webdatabase/DatabaseBackendBase.cpp | |
parent | a4e969f4965059196ca948db781e52f7cfebf19e (diff) | |
download | WebKitGtk-tarball-32761a6cee1d0dee366b885b7b9c777e67885688.tar.gz |
webkitgtk-2.4.11webkitgtk-2.4.11
Diffstat (limited to 'Source/WebCore/Modules/webdatabase/DatabaseBackendBase.cpp')
-rw-r--r-- | Source/WebCore/Modules/webdatabase/DatabaseBackendBase.cpp | 626 |
1 files changed, 626 insertions, 0 deletions
diff --git a/Source/WebCore/Modules/webdatabase/DatabaseBackendBase.cpp b/Source/WebCore/Modules/webdatabase/DatabaseBackendBase.cpp new file mode 100644 index 000000000..ea3e6112c --- /dev/null +++ b/Source/WebCore/Modules/webdatabase/DatabaseBackendBase.cpp @@ -0,0 +1,626 @@ +/* + * 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) |