| /* |
| * 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 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" |
| |
| #include "Database.h" |
| #include "DatabaseAuthorizer.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 DatabaseContext. |
| // |
| // 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 infoTableName[] = "__WebKitDatabaseInfoTable__"; |
| |
| 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 LazyNeverDestroyed<std::mutex> mutex; |
| |
| std::call_once(onceFlag, []{ |
| mutex.construct(); |
| }); |
| |
| return mutex; |
| } |
| |
| typedef HashMap<DatabaseGuid, String> GuidVersionMap; |
| static GuidVersionMap& guidToVersionMap() |
| { |
| static NeverDestroyed<GuidVersionMap> map; |
| return map; |
| } |
| |
| // NOTE: Caller must lock guidMutex(). |
| static inline void updateGuidVersionMap(DatabaseGuid guid, String newVersion) |
| { |
| // 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() |
| { |
| static NeverDestroyed<GuidDatabaseMap> map; |
| return map; |
| } |
| |
| static DatabaseGuid guidForOriginAndName(const String& origin, const String& name) |
| { |
| String stringID = origin + "/" + name; |
| |
| 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; |
| } |
| |
| // static |
| const char* DatabaseBackendBase::databaseInfoTableName() |
| { |
| return infoTableName; |
| } |
| |
| #if !LOG_DISABLED || !ERROR_DISABLED |
| String DatabaseBackendBase::databaseDebugName() const |
| { |
| return m_contextThreadSecurityOrigin->toString() + "::" + m_name; |
| } |
| #endif |
| |
| DatabaseBackendBase::DatabaseBackendBase(PassRefPtr<DatabaseContext> databaseContext, const String& name, const String& expectedVersion, const String& displayName, unsigned long estimatedSize) |
| : 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_contextThreadSecurityOrigin = m_databaseContext->securityOrigin()->isolatedCopy(); |
| |
| m_databaseAuthorizer = DatabaseAuthorizer::create(infoTableName); |
| |
| 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(static_cast<Database*>(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(static_cast<Database*>(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(infoTableName); |
| 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(static_cast<Database*>(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 ") + infoTableName + " 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 ") + infoTableName + " (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::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(static_cast<const Database*>(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 |