2 * Copyright (C) 2011 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
27 #include "StorageTracker.h"
29 #include "DatabaseThread.h"
30 #include "FileSystem.h"
32 #include "PageGroup.h"
33 #include "SQLiteDatabaseTracker.h"
34 #include "SQLiteFileSystem.h"
35 #include "SQLiteStatement.h"
36 #include "SecurityOrigin.h"
37 #include "StorageThread.h"
38 #include "StorageTrackerClient.h"
39 #include "TextEncoding.h"
40 #include <wtf/Functional.h>
41 #include <wtf/MainThread.h>
42 #include <wtf/StdLibExtras.h>
43 #include <wtf/Vector.h>
44 #include <wtf/text/CString.h>
48 static StorageTracker* storageTracker = 0;
50 // If there is no document referencing a storage database, close the underlying database
51 // after it has been idle for m_StorageDatabaseIdleInterval seconds.
52 static const double DefaultStorageDatabaseIdleInterval = 300;
54 void StorageTracker::initializeTracker(const String& storagePath, StorageTrackerClient* client)
56 ASSERT(isMainThread());
57 ASSERT(!storageTracker || !storageTracker->m_client);
60 storageTracker = new StorageTracker(storagePath);
62 storageTracker->m_client = client;
63 storageTracker->m_needsInitialization = true;
66 void StorageTracker::internalInitialize()
68 m_needsInitialization = false;
70 ASSERT(isMainThread());
72 // Make sure text encoding maps have been built on the main thread, as the StorageTracker thread might try to do it there instead.
73 // FIXME (<rdar://problem/9127819>): Is there a more explicit way of doing this besides accessing the UTF8Encoding?
76 storageTracker->setIsActive(true);
77 storageTracker->m_thread->start();
78 storageTracker->importOriginIdentifiers();
81 StorageTracker& StorageTracker::tracker()
84 storageTracker = new StorageTracker("");
85 if (storageTracker->m_needsInitialization)
86 storageTracker->internalInitialize();
88 return *storageTracker;
91 StorageTracker::StorageTracker(const String& storagePath)
92 : m_storageDirectoryPath(storagePath.isolatedCopy())
94 , m_thread(std::make_unique<StorageThread>())
96 , m_needsInitialization(false)
97 , m_StorageDatabaseIdleInterval(DefaultStorageDatabaseIdleInterval)
101 void StorageTracker::setDatabaseDirectoryPath(const String& path)
103 MutexLocker locker(m_databaseMutex);
105 if (m_database.isOpen())
108 m_storageDirectoryPath = path.isolatedCopy();
111 MutexLocker locker(m_originSetMutex);
118 importOriginIdentifiers();
121 String StorageTracker::databaseDirectoryPath() const
123 return m_storageDirectoryPath.isolatedCopy();
126 String StorageTracker::trackerDatabasePath()
128 ASSERT(!m_databaseMutex.tryLock());
129 return SQLiteFileSystem::appendDatabaseFileNameToPath(m_storageDirectoryPath, "StorageTracker.db");
132 void StorageTracker::openTrackerDatabase(bool createIfDoesNotExist)
135 ASSERT(!isMainThread());
137 SQLiteTransactionInProgressAutoCounter transactionCounter;
139 ASSERT(!m_databaseMutex.tryLock());
141 if (m_database.isOpen())
144 String databasePath = trackerDatabasePath();
146 if (!SQLiteFileSystem::ensureDatabaseFileExists(databasePath, createIfDoesNotExist)) {
147 if (createIfDoesNotExist)
148 LOG_ERROR("Failed to create database file '%s'", databasePath.ascii().data());
152 if (!m_database.open(databasePath)) {
153 LOG_ERROR("Failed to open databasePath %s.", databasePath.ascii().data());
157 m_database.disableThreadingChecks();
159 if (!m_database.tableExists("Origins")) {
160 if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, path TEXT);"))
161 LOG_ERROR("Failed to create Origins table.");
165 void StorageTracker::importOriginIdentifiers()
170 ASSERT(isMainThread());
173 m_thread->dispatch(bind(&StorageTracker::syncImportOriginIdentifiers, this));
176 void StorageTracker::finishedImportingOriginIdentifiers()
178 MutexLocker locker(m_databaseMutex);
180 m_client->didFinishLoadingOrigins();
183 void StorageTracker::syncImportOriginIdentifiers()
187 ASSERT(!isMainThread());
190 MutexLocker locker(m_databaseMutex);
192 // Don't force creation of StorageTracker's db just because a tracker
193 // was initialized. It will be created if local storage dbs are found
194 // by syncFileSystemAndTrackerDatabse() or the next time a local storage
195 // db is created by StorageAreaSync.
196 openTrackerDatabase(false);
198 if (m_database.isOpen()) {
199 SQLiteTransactionInProgressAutoCounter transactionCounter;
201 SQLiteStatement statement(m_database, "SELECT origin FROM Origins");
202 if (statement.prepare() != SQLResultOk) {
203 LOG_ERROR("Failed to prepare statement.");
210 MutexLocker lockOrigins(m_originSetMutex);
211 while ((result = statement.step()) == SQLResultRow)
212 m_originSet.add(statement.getColumnText(0).isolatedCopy());
215 if (result != SQLResultDone) {
216 LOG_ERROR("Failed to read in all origins from the database.");
222 syncFileSystemAndTrackerDatabase();
225 MutexLocker locker(m_clientMutex);
228 MutexLocker locker(m_originSetMutex);
229 OriginSet::const_iterator end = m_originSet.end();
230 for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
231 m_client->dispatchDidModifyOrigin(*it);
235 callOnMainThread(bind(&StorageTracker::finishedImportingOriginIdentifiers, this));
238 void StorageTracker::syncFileSystemAndTrackerDatabase()
240 ASSERT(!isMainThread());
242 SQLiteTransactionInProgressAutoCounter transactionCounter;
246 Vector<String> paths;
248 MutexLocker locker(m_databaseMutex);
249 paths = listDirectory(m_storageDirectoryPath, "*.localstorage");
252 // Use a copy of m_originSet to find expired entries and to schedule their
253 // deletions from disk and from m_originSet.
254 OriginSet originSetCopy;
256 MutexLocker locker(m_originSetMutex);
257 for (OriginSet::const_iterator it = m_originSet.begin(), end = m_originSet.end(); it != end; ++it)
258 originSetCopy.add((*it).isolatedCopy());
261 // Add missing StorageTracker records.
262 OriginSet foundOrigins;
263 String fileExtension = ASCIILiteral(".localstorage");
265 for (Vector<String>::const_iterator it = paths.begin(), end = paths.end(); it != end; ++it) {
266 const String& path = *it;
268 if (path.length() > fileExtension.length() && path.endsWith(fileExtension, true)) {
269 String file = pathGetFileName(path);
270 String originIdentifier = file.substring(0, file.length() - fileExtension.length());
271 if (!originSetCopy.contains(originIdentifier))
272 syncSetOriginDetails(originIdentifier, path);
274 foundOrigins.add(originIdentifier);
278 // Delete stale StorageTracker records.
279 for (OriginSet::const_iterator it = originSetCopy.begin(), end = originSetCopy.end(); it != end; ++it) {
280 const String& originIdentifier = *it;
281 if (foundOrigins.contains(originIdentifier))
284 callOnMainThread(bind(&StorageTracker::deleteOriginWithIdentifier, this, originIdentifier.isolatedCopy()));
288 void StorageTracker::setOriginDetails(const String& originIdentifier, const String& databaseFile)
294 MutexLocker locker(m_originSetMutex);
296 if (m_originSet.contains(originIdentifier))
299 m_originSet.add(originIdentifier);
302 Function<void ()> function = bind(&StorageTracker::syncSetOriginDetails, this, originIdentifier.isolatedCopy(), databaseFile.isolatedCopy());
304 if (isMainThread()) {
306 m_thread->dispatch(function);
308 // FIXME: This weird ping-ponging was done to fix a deadlock. We should figure out a cleaner way to avoid it instead.
309 callOnMainThread(bind(&StorageThread::dispatch, m_thread.get(), function));
313 void StorageTracker::syncSetOriginDetails(const String& originIdentifier, const String& databaseFile)
315 ASSERT(!isMainThread());
317 SQLiteTransactionInProgressAutoCounter transactionCounter;
319 MutexLocker locker(m_databaseMutex);
321 openTrackerDatabase(true);
323 if (!m_database.isOpen())
326 SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
327 if (statement.prepare() != SQLResultOk) {
328 LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data());
332 statement.bindText(1, originIdentifier);
333 statement.bindText(2, databaseFile);
335 if (statement.step() != SQLResultDone)
336 LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data());
339 MutexLocker locker(m_originSetMutex);
340 if (!m_originSet.contains(originIdentifier))
341 m_originSet.add(originIdentifier);
345 MutexLocker locker(m_clientMutex);
347 m_client->dispatchDidModifyOrigin(originIdentifier);
351 void StorageTracker::origins(Vector<RefPtr<SecurityOrigin>>& result)
358 MutexLocker locker(m_originSetMutex);
360 for (OriginSet::const_iterator it = m_originSet.begin(), end = m_originSet.end(); it != end; ++it)
361 result.append(SecurityOrigin::createFromDatabaseIdentifier(*it));
364 void StorageTracker::deleteAllOrigins()
367 ASSERT(isMainThread());
374 MutexLocker locker(m_originSetMutex);
375 willDeleteAllOrigins();
379 PageGroup::clearLocalStorageForAllOrigins();
381 m_thread->dispatch(bind(&StorageTracker::syncDeleteAllOrigins, this));
384 void StorageTracker::syncDeleteAllOrigins()
386 ASSERT(!isMainThread());
388 SQLiteTransactionInProgressAutoCounter transactionCounter;
390 MutexLocker locker(m_databaseMutex);
392 openTrackerDatabase(false);
393 if (!m_database.isOpen())
396 SQLiteStatement statement(m_database, "SELECT origin, path FROM Origins");
397 if (statement.prepare() != SQLResultOk) {
398 LOG_ERROR("Failed to prepare statement.");
403 while ((result = statement.step()) == SQLResultRow) {
404 if (!canDeleteOrigin(statement.getColumnText(0)))
407 SQLiteFileSystem::deleteDatabaseFile(statement.getColumnText(1));
410 MutexLocker locker(m_clientMutex);
412 m_client->dispatchDidModifyOrigin(statement.getColumnText(0));
416 if (result != SQLResultDone)
417 LOG_ERROR("Failed to read in all origins from the database.");
419 if (m_database.isOpen()) {
421 SQLiteFileSystem::truncateDatabaseFile(m_database.sqlite3Handle());
427 if (!SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath())) {
428 // In the case where it is not possible to delete the database file (e.g some other program
429 // like a virus scanner is accessing it), make sure to remove all entries.
430 openTrackerDatabase(false);
431 if (!m_database.isOpen())
433 SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins");
434 if (deleteStatement.prepare() != SQLResultOk) {
435 LOG_ERROR("Unable to prepare deletion of all origins");
438 if (!deleteStatement.executeCommand()) {
439 LOG_ERROR("Unable to execute deletion of all origins");
443 SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_storageDirectoryPath);
447 void StorageTracker::deleteOriginWithIdentifier(const String& originIdentifier)
449 deleteOrigin(SecurityOrigin::createFromDatabaseIdentifier(originIdentifier).get());
452 void StorageTracker::deleteOrigin(SecurityOrigin* origin)
455 ASSERT(isMainThread());
461 // Before deleting database, we need to clear in-memory local storage data
462 // in StorageArea, and to close the StorageArea db. It's possible for an
463 // item to be added immediately after closing the db and cause StorageAreaSync
464 // to reopen the db before the db is deleted by a StorageTracker thread.
465 // In this case, reopening the db in StorageAreaSync will cancel a pending
466 // StorageTracker db deletion.
467 PageGroup::clearLocalStorageForOrigin(origin);
469 String originId = origin->databaseIdentifier();
472 MutexLocker locker(m_originSetMutex);
473 willDeleteOrigin(originId);
474 m_originSet.remove(originId);
477 m_thread->dispatch(bind(&StorageTracker::syncDeleteOrigin, this, originId.isolatedCopy()));
480 void StorageTracker::syncDeleteOrigin(const String& originIdentifier)
482 ASSERT(!isMainThread());
484 SQLiteTransactionInProgressAutoCounter transactionCounter;
486 MutexLocker locker(m_databaseMutex);
488 if (!canDeleteOrigin(originIdentifier)) {
489 LOG_ERROR("Attempted to delete origin '%s' while it was being created\n", originIdentifier.ascii().data());
493 openTrackerDatabase(false);
494 if (!m_database.isOpen())
497 String path = databasePathForOrigin(originIdentifier);
498 if (path.isEmpty()) {
499 // It is possible to get a request from the API to delete the storage for an origin that
500 // has no such storage.
504 SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins where origin=?");
505 if (deleteStatement.prepare() != SQLResultOk) {
506 LOG_ERROR("Unable to prepare deletion of origin '%s'", originIdentifier.ascii().data());
509 deleteStatement.bindText(1, originIdentifier);
510 if (!deleteStatement.executeCommand()) {
511 LOG_ERROR("Unable to execute deletion of origin '%s'", originIdentifier.ascii().data());
515 SQLiteFileSystem::deleteDatabaseFile(path);
517 bool shouldDeleteTrackerFiles = false;
519 MutexLocker locker(m_originSetMutex);
520 m_originSet.remove(originIdentifier);
521 shouldDeleteTrackerFiles = m_originSet.isEmpty();
524 if (shouldDeleteTrackerFiles) {
526 SQLiteFileSystem::truncateDatabaseFile(m_database.sqlite3Handle());
530 SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath());
531 SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_storageDirectoryPath);
536 MutexLocker locker(m_clientMutex);
538 m_client->dispatchDidModifyOrigin(originIdentifier);
542 void StorageTracker::willDeleteAllOrigins()
544 ASSERT(!m_originSetMutex.tryLock());
546 OriginSet::const_iterator end = m_originSet.end();
547 for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
548 m_originsBeingDeleted.add((*it).isolatedCopy());
551 void StorageTracker::willDeleteOrigin(const String& originIdentifier)
553 ASSERT(isMainThread());
554 ASSERT(!m_originSetMutex.tryLock());
556 m_originsBeingDeleted.add(originIdentifier);
559 bool StorageTracker::canDeleteOrigin(const String& originIdentifier)
561 ASSERT(!m_databaseMutex.tryLock());
562 MutexLocker locker(m_originSetMutex);
563 return m_originsBeingDeleted.contains(originIdentifier);
566 void StorageTracker::cancelDeletingOrigin(const String& originIdentifier)
571 MutexLocker locker(m_databaseMutex);
573 MutexLocker locker(m_originSetMutex);
574 if (!m_originsBeingDeleted.isEmpty())
575 m_originsBeingDeleted.remove(originIdentifier);
579 bool StorageTracker::isActive()
584 void StorageTracker::setIsActive(bool flag)
589 String StorageTracker::databasePathForOrigin(const String& originIdentifier)
591 ASSERT(!m_databaseMutex.tryLock());
594 if (!m_database.isOpen())
597 SQLiteTransactionInProgressAutoCounter transactionCounter;
599 SQLiteStatement pathStatement(m_database, "SELECT path FROM Origins WHERE origin=?");
600 if (pathStatement.prepare() != SQLResultOk) {
601 LOG_ERROR("Unable to prepare selection of path for origin '%s'", originIdentifier.ascii().data());
604 pathStatement.bindText(1, originIdentifier);
605 int result = pathStatement.step();
606 if (result != SQLResultRow)
609 return pathStatement.getColumnText(0);
612 long long StorageTracker::diskUsageForOrigin(SecurityOrigin* origin)
617 MutexLocker locker(m_databaseMutex);
619 String path = databasePathForOrigin(origin->databaseIdentifier());
623 return SQLiteFileSystem::getDatabaseFileSize(path);
626 } // namespace WebCore