DatabaseTracker.cpp [plain text]
#include "config.h"
#include "DatabaseTracker.h"
#include "Database.h"
#include "DatabaseContext.h"
#include "DatabaseManager.h"
#include "DatabaseManagerClient.h"
#include "DatabaseThread.h"
#include "FileSystem.h"
#include "Logging.h"
#include "OriginLock.h"
#include "SecurityOrigin.h"
#include "SecurityOriginData.h"
#include "SecurityOriginHash.h"
#include "SQLiteFileSystem.h"
#include "SQLiteStatement.h"
#include "SQLiteTransaction.h"
#include <wtf/MainThread.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/StdLibExtras.h>
#include <wtf/UUID.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringBuilder.h>
#if PLATFORM(IOS_FAMILY)
#include "WebCoreThread.h"
#endif
namespace WebCore {
static Vector<String> isolatedCopy(const Vector<String>& original)
{
Vector<String> copy;
copy.reserveInitialCapacity(original.size());
for (auto& string : original)
copy.uncheckedAppend(string.isolatedCopy());
return copy;
}
std::unique_ptr<DatabaseTracker> DatabaseTracker::trackerWithDatabasePath(const String& databasePath)
{
return std::unique_ptr<DatabaseTracker>(new DatabaseTracker(databasePath));
}
static DatabaseTracker* staticTracker = nullptr;
void DatabaseTracker::initializeTracker(const String& databasePath)
{
ASSERT(!staticTracker);
if (staticTracker)
return;
staticTracker = new DatabaseTracker(databasePath);
}
DatabaseTracker& DatabaseTracker::singleton()
{
if (!staticTracker)
staticTracker = new DatabaseTracker(emptyString());
return *staticTracker;
}
DatabaseTracker::DatabaseTracker(const String& databasePath)
: m_databaseDirectoryPath(databasePath.isolatedCopy())
{
}
String DatabaseTracker::trackerDatabasePath() const
{
return SQLiteFileSystem::appendDatabaseFileNameToPath(m_databaseDirectoryPath.isolatedCopy(), "Databases.db");
}
void DatabaseTracker::openTrackerDatabase(TrackerCreationAction createAction)
{
ASSERT(!m_databaseGuard.tryLock());
if (m_database.isOpen())
return;
String databasePath = trackerDatabasePath();
if (!SQLiteFileSystem::ensureDatabaseFileExists(databasePath, createAction == CreateIfDoesNotExist))
return;
if (!m_database.open(databasePath)) {
LOG_ERROR("Failed to open databasePath %s.", databasePath.utf8().data());
return;
}
m_database.disableThreadingChecks();
if (!m_database.tableExists("Origins")) {
if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, quota INTEGER NOT NULL ON CONFLICT FAIL);")) {
LOG_ERROR("Failed to create Origins table");
}
}
if (!m_database.tableExists("Databases")) {
if (!m_database.executeCommand("CREATE TABLE Databases (guid INTEGER PRIMARY KEY AUTOINCREMENT, origin TEXT, name TEXT, displayName TEXT, estimatedSize INTEGER, path TEXT);")) {
LOG_ERROR("Failed to create Databases table");
}
}
}
ExceptionOr<void> DatabaseTracker::hasAdequateQuotaForOrigin(const SecurityOriginData& origin, unsigned long long estimatedSize)
{
ASSERT(!m_databaseGuard.tryLock());
auto usage = this->usage(origin);
auto requirement = usage + std::max<unsigned long long>(1, estimatedSize);
if (requirement < usage) {
return Exception { SecurityError };
}
if (requirement > quotaNoLock(origin))
return Exception { QuotaExceededError };
return { };
}
ExceptionOr<void> DatabaseTracker::canEstablishDatabase(DatabaseContext& context, const String& name, unsigned long long estimatedSize)
{
LockHolder lockDatabase(m_databaseGuard);
auto origin = context.securityOrigin();
if (isDeletingDatabaseOrOriginFor(origin, name))
return Exception { SecurityError };
recordCreatingDatabase(origin, name);
if (hasEntryForDatabase(origin, name))
return { };
auto result = hasAdequateQuotaForOrigin(origin, estimatedSize);
if (!result.hasException())
return { };
auto exception = result.releaseException();
if (exception.code() != QuotaExceededError)
doneCreatingDatabase(origin, name);
return WTFMove(exception);
}
ExceptionOr<void> DatabaseTracker::retryCanEstablishDatabase(DatabaseContext& context, const String& name, unsigned long long estimatedSize)
{
LockHolder lockDatabase(m_databaseGuard);
auto origin = context.securityOrigin();
auto result = hasAdequateQuotaForOrigin(origin, estimatedSize);
if (!result.hasException())
return { };
auto exception = result.releaseException();
ASSERT(exception.code() == QuotaExceededError);
doneCreatingDatabase(origin, name);
return WTFMove(exception);
}
bool DatabaseTracker::hasEntryForOriginNoLock(const SecurityOriginData& origin)
{
ASSERT(!m_databaseGuard.tryLock());
openTrackerDatabase(DontCreateIfDoesNotExist);
if (!m_database.isOpen())
return false;
SQLiteStatement statement(m_database, "SELECT origin FROM Origins where origin=?;");
if (statement.prepare() != SQLITE_OK) {
LOG_ERROR("Failed to prepare statement.");
return false;
}
statement.bindText(1, origin.databaseIdentifier());
return statement.step() == SQLITE_ROW;
}
bool DatabaseTracker::hasEntryForDatabase(const SecurityOriginData& origin, const String& databaseIdentifier)
{
ASSERT(!m_databaseGuard.tryLock());
openTrackerDatabase(DontCreateIfDoesNotExist);
if (!m_database.isOpen()) {
return false;
}
SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?;");
if (statement.prepare() != SQLITE_OK)
return false;
statement.bindText(1, origin.databaseIdentifier());
statement.bindText(2, databaseIdentifier);
return statement.step() == SQLITE_ROW;
}
unsigned long long DatabaseTracker::maximumSize(Database& database)
{
LockHolder lockDatabase(m_databaseGuard);
auto origin = database.securityOrigin();
unsigned long long quota = quotaNoLock(origin);
unsigned long long diskUsage = usage(origin);
unsigned long long databaseFileSize = SQLiteFileSystem::getDatabaseFileSize(database.fileName());
ASSERT(databaseFileSize <= diskUsage);
if (diskUsage > quota)
return databaseFileSize;
unsigned long long maxSize = quota - diskUsage + databaseFileSize;
if (maxSize > quota)
maxSize = databaseFileSize;
return maxSize;
}
void DatabaseTracker::closeAllDatabases(CurrentQueryBehavior currentQueryBehavior)
{
Vector<Ref<Database>> openDatabases;
{
LockHolder openDatabaseMapLock(m_openDatabaseMapGuard);
if (!m_openDatabaseMap)
return;
for (auto& nameMap : m_openDatabaseMap->values()) {
for (auto& set : nameMap->values()) {
for (auto& database : *set)
openDatabases.append(*database);
}
}
}
for (auto& database : openDatabases) {
if (currentQueryBehavior == CurrentQueryBehavior::Interrupt)
database->interrupt();
database->close();
}
}
String DatabaseTracker::originPath(const SecurityOriginData& origin) const
{
return SQLiteFileSystem::appendDatabaseFileNameToPath(m_databaseDirectoryPath.isolatedCopy(), origin.databaseIdentifier());
}
static String generateDatabaseFileName()
{
StringBuilder stringBuilder;
stringBuilder.append(createCanonicalUUIDString());
stringBuilder.appendLiteral(".db");
return stringBuilder.toString();
}
String DatabaseTracker::fullPathForDatabaseNoLock(const SecurityOriginData& origin, const String& name, bool createIfNotExists)
{
ASSERT(!m_databaseGuard.tryLock());
String originIdentifier = origin.databaseIdentifier();
String originPath = this->originPath(origin);
if (createIfNotExists && !SQLiteFileSystem::ensureDatabaseDirectoryExists(originPath))
return String();
if (!m_database.isOpen())
return String();
SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;");
if (statement.prepare() != SQLITE_OK)
return String();
statement.bindText(1, originIdentifier);
statement.bindText(2, name);
int result = statement.step();
if (result == SQLITE_ROW)
return SQLiteFileSystem::appendDatabaseFileNameToPath(originPath, statement.getColumnText(0));
if (!createIfNotExists)
return String();
if (result != SQLITE_DONE) {
LOG_ERROR("Failed to retrieve filename from Database Tracker for origin %s, name %s", originIdentifier.utf8().data(), name.utf8().data());
return String();
}
statement.finalize();
String fileName = generateDatabaseFileName();
if (!addDatabase(origin, name, fileName))
return String();
String fullFilePath = SQLiteFileSystem::appendDatabaseFileNameToPath(originPath, fileName);
return fullFilePath;
}
String DatabaseTracker::fullPathForDatabase(const SecurityOriginData& origin, const String& name, bool createIfNotExists)
{
LockHolder lockDatabase(m_databaseGuard);
return fullPathForDatabaseNoLock(origin, name, createIfNotExists).isolatedCopy();
}
Vector<SecurityOriginData> DatabaseTracker::origins()
{
LockHolder lockDatabase(m_databaseGuard);
openTrackerDatabase(DontCreateIfDoesNotExist);
if (!m_database.isOpen())
return { };
SQLiteStatement statement(m_database, "SELECT origin FROM Origins");
if (statement.prepare() != SQLITE_OK) {
LOG_ERROR("Failed to prepare statement.");
return { };
}
Vector<SecurityOriginData> origins;
int stepResult;
while ((stepResult = statement.step()) == SQLITE_ROW)
origins.append(SecurityOriginData::fromDatabaseIdentifier(statement.getColumnText(0))->isolatedCopy());
origins.shrinkToFit();
if (stepResult != SQLITE_DONE)
LOG_ERROR("Failed to read in all origins from the database.");
return origins;
}
Vector<String> DatabaseTracker::databaseNamesNoLock(const SecurityOriginData& origin)
{
ASSERT(!m_databaseGuard.tryLock());
openTrackerDatabase(DontCreateIfDoesNotExist);
if (!m_database.isOpen())
return { };
SQLiteStatement statement(m_database, "SELECT name FROM Databases where origin=?;");
if (statement.prepare() != SQLITE_OK)
return { };
statement.bindText(1, origin.databaseIdentifier());
Vector<String> names;
int result;
while ((result = statement.step()) == SQLITE_ROW)
names.append(statement.getColumnText(0));
names.shrinkToFit();
if (result != SQLITE_DONE) {
LOG_ERROR("Failed to retrieve all database names for origin %s", origin.databaseIdentifier().utf8().data());
return { };
}
return names;
}
Vector<String> DatabaseTracker::databaseNames(const SecurityOriginData& origin)
{
Vector<String> names;
{
LockHolder lockDatabase(m_databaseGuard);
names = databaseNamesNoLock(origin);
}
return isolatedCopy(names);
}
DatabaseDetails DatabaseTracker::detailsForNameAndOrigin(const String& name, const SecurityOriginData& origin)
{
String originIdentifier = origin.databaseIdentifier();
String displayName;
int64_t expectedUsage;
{
LockHolder lockDatabase(m_databaseGuard);
openTrackerDatabase(DontCreateIfDoesNotExist);
if (!m_database.isOpen())
return DatabaseDetails();
SQLiteStatement statement(m_database, "SELECT displayName, estimatedSize FROM Databases WHERE origin=? AND name=?");
if (statement.prepare() != SQLITE_OK)
return DatabaseDetails();
statement.bindText(1, originIdentifier);
statement.bindText(2, name);
int result = statement.step();
if (result == SQLITE_DONE)
return DatabaseDetails();
if (result != SQLITE_ROW) {
LOG_ERROR("Error retrieving details for database %s in origin %s from tracker database", name.utf8().data(), originIdentifier.utf8().data());
return DatabaseDetails();
}
displayName = statement.getColumnText(0);
expectedUsage = statement.getColumnInt64(1);
}
String path = fullPathForDatabase(origin, name, false);
if (path.isEmpty())
return DatabaseDetails(name, displayName, expectedUsage, 0, WTF::nullopt, WTF::nullopt);
return DatabaseDetails(name, displayName, expectedUsage, SQLiteFileSystem::getDatabaseFileSize(path), SQLiteFileSystem::databaseCreationTime(path), SQLiteFileSystem::databaseModificationTime(path));
}
void DatabaseTracker::setDatabaseDetails(const SecurityOriginData& origin, const String& name, const String& displayName, unsigned long long estimatedSize)
{
String originIdentifier = origin.databaseIdentifier();
int64_t guid = 0;
LockHolder lockDatabase(m_databaseGuard);
openTrackerDatabase(CreateIfDoesNotExist);
if (!m_database.isOpen())
return;
SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?");
if (statement.prepare() != SQLITE_OK)
return;
statement.bindText(1, originIdentifier);
statement.bindText(2, name);
int result = statement.step();
if (result == SQLITE_ROW)
guid = statement.getColumnInt64(0);
statement.finalize();
if (guid == 0) {
if (result != SQLITE_DONE)
LOG_ERROR("Error to determing existence of database %s in origin %s in tracker database", name.utf8().data(), originIdentifier.utf8().data());
else {
LOG_ERROR("Could not retrieve guid for database %s in origin %s from the tracker database - it is invalid to set database details on a database that doesn't already exist in the tracker", name.utf8().data(), originIdentifier.utf8().data());
}
return;
}
SQLiteStatement updateStatement(m_database, "UPDATE Databases SET displayName=?, estimatedSize=? WHERE guid=?");
if (updateStatement.prepare() != SQLITE_OK)
return;
updateStatement.bindText(1, displayName);
updateStatement.bindInt64(2, estimatedSize);
updateStatement.bindInt64(3, guid);
if (updateStatement.step() != SQLITE_DONE) {
LOG_ERROR("Failed to update details for database %s in origin %s", name.utf8().data(), originIdentifier.utf8().data());
return;
}
if (m_client)
m_client->dispatchDidModifyDatabase(origin, name);
}
void DatabaseTracker::doneCreatingDatabase(Database& database)
{
LockHolder lockDatabase(m_databaseGuard);
doneCreatingDatabase(database.securityOrigin(), database.stringIdentifier());
}
void DatabaseTracker::addOpenDatabase(Database& database)
{
LockHolder openDatabaseMapLock(m_openDatabaseMapGuard);
if (!m_openDatabaseMap)
m_openDatabaseMap = std::make_unique<DatabaseOriginMap>();
auto origin = database.securityOrigin();
auto* nameMap = m_openDatabaseMap->get(origin);
if (!nameMap) {
nameMap = new DatabaseNameMap;
m_openDatabaseMap->add(origin.isolatedCopy(), nameMap);
}
String name = database.stringIdentifier();
auto* databaseSet = nameMap->get(name);
if (!databaseSet) {
databaseSet = new DatabaseSet;
nameMap->set(name.isolatedCopy(), databaseSet);
}
databaseSet->add(&database);
LOG(StorageAPI, "Added open Database %s (%p)\n", database.stringIdentifier().utf8().data(), &database);
}
void DatabaseTracker::removeOpenDatabase(Database& database)
{
LockHolder openDatabaseMapLock(m_openDatabaseMapGuard);
if (!m_openDatabaseMap) {
ASSERT_NOT_REACHED();
return;
}
DatabaseNameMap* nameMap = m_openDatabaseMap->get(database.securityOrigin());
if (!nameMap) {
ASSERT_NOT_REACHED();
return;
}
String name = database.stringIdentifier();
auto* databaseSet = nameMap->get(name);
if (!databaseSet) {
ASSERT_NOT_REACHED();
return;
}
databaseSet->remove(&database);
LOG(StorageAPI, "Removed open Database %s (%p)\n", database.stringIdentifier().utf8().data(), &database);
if (!databaseSet->isEmpty())
return;
nameMap->remove(name);
delete databaseSet;
if (!nameMap->isEmpty())
return;
m_openDatabaseMap->remove(database.securityOrigin());
delete nameMap;
}
RefPtr<OriginLock> DatabaseTracker::originLockFor(const SecurityOriginData& origin)
{
LockHolder lockDatabase(m_databaseGuard);
String databaseIdentifier = origin.databaseIdentifier();
databaseIdentifier = databaseIdentifier.isolatedCopy();
OriginLockMap::AddResult addResult =
m_originLockMap.add(databaseIdentifier, RefPtr<OriginLock>());
if (!addResult.isNewEntry)
return addResult.iterator->value;
String path = originPath(origin);
RefPtr<OriginLock> lock = adoptRef(*new OriginLock(path));
ASSERT(lock);
addResult.iterator->value = lock;
return lock;
}
void DatabaseTracker::deleteOriginLockFor(const SecurityOriginData& origin)
{
ASSERT(!m_databaseGuard.tryLock());
m_originLockMap.remove(origin.databaseIdentifier());
OriginLock::deleteLockFile(originPath(origin));
}
unsigned long long DatabaseTracker::usage(const SecurityOriginData& origin)
{
String originPath = this->originPath(origin);
unsigned long long diskUsage = 0;
for (auto& fileName : FileSystem::listDirectory(originPath, "*.db"_s)) {
long long size;
FileSystem::getFileSize(fileName, size);
diskUsage += size;
}
return diskUsage;
}
unsigned long long DatabaseTracker::quotaNoLock(const SecurityOriginData& origin)
{
ASSERT(!m_databaseGuard.tryLock());
unsigned long long quota = 0;
openTrackerDatabase(DontCreateIfDoesNotExist);
if (!m_database.isOpen())
return quota;
SQLiteStatement statement(m_database, "SELECT quota FROM Origins where origin=?;");
if (statement.prepare() != SQLITE_OK) {
LOG_ERROR("Failed to prepare statement.");
return quota;
}
statement.bindText(1, origin.databaseIdentifier());
if (statement.step() == SQLITE_ROW)
quota = statement.getColumnInt64(0);
return quota;
}
unsigned long long DatabaseTracker::quota(const SecurityOriginData& origin)
{
LockHolder lockDatabase(m_databaseGuard);
return quotaNoLock(origin);
}
void DatabaseTracker::setQuota(const SecurityOriginData& origin, unsigned long long quota)
{
LockHolder lockDatabase(m_databaseGuard);
if (quotaNoLock(origin) == quota)
return;
openTrackerDatabase(CreateIfDoesNotExist);
if (!m_database.isOpen())
return;
bool insertedNewOrigin = false;
bool originEntryExists = hasEntryForOriginNoLock(origin);
if (!originEntryExists) {
SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
if (statement.prepare() != SQLITE_OK) {
LOG_ERROR("Unable to establish origin %s in the tracker", origin.databaseIdentifier().utf8().data());
} else {
statement.bindText(1, origin.databaseIdentifier());
statement.bindInt64(2, quota);
if (statement.step() != SQLITE_DONE)
LOG_ERROR("Unable to establish origin %s in the tracker", origin.databaseIdentifier().utf8().data());
else
insertedNewOrigin = true;
}
} else {
SQLiteStatement statement(m_database, "UPDATE Origins SET quota=? WHERE origin=?");
bool error = statement.prepare() != SQLITE_OK;
if (!error) {
statement.bindInt64(1, quota);
statement.bindText(2, origin.databaseIdentifier());
error = !statement.executeCommand();
}
if (error)
LOG_ERROR("Failed to set quota %llu in tracker database for origin %s", quota, origin.databaseIdentifier().utf8().data());
}
if (m_client) {
if (insertedNewOrigin)
m_client->dispatchDidAddNewOrigin();
m_client->dispatchDidModifyOrigin(origin);
}
}
bool DatabaseTracker::addDatabase(const SecurityOriginData& origin, const String& name, const String& path)
{
ASSERT(!m_databaseGuard.tryLock());
openTrackerDatabase(CreateIfDoesNotExist);
if (!m_database.isOpen())
return false;
ASSERT(hasEntryForOriginNoLock(origin));
SQLiteStatement statement(m_database, "INSERT INTO Databases (origin, name, path) VALUES (?, ?, ?);");
if (statement.prepare() != SQLITE_OK)
return false;
statement.bindText(1, origin.databaseIdentifier());
statement.bindText(2, name);
statement.bindText(3, path);
if (!statement.executeCommand()) {
LOG_ERROR("Failed to add database %s to origin %s: %s\n", name.utf8().data(), origin.databaseIdentifier().utf8().data(), m_database.lastErrorMsg());
return false;
}
if (m_client)
m_client->dispatchDidModifyOrigin(origin);
return true;
}
void DatabaseTracker::deleteAllDatabasesImmediately()
{
for (auto& origin : origins())
deleteOrigin(origin, DeletionMode::Immediate);
}
void DatabaseTracker::deleteDatabasesModifiedSince(WallTime time)
{
for (auto& origin : origins()) {
Vector<String> databaseNames = this->databaseNames(origin);
Vector<String> databaseNamesToDelete;
databaseNamesToDelete.reserveInitialCapacity(databaseNames.size());
for (const auto& databaseName : databaseNames) {
auto fullPath = fullPathForDatabase(origin, databaseName, false);
if (FileSystem::fileExists(fullPath)) {
auto modificationTime = FileSystem::getFileModificationTime(fullPath);
if (!modificationTime)
continue;
if (modificationTime.value() < time)
continue;
}
databaseNamesToDelete.uncheckedAppend(databaseName);
}
if (databaseNames.size() == databaseNamesToDelete.size())
deleteOrigin(origin);
else {
for (const auto& databaseName : databaseNamesToDelete)
deleteDatabase(origin, databaseName);
}
}
}
bool DatabaseTracker::deleteOrigin(const SecurityOriginData& origin)
{
return deleteOrigin(origin, DeletionMode::Default);
}
bool DatabaseTracker::deleteOrigin(const SecurityOriginData& origin, DeletionMode deletionMode)
{
Vector<String> databaseNames;
{
LockHolder lockDatabase(m_databaseGuard);
openTrackerDatabase(DontCreateIfDoesNotExist);
if (!m_database.isOpen())
return false;
databaseNames = databaseNamesNoLock(origin);
if (databaseNames.isEmpty())
LOG_ERROR("Unable to retrieve list of database names for origin %s", origin.databaseIdentifier().utf8().data());
if (!canDeleteOrigin(origin)) {
LOG_ERROR("Tried to delete an origin (%s) while either creating database in it or already deleting it", origin.databaseIdentifier().utf8().data());
ASSERT_NOT_REACHED();
return false;
}
recordDeletingOrigin(origin);
}
bool failedToDeleteAnyDatabaseFile = false;
for (auto& name : databaseNames) {
if (FileSystem::fileExists(fullPathForDatabase(origin, name, false)) && !deleteDatabaseFile(origin, name, deletionMode)) {
LOG_ERROR("Unable to delete file for database %s in origin %s", name.utf8().data(), origin.databaseIdentifier().utf8().data());
failedToDeleteAnyDatabaseFile = true;
}
}
if (databaseNames.isEmpty()) {
#if PLATFORM(COCOA)
RELEASE_LOG_ERROR(DatabaseTracker, "Unable to retrieve list of database names for origin");
#endif
for (const auto& file : FileSystem::listDirectory(originPath(origin), "*")) {
if (!FileSystem::deleteFile(file))
failedToDeleteAnyDatabaseFile = true;
}
}
if (failedToDeleteAnyDatabaseFile) {
#if PLATFORM(COCOA)
RELEASE_LOG_ERROR(DatabaseTracker, "Failed to delete database for origin");
#endif
return false;
}
{
LockHolder lockDatabase(m_databaseGuard);
deleteOriginLockFor(origin);
doneDeletingOrigin(origin);
SQLiteTransaction transaction(m_database);
transaction.begin();
SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=?");
if (statement.prepare() != SQLITE_OK) {
LOG_ERROR("Unable to prepare deletion of databases from origin %s from tracker", origin.databaseIdentifier().utf8().data());
return false;
}
statement.bindText(1, origin.databaseIdentifier());
if (!statement.executeCommand()) {
LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin.databaseIdentifier().utf8().data());
return false;
}
SQLiteStatement originStatement(m_database, "DELETE FROM Origins WHERE origin=?");
if (originStatement.prepare() != SQLITE_OK) {
LOG_ERROR("Unable to prepare deletion of origin %s from tracker", origin.databaseIdentifier().utf8().data());
return false;
}
originStatement.bindText(1, origin.databaseIdentifier());
if (!originStatement.executeCommand()) {
LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin.databaseIdentifier().utf8().data());
return false;
}
transaction.commit();
SQLiteFileSystem::deleteEmptyDatabaseDirectory(originPath(origin));
bool isEmpty = true;
openTrackerDatabase(DontCreateIfDoesNotExist);
if (m_database.isOpen()) {
SQLiteStatement statement(m_database, "SELECT origin FROM Origins");
if (statement.prepare() != SQLITE_OK)
LOG_ERROR("Failed to prepare statement.");
else if (statement.step() == SQLITE_ROW)
isEmpty = false;
}
if (isEmpty) {
if (m_database.isOpen())
m_database.close();
SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath());
SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_databaseDirectoryPath);
}
if (m_client) {
m_client->dispatchDidModifyOrigin(origin);
m_client->dispatchDidDeleteDatabaseOrigin();
for (auto& name : databaseNames)
m_client->dispatchDidModifyDatabase(origin, name);
}
}
return true;
}
bool DatabaseTracker::isDeletingDatabaseOrOriginFor(const SecurityOriginData& origin, const String& name)
{
ASSERT(!m_databaseGuard.tryLock());
return isDeletingDatabase(origin, name) || isDeletingOrigin(origin);
}
void DatabaseTracker::recordCreatingDatabase(const SecurityOriginData& origin, const String& name)
{
ASSERT(!m_databaseGuard.tryLock());
auto* nameSet = m_beingCreated.get(origin);
if (!nameSet) {
auto ownedSet = std::make_unique<HashCountedSet<String>>();
nameSet = ownedSet.get();
m_beingCreated.add(origin.isolatedCopy(), WTFMove(ownedSet));
}
nameSet->add(name.isolatedCopy());
}
void DatabaseTracker::doneCreatingDatabase(const SecurityOriginData& origin, const String& name)
{
ASSERT(!m_databaseGuard.tryLock());
ASSERT(m_beingCreated.contains(origin));
auto iterator = m_beingCreated.find(origin);
if (iterator == m_beingCreated.end())
return;
auto& countedSet = *iterator->value;
ASSERT(countedSet.contains(name));
if (countedSet.remove(name) && countedSet.isEmpty())
m_beingCreated.remove(iterator);
}
bool DatabaseTracker::creatingDatabase(const SecurityOriginData& origin, const String& name)
{
ASSERT(!m_databaseGuard.tryLock());
auto iterator = m_beingCreated.find(origin);
return iterator != m_beingCreated.end() && iterator->value->contains(name);
}
bool DatabaseTracker::canDeleteDatabase(const SecurityOriginData& origin, const String& name)
{
ASSERT(!m_databaseGuard.tryLock());
return !creatingDatabase(origin, name) && !isDeletingDatabase(origin, name);
}
void DatabaseTracker::recordDeletingDatabase(const SecurityOriginData& origin, const String& name)
{
ASSERT(!m_databaseGuard.tryLock());
ASSERT(canDeleteDatabase(origin, name));
auto* nameSet = m_beingDeleted.get(origin);
if (!nameSet) {
auto ownedSet = std::make_unique<HashSet<String>>();
nameSet = ownedSet.get();
m_beingDeleted.add(origin.isolatedCopy(), WTFMove(ownedSet));
}
ASSERT(!nameSet->contains(name));
nameSet->add(name.isolatedCopy());
}
void DatabaseTracker::doneDeletingDatabase(const SecurityOriginData& origin, const String& name)
{
ASSERT(!m_databaseGuard.tryLock());
ASSERT(m_beingDeleted.contains(origin));
auto iterator = m_beingDeleted.find(origin);
if (iterator == m_beingDeleted.end())
return;
ASSERT(iterator->value->contains(name));
iterator->value->remove(name);
if (iterator->value->isEmpty())
m_beingDeleted.remove(iterator);
}
bool DatabaseTracker::isDeletingDatabase(const SecurityOriginData& origin, const String& name)
{
ASSERT(!m_databaseGuard.tryLock());
auto* nameSet = m_beingDeleted.get(origin);
return nameSet && nameSet->contains(name);
}
bool DatabaseTracker::canDeleteOrigin(const SecurityOriginData& origin)
{
ASSERT(!m_databaseGuard.tryLock());
return !(isDeletingOrigin(origin) || m_beingCreated.get(origin));
}
bool DatabaseTracker::isDeletingOrigin(const SecurityOriginData& origin)
{
ASSERT(!m_databaseGuard.tryLock());
return m_originsBeingDeleted.contains(origin);
}
void DatabaseTracker::recordDeletingOrigin(const SecurityOriginData& origin)
{
ASSERT(!m_databaseGuard.tryLock());
ASSERT(!isDeletingOrigin(origin));
m_originsBeingDeleted.add(origin.isolatedCopy());
}
void DatabaseTracker::doneDeletingOrigin(const SecurityOriginData& origin)
{
ASSERT(!m_databaseGuard.tryLock());
ASSERT(isDeletingOrigin(origin));
m_originsBeingDeleted.remove(origin);
}
bool DatabaseTracker::deleteDatabase(const SecurityOriginData& origin, const String& name)
{
{
LockHolder lockDatabase(m_databaseGuard);
openTrackerDatabase(DontCreateIfDoesNotExist);
if (!m_database.isOpen())
return false;
if (!canDeleteDatabase(origin, name)) {
ASSERT_NOT_REACHED();
return false;
}
recordDeletingDatabase(origin, name);
}
if (FileSystem::fileExists(fullPathForDatabase(origin, name, false)) && !deleteDatabaseFile(origin, name, DeletionMode::Default)) {
LOG_ERROR("Unable to delete file for database %s in origin %s", name.utf8().data(), origin.databaseIdentifier().utf8().data());
LockHolder lockDatabase(m_databaseGuard);
doneDeletingDatabase(origin, name);
return false;
}
LockHolder lockDatabase(m_databaseGuard);
SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=? AND name=?");
if (statement.prepare() != SQLITE_OK) {
LOG_ERROR("Unable to prepare deletion of database %s from origin %s from tracker", name.utf8().data(), origin.databaseIdentifier().utf8().data());
doneDeletingDatabase(origin, name);
return false;
}
statement.bindText(1, origin.databaseIdentifier());
statement.bindText(2, name);
if (!statement.executeCommand()) {
LOG_ERROR("Unable to execute deletion of database %s from origin %s from tracker", name.utf8().data(), origin.databaseIdentifier().utf8().data());
doneDeletingDatabase(origin, name);
return false;
}
if (m_client) {
m_client->dispatchDidModifyOrigin(origin);
m_client->dispatchDidModifyDatabase(origin, name);
m_client->dispatchDidDeleteDatabase();
}
doneDeletingDatabase(origin, name);
return true;
}
bool DatabaseTracker::deleteDatabaseFile(const SecurityOriginData& origin, const String& name, DeletionMode deletionMode)
{
String fullPath = fullPathForDatabase(origin, name, false);
if (fullPath.isEmpty())
return true;
#ifndef NDEBUG
{
LockHolder lockDatabase(m_databaseGuard);
ASSERT(isDeletingDatabaseOrOriginFor(origin, name));
}
#endif
Vector<Ref<Database>> deletedDatabases;
{
LockHolder openDatabaseMapLock(m_openDatabaseMapGuard);
if (m_openDatabaseMap) {
if (auto* nameMap = m_openDatabaseMap->get(origin)) {
if (auto* databaseSet = nameMap->get(name)) {
for (auto& database : *databaseSet)
deletedDatabases.append(*database);
}
}
}
}
for (auto& database : deletedDatabases)
database->markAsDeletedAndClose();
#if PLATFORM(IOS_FAMILY)
if (deletionMode == DeletionMode::Deferred) {
SQLiteDatabase database;
if (!database.open(fullPath))
return false;
return SQLiteFileSystem::truncateDatabaseFile(database.sqlite3Handle());
}
#else
UNUSED_PARAM(deletionMode);
#endif
return SQLiteFileSystem::deleteDatabaseFile(fullPath);
}
#if PLATFORM(IOS_FAMILY)
void DatabaseTracker::removeDeletedOpenedDatabases()
{
{
LockHolder lockDatabase(m_databaseGuard);
openTrackerDatabase(DontCreateIfDoesNotExist);
}
if (!m_database.isOpen())
return;
Vector<RefPtr<Database>> deletedDatabases;
Vector<std::pair<SecurityOriginData, Vector<String>>> deletedDatabaseNames;
{
LockHolder openDatabaseMapLock(m_openDatabaseMapGuard);
if (m_openDatabaseMap) {
for (auto& openDatabase : *m_openDatabaseMap) {
auto& origin = openDatabase.key;
DatabaseNameMap* databaseNameMap = openDatabase.value;
Vector<String> deletedDatabaseNamesForThisOrigin;
for (auto& databases : *databaseNameMap) {
String databaseName = databases.key;
String databaseFileName;
SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;");
if (statement.prepare() == SQLITE_OK) {
statement.bindText(1, origin.databaseIdentifier());
statement.bindText(2, databaseName);
if (statement.step() == SQLITE_ROW)
databaseFileName = statement.getColumnText(0);
statement.finalize();
}
bool foundDeletedDatabase = false;
for (auto& db : *databases.value) {
if (db->deleted())
continue;
if (databaseFileName.isNull() || databaseFileName != FileSystem::pathGetFileName(db->fileName())) {
deletedDatabases.append(db);
foundDeletedDatabase = true;
}
}
if (m_client && foundDeletedDatabase && databaseFileName.isNull())
deletedDatabaseNamesForThisOrigin.append(databaseName);
}
if (!deletedDatabaseNamesForThisOrigin.isEmpty())
deletedDatabaseNames.append({ origin, WTFMove(deletedDatabaseNamesForThisOrigin) });
}
}
}
for (auto& deletedDatabase : deletedDatabases)
deletedDatabase->markAsDeletedAndClose();
for (auto& deletedDatabase : deletedDatabaseNames) {
auto& origin = deletedDatabase.first;
m_client->dispatchDidModifyOrigin(origin);
for (auto& databaseName : deletedDatabase.second)
m_client->dispatchDidModifyDatabase(origin, databaseName);
}
}
static bool isZeroByteFile(const String& path)
{
long long size = 0;
return FileSystem::getFileSize(path, size) && !size;
}
bool DatabaseTracker::deleteDatabaseFileIfEmpty(const String& path)
{
if (!isZeroByteFile(path))
return false;
SQLiteDatabase database;
if (!database.open(path))
return false;
SQLiteStatement lockStatement(database, "PRAGMA locking_mode=EXCLUSIVE;");
if (lockStatement.prepare() != SQLITE_OK)
return false;
int result = lockStatement.step();
if (result != SQLITE_ROW && result != SQLITE_DONE)
return false;
lockStatement.finalize();
if (!database.executeCommand("BEGIN EXCLUSIVE TRANSACTION;"))
return false;
if (!database.executeCommand("SELECT name FROM sqlite_master WHERE type='table';"))
return false;
database.executeCommand("COMMIT TRANSACTION;");
database.close();
return SQLiteFileSystem::deleteDatabaseFile(path);
}
static Lock openDatabaseLock;
Lock& DatabaseTracker::openDatabaseMutex()
{
return openDatabaseLock;
}
void DatabaseTracker::emptyDatabaseFilesRemovalTaskWillBeScheduled()
{
openDatabaseLock.lock();
}
void DatabaseTracker::emptyDatabaseFilesRemovalTaskDidFinish()
{
openDatabaseLock.unlock();
}
#endif
void DatabaseTracker::setClient(DatabaseManagerClient* client)
{
m_client = client;
}
static Lock notificationLock;
using NotificationQueue = Vector<std::pair<SecurityOriginData, String>>;
static NotificationQueue& notificationQueue()
{
static NeverDestroyed<NotificationQueue> queue;
return queue;
}
void DatabaseTracker::scheduleNotifyDatabaseChanged(const SecurityOriginData& origin, const String& name)
{
auto locker = holdLock(notificationLock);
notificationQueue().append(std::make_pair(origin.isolatedCopy(), name.isolatedCopy()));
scheduleForNotification();
}
static bool notificationScheduled = false;
void DatabaseTracker::scheduleForNotification()
{
ASSERT(!notificationLock.tryLock());
if (!notificationScheduled) {
callOnMainThread([] {
notifyDatabasesChanged();
});
notificationScheduled = true;
}
}
void DatabaseTracker::notifyDatabasesChanged()
{
auto& tracker = DatabaseTracker::singleton();
NotificationQueue notifications;
{
auto locker = holdLock(notificationLock);
notifications.swap(notificationQueue());
notificationScheduled = false;
}
if (!tracker.m_client)
return;
for (auto& notification : notifications)
tracker.m_client->dispatchDidModifyDatabase(notification.first, notification.second);
}
}