blob: 42a536f693daeac007229ad2e0e8a0902d9266ff [file] [log] [blame]
/*
* Copyright (C) 2011 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 INC. 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 "StorageTracker.h"
#include "DatabaseThread.h"
#include "FileSystem.h"
#include "StorageTask.h"
#include "StorageThread.h"
#include "Logging.h"
#include "PageGroup.h"
#include "SQLiteFileSystem.h"
#include "SQLiteStatement.h"
#include "SecurityOrigin.h"
#include "StorageTrackerClient.h"
#include "TextEncoding.h"
#include <wtf/MainThread.h>
#include <wtf/StdLibExtras.h>
#include <wtf/Vector.h>
#include <wtf/text/CString.h>
namespace WebCore {
static StorageTracker* storageTracker = 0;
// If there is no document referencing a storage database, close the underlying database
// after it has been idle for m_StorageDatabaseIdleInterval seconds.
static const double DefaultStorageDatabaseIdleInterval = 300;
void StorageTracker::initializeTracker(const String& storagePath, StorageTrackerClient* client)
{
ASSERT(isMainThread());
ASSERT(!storageTracker || !storageTracker->m_client);
if (!storageTracker)
storageTracker = new StorageTracker(storagePath);
storageTracker->m_client = client;
storageTracker->m_needsInitialization = true;
}
void StorageTracker::internalInitialize()
{
m_needsInitialization = false;
ASSERT(isMainThread());
// Make sure text encoding maps have been built on the main thread, as the StorageTracker thread might try to do it there instead.
// FIXME (<rdar://problem/9127819>): Is there a more explicit way of doing this besides accessing the UTF8Encoding?
UTF8Encoding();
SQLiteFileSystem::registerSQLiteVFS();
storageTracker->setIsActive(true);
storageTracker->m_thread->start();
storageTracker->importOriginIdentifiers();
}
StorageTracker& StorageTracker::tracker()
{
if (!storageTracker)
storageTracker = new StorageTracker("");
if (storageTracker->m_needsInitialization)
storageTracker->internalInitialize();
return *storageTracker;
}
StorageTracker::StorageTracker(const String& storagePath)
: m_storageDirectoryPath(storagePath.isolatedCopy())
, m_client(0)
, m_thread(StorageThread::create())
, m_isActive(false)
, m_needsInitialization(false)
, m_finishedImportingOriginIdentifiers(false)
, m_StorageDatabaseIdleInterval(DefaultStorageDatabaseIdleInterval)
{
}
void StorageTracker::setDatabaseDirectoryPath(const String& path)
{
MutexLocker lockStorage(m_databaseGuard);
if (m_database.isOpen())
m_database.close();
m_storageDirectoryPath = path.isolatedCopy();
{
MutexLocker lockOrigins(m_originSetGuard);
m_originSet.clear();
}
if (!m_isActive)
return;
importOriginIdentifiers();
}
String StorageTracker::databaseDirectoryPath() const
{
return m_storageDirectoryPath.isolatedCopy();
}
String StorageTracker::trackerDatabasePath()
{
ASSERT(!m_databaseGuard.tryLock());
return SQLiteFileSystem::appendDatabaseFileNameToPath(m_storageDirectoryPath, "StorageTracker.db");
}
void StorageTracker::openTrackerDatabase(bool createIfDoesNotExist)
{
ASSERT(m_isActive);
ASSERT(!isMainThread());
ASSERT(!m_databaseGuard.tryLock());
if (m_database.isOpen())
return;
String databasePath = trackerDatabasePath();
if (!SQLiteFileSystem::ensureDatabaseFileExists(databasePath, createIfDoesNotExist)) {
if (createIfDoesNotExist)
LOG_ERROR("Failed to create database file '%s'", databasePath.ascii().data());
return;
}
if (!m_database.open(databasePath)) {
LOG_ERROR("Failed to open databasePath %s.", databasePath.ascii().data());
return;
}
m_database.disableThreadingChecks();
if (!m_database.tableExists("Origins")) {
if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, path TEXT);"))
LOG_ERROR("Failed to create Origins table.");
}
}
void StorageTracker::importOriginIdentifiers()
{
if (!m_isActive)
return;
ASSERT(isMainThread());
ASSERT(m_thread);
m_thread->scheduleTask(StorageTask::createOriginIdentifiersImport());
}
void StorageTracker::notifyFinishedImportingOriginIdentifiersOnMainThread(void*)
{
tracker().finishedImportingOriginIdentifiers();
}
void StorageTracker::finishedImportingOriginIdentifiers()
{
m_finishedImportingOriginIdentifiers = true;
MutexLocker lockClient(m_clientGuard);
if (m_client)
m_client->didFinishLoadingOrigins();
}
void StorageTracker::syncImportOriginIdentifiers()
{
ASSERT(m_isActive);
ASSERT(!isMainThread());
{
MutexLocker lockDatabase(m_databaseGuard);
// Don't force creation of StorageTracker's db just because a tracker
// was initialized. It will be created if local storage dbs are found
// by syncFileSystemAndTrackerDatabse() or the next time a local storage
// db is created by StorageAreaSync.
openTrackerDatabase(false);
if (m_database.isOpen()) {
SQLiteStatement statement(m_database, "SELECT origin FROM Origins");
if (statement.prepare() != SQLResultOk) {
LOG_ERROR("Failed to prepare statement.");
return;
}
int result;
{
MutexLocker lockOrigins(m_originSetGuard);
while ((result = statement.step()) == SQLResultRow)
m_originSet.add(statement.getColumnText(0).isolatedCopy());
}
if (result != SQLResultDone) {
LOG_ERROR("Failed to read in all origins from the database.");
return;
}
}
}
syncFileSystemAndTrackerDatabase();
{
MutexLocker lockClient(m_clientGuard);
if (m_client) {
MutexLocker lockOrigins(m_originSetGuard);
OriginSet::const_iterator end = m_originSet.end();
for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
m_client->dispatchDidModifyOrigin(*it);
}
}
callOnMainThread(notifyFinishedImportingOriginIdentifiersOnMainThread, 0);
}
void StorageTracker::syncFileSystemAndTrackerDatabase()
{
ASSERT(!isMainThread());
ASSERT(m_isActive);
m_databaseGuard.lock();
DEFINE_STATIC_LOCAL(const String, fileMatchPattern, (ASCIILiteral("*.localstorage")));
DEFINE_STATIC_LOCAL(const String, fileExt, (ASCIILiteral(".localstorage")));
static const unsigned fileExtLength = fileExt.length();
m_databaseGuard.unlock();
Vector<String> paths;
{
MutexLocker lock(m_databaseGuard);
paths = listDirectory(m_storageDirectoryPath, fileMatchPattern);
}
// Use a copy of m_originSet to find expired entries and to schedule their
// deletions from disk and from m_originSet.
OriginSet originSetCopy;
{
MutexLocker lock(m_originSetGuard);
OriginSet::const_iterator end = m_originSet.end();
for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
originSetCopy.add((*it).isolatedCopy());
}
// Add missing StorageTracker records.
OriginSet foundOrigins;
Vector<String>::const_iterator end = paths.end();
for (Vector<String>::const_iterator it = paths.begin(); it != end; ++it) {
String path = *it;
if (path.endsWith(fileExt, true) && path.length() > fileExtLength) {
String file = pathGetFileName(path);
String originIdentifier = file.substring(0, file.length() - fileExtLength);
if (!originSetCopy.contains(originIdentifier))
syncSetOriginDetails(originIdentifier, path);
foundOrigins.add(originIdentifier);
}
}
// Delete stale StorageTracker records.
OriginSet::const_iterator setEnd = originSetCopy.end();
for (OriginSet::const_iterator it = originSetCopy.begin(); it != setEnd; ++it) {
if (!foundOrigins.contains(*it)) {
RefPtr<StringImpl> originIdentifier = (*it).isolatedCopy().impl();
callOnMainThread(deleteOriginOnMainThread, originIdentifier.release().leakRef());
}
}
}
void StorageTracker::setOriginDetails(const String& originIdentifier, const String& databaseFile)
{
if (!m_isActive)
return;
{
MutexLocker lockOrigins(m_originSetGuard);
if (m_originSet.contains(originIdentifier))
return;
m_originSet.add(originIdentifier);
}
OwnPtr<StorageTask> task = StorageTask::createSetOriginDetails(originIdentifier.isolatedCopy(), databaseFile);
if (isMainThread()) {
ASSERT(m_thread);
m_thread->scheduleTask(task.release());
} else
callOnMainThread(scheduleTask, reinterpret_cast<void*>(task.leakPtr()));
}
void StorageTracker::scheduleTask(void* taskIn)
{
ASSERT(isMainThread());
ASSERT(StorageTracker::tracker().m_thread);
OwnPtr<StorageTask> task = adoptPtr(reinterpret_cast<StorageTask*>(taskIn));
StorageTracker::tracker().m_thread->scheduleTask(task.release());
}
void StorageTracker::syncSetOriginDetails(const String& originIdentifier, const String& databaseFile)
{
ASSERT(!isMainThread());
MutexLocker lockDatabase(m_databaseGuard);
openTrackerDatabase(true);
if (!m_database.isOpen())
return;
SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
if (statement.prepare() != SQLResultOk) {
LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data());
return;
}
statement.bindText(1, originIdentifier);
statement.bindText(2, databaseFile);
if (statement.step() != SQLResultDone)
LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data());
{
MutexLocker lockOrigins(m_originSetGuard);
if (!m_originSet.contains(originIdentifier))
m_originSet.add(originIdentifier);
}
{
MutexLocker lockClient(m_clientGuard);
if (m_client)
m_client->dispatchDidModifyOrigin(originIdentifier);
}
}
void StorageTracker::origins(Vector<RefPtr<SecurityOrigin> >& result)
{
ASSERT(m_isActive);
if (!m_isActive)
return;
MutexLocker lockOrigins(m_originSetGuard);
OriginSet::const_iterator end = m_originSet.end();
for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
result.append(SecurityOrigin::createFromDatabaseIdentifier(*it));
}
void StorageTracker::deleteAllOrigins()
{
ASSERT(m_isActive);
ASSERT(isMainThread());
ASSERT(m_thread);
if (!m_isActive)
return;
{
MutexLocker lockOrigins(m_originSetGuard);
willDeleteAllOrigins();
m_originSet.clear();
}
PageGroup::clearLocalStorageForAllOrigins();
m_thread->scheduleTask(StorageTask::createDeleteAllOrigins());
}
void StorageTracker::syncDeleteAllOrigins()
{
ASSERT(!isMainThread());
MutexLocker lockDatabase(m_databaseGuard);
openTrackerDatabase(false);
if (!m_database.isOpen())
return;
SQLiteStatement statement(m_database, "SELECT origin, path FROM Origins");
if (statement.prepare() != SQLResultOk) {
LOG_ERROR("Failed to prepare statement.");
return;
}
int result;
while ((result = statement.step()) == SQLResultRow) {
if (!canDeleteOrigin(statement.getColumnText(0)))
continue;
SQLiteFileSystem::deleteDatabaseFile(statement.getColumnText(1));
{
MutexLocker lockClient(m_clientGuard);
if (m_client)
m_client->dispatchDidModifyOrigin(statement.getColumnText(0));
}
}
if (result != SQLResultDone)
LOG_ERROR("Failed to read in all origins from the database.");
if (m_database.isOpen())
m_database.close();
if (!SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath())) {
// In the case where it is not possible to delete the database file (e.g some other program
// like a virus scanner is accessing it), make sure to remove all entries.
openTrackerDatabase(false);
if (!m_database.isOpen())
return;
SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins");
if (deleteStatement.prepare() != SQLResultOk) {
LOG_ERROR("Unable to prepare deletion of all origins");
return;
}
if (!deleteStatement.executeCommand()) {
LOG_ERROR("Unable to execute deletion of all origins");
return;
}
}
SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_storageDirectoryPath);
}
void StorageTracker::deleteOriginOnMainThread(void* originIdentifier)
{
ASSERT(isMainThread());
String identifier = adoptRef(reinterpret_cast<StringImpl*>(originIdentifier));
tracker().deleteOrigin(identifier);
}
void StorageTracker::deleteOrigin(const String& originIdentifier)
{
deleteOrigin(SecurityOrigin::createFromDatabaseIdentifier(originIdentifier).get());
}
void StorageTracker::deleteOrigin(SecurityOrigin* origin)
{
ASSERT(m_isActive);
ASSERT(isMainThread());
ASSERT(m_thread);
if (!m_isActive)
return;
// Before deleting database, we need to clear in-memory local storage data
// in StorageArea, and to close the StorageArea db. It's possible for an
// item to be added immediately after closing the db and cause StorageAreaSync
// to reopen the db before the db is deleted by a StorageTracker thread.
// In this case, reopening the db in StorageAreaSync will cancel a pending
// StorageTracker db deletion.
PageGroup::clearLocalStorageForOrigin(origin);
String originId = origin->databaseIdentifier();
{
MutexLocker lockOrigins(m_originSetGuard);
willDeleteOrigin(originId);
m_originSet.remove(originId);
}
m_thread->scheduleTask(StorageTask::createDeleteOrigin(originId));
}
void StorageTracker::syncDeleteOrigin(const String& originIdentifier)
{
ASSERT(!isMainThread());
MutexLocker lockDatabase(m_databaseGuard);
if (!canDeleteOrigin(originIdentifier)) {
LOG_ERROR("Attempted to delete origin '%s' while it was being created\n", originIdentifier.ascii().data());
return;
}
openTrackerDatabase(false);
if (!m_database.isOpen())
return;
String path = databasePathForOrigin(originIdentifier);
if (path.isEmpty()) {
// It is possible to get a request from the API to delete the storage for an origin that
// has no such storage.
return;
}
SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins where origin=?");
if (deleteStatement.prepare() != SQLResultOk) {
LOG_ERROR("Unable to prepare deletion of origin '%s'", originIdentifier.ascii().data());
return;
}
deleteStatement.bindText(1, originIdentifier);
if (!deleteStatement.executeCommand()) {
LOG_ERROR("Unable to execute deletion of origin '%s'", originIdentifier.ascii().data());
return;
}
SQLiteFileSystem::deleteDatabaseFile(path);
bool shouldDeleteTrackerFiles = false;
{
MutexLocker originLock(m_originSetGuard);
m_originSet.remove(originIdentifier);
shouldDeleteTrackerFiles = m_originSet.isEmpty();
}
if (shouldDeleteTrackerFiles) {
m_database.close();
SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath());
SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_storageDirectoryPath);
}
{
MutexLocker lockClient(m_clientGuard);
if (m_client)
m_client->dispatchDidModifyOrigin(originIdentifier);
}
}
void StorageTracker::willDeleteAllOrigins()
{
ASSERT(!m_originSetGuard.tryLock());
OriginSet::const_iterator end = m_originSet.end();
for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
m_originsBeingDeleted.add((*it).isolatedCopy());
}
void StorageTracker::willDeleteOrigin(const String& originIdentifier)
{
ASSERT(isMainThread());
ASSERT(!m_originSetGuard.tryLock());
m_originsBeingDeleted.add(originIdentifier);
}
bool StorageTracker::canDeleteOrigin(const String& originIdentifier)
{
ASSERT(!m_databaseGuard.tryLock());
MutexLocker lockOrigins(m_originSetGuard);
return m_originsBeingDeleted.contains(originIdentifier);
}
void StorageTracker::cancelDeletingOrigin(const String& originIdentifier)
{
if (!m_isActive)
return;
MutexLocker lockDatabase(m_databaseGuard);
MutexLocker lockOrigins(m_originSetGuard);
if (!m_originsBeingDeleted.isEmpty())
m_originsBeingDeleted.remove(originIdentifier);
}
void StorageTracker::setClient(StorageTrackerClient* client)
{
MutexLocker lockClient(m_clientGuard);
m_client = client;
}
void StorageTracker::syncLocalStorage()
{
PageGroup::syncLocalStorage();
}
bool StorageTracker::isActive()
{
return m_isActive;
}
void StorageTracker::setIsActive(bool flag)
{
m_isActive = flag;
}
String StorageTracker::databasePathForOrigin(const String& originIdentifier)
{
ASSERT(!m_databaseGuard.tryLock());
ASSERT(m_isActive);
if (!m_database.isOpen())
return String();
SQLiteStatement pathStatement(m_database, "SELECT path FROM Origins WHERE origin=?");
if (pathStatement.prepare() != SQLResultOk) {
LOG_ERROR("Unable to prepare selection of path for origin '%s'", originIdentifier.ascii().data());
return String();
}
pathStatement.bindText(1, originIdentifier);
int result = pathStatement.step();
if (result != SQLResultRow)
return String();
return pathStatement.getColumnText(0);
}
long long StorageTracker::diskUsageForOrigin(SecurityOrigin* origin)
{
if (!m_isActive)
return 0;
MutexLocker lock(m_databaseGuard);
String path = databasePathForOrigin(origin->databaseIdentifier());
if (path.isEmpty())
return 0;
return SQLiteFileSystem::getDatabaseFileSize(path);
}
} // namespace WebCore