Home | History | Annotate | Download | only in storage
      1 /*
      2  * Copyright (C) 2011 Apple Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      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.
     12  *
     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.
     24  */
     25 
     26 #include "config.h"
     27 #include "StorageTracker.h"
     28 
     29 #if ENABLE(DOM_STORAGE)
     30 
     31 #include "DatabaseThread.h"
     32 #include "FileSystem.h"
     33 #include "LocalStorageTask.h"
     34 #include "LocalStorageThread.h"
     35 #include "Logging.h"
     36 #include "PageGroup.h"
     37 #include "SQLiteFileSystem.h"
     38 #include "SQLiteStatement.h"
     39 #include "SecurityOrigin.h"
     40 #include "StorageTrackerClient.h"
     41 #include "TextEncoding.h"
     42 #include <wtf/MainThread.h>
     43 #include <wtf/StdLibExtras.h>
     44 #include <wtf/Vector.h>
     45 #include <wtf/text/CString.h>
     46 
     47 namespace WebCore {
     48 
     49 static StorageTracker* storageTracker = 0;
     50 
     51 void StorageTracker::initializeTracker(const String& storagePath)
     52 {
     53     ASSERT(isMainThread());
     54     ASSERT(!storageTracker);
     55 
     56     if (!storageTracker)
     57         storageTracker = new StorageTracker(storagePath);
     58 
     59     // Make sure text encoding maps have been built on the main thread, as the StorageTracker thread might try to do it there instead.
     60     // FIXME (<rdar://problem/9127819>): Is there a more explicit way of doing this besides accessing the UTF8Encoding?
     61     UTF8Encoding();
     62 
     63     SQLiteFileSystem::registerSQLiteVFS();
     64     storageTracker->setIsActive(true);
     65     storageTracker->m_thread->start();
     66     storageTracker->importOriginIdentifiers();
     67 }
     68 
     69 StorageTracker& StorageTracker::tracker()
     70 {
     71     if (!storageTracker)
     72         storageTracker = new StorageTracker("");
     73 
     74     return *storageTracker;
     75 }
     76 
     77 StorageTracker::StorageTracker(const String& storagePath)
     78     : m_client(0)
     79     , m_thread(LocalStorageThread::create())
     80     , m_isActive(false)
     81 {
     82     setStorageDirectoryPath(storagePath);
     83 }
     84 
     85 void StorageTracker::setStorageDirectoryPath(const String& path)
     86 {
     87     MutexLocker lockDatabase(m_databaseGuard);
     88     ASSERT(!m_database.isOpen());
     89 
     90     m_storageDirectoryPath = path.threadsafeCopy();
     91 }
     92 
     93 String StorageTracker::trackerDatabasePath()
     94 {
     95     ASSERT(!m_databaseGuard.tryLock());
     96     return SQLiteFileSystem::appendDatabaseFileNameToPath(m_storageDirectoryPath, "StorageTracker.db");
     97 }
     98 
     99 void StorageTracker::openTrackerDatabase(bool createIfDoesNotExist)
    100 {
    101     ASSERT(m_isActive);
    102     ASSERT(!isMainThread());
    103     ASSERT(!m_databaseGuard.tryLock());
    104 
    105     if (m_database.isOpen())
    106         return;
    107 
    108     String databasePath = trackerDatabasePath();
    109 
    110     if (!SQLiteFileSystem::ensureDatabaseFileExists(databasePath, createIfDoesNotExist)) {
    111         if (createIfDoesNotExist)
    112             LOG_ERROR("Failed to create database file '%s'", databasePath.ascii().data());
    113         return;
    114     }
    115 
    116     if (!m_database.open(databasePath)) {
    117         LOG_ERROR("Failed to open databasePath %s.", databasePath.ascii().data());
    118         return;
    119     }
    120 
    121     m_database.disableThreadingChecks();
    122 
    123     if (!m_database.tableExists("Origins")) {
    124         if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, path TEXT);"))
    125             LOG_ERROR("Failed to create Origins table.");
    126     }
    127 }
    128 
    129 void StorageTracker::importOriginIdentifiers()
    130 {
    131     if (!m_isActive)
    132         return;
    133 
    134     ASSERT(isMainThread());
    135     ASSERT(m_thread);
    136 
    137     m_thread->scheduleTask(LocalStorageTask::createOriginIdentifiersImport());
    138 }
    139 
    140 void StorageTracker::syncImportOriginIdentifiers()
    141 {
    142     ASSERT(m_isActive);
    143 
    144     ASSERT(!isMainThread());
    145 
    146     {
    147         MutexLocker lockDatabase(m_databaseGuard);
    148 
    149         // Don't force creation of StorageTracker's db just because a tracker
    150         // was initialized. It will be created if local storage dbs are found
    151         // by syncFileSystemAndTrackerDatabse() or the next time a local storage
    152         // db is created by StorageAreaSync.
    153         openTrackerDatabase(false);
    154 
    155         if (m_database.isOpen()) {
    156             SQLiteStatement statement(m_database, "SELECT origin FROM Origins");
    157             if (statement.prepare() != SQLResultOk) {
    158                 LOG_ERROR("Failed to prepare statement.");
    159                 return;
    160             }
    161 
    162             int result;
    163 
    164             {
    165                 MutexLocker lockOrigins(m_originSetGuard);
    166                 while ((result = statement.step()) == SQLResultRow)
    167                     m_originSet.add(statement.getColumnText(0).threadsafeCopy());
    168             }
    169 
    170             if (result != SQLResultDone) {
    171                 LOG_ERROR("Failed to read in all origins from the database.");
    172                 return;
    173             }
    174         }
    175     }
    176 
    177     syncFileSystemAndTrackerDatabase();
    178 
    179     {
    180         MutexLocker lockClient(m_clientGuard);
    181         if (m_client) {
    182             MutexLocker lockOrigins(m_originSetGuard);
    183             OriginSet::const_iterator end = m_originSet.end();
    184             for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
    185                 m_client->dispatchDidModifyOrigin(*it);
    186         }
    187     }
    188 }
    189 
    190 void StorageTracker::syncFileSystemAndTrackerDatabase()
    191 {
    192     ASSERT(!isMainThread());
    193     ASSERT(m_isActive);
    194 
    195     m_databaseGuard.lock();
    196     DEFINE_STATIC_LOCAL(const String, fileMatchPattern, ("*.localstorage"));
    197     DEFINE_STATIC_LOCAL(const String, fileExt, (".localstorage"));
    198     DEFINE_STATIC_LOCAL(const unsigned, fileExtLength, (fileExt.length()));
    199     m_databaseGuard.unlock();
    200 
    201     Vector<String> paths;
    202     {
    203         MutexLocker lock(m_databaseGuard);
    204         paths = listDirectory(m_storageDirectoryPath, fileMatchPattern);
    205     }
    206 
    207     // Use a copy of m_originSet to find expired entries and to schedule their
    208     // deletions from disk and from m_originSet.
    209     OriginSet originSetCopy;
    210     {
    211         MutexLocker lock(m_originSetGuard);
    212         OriginSet::const_iterator end = m_originSet.end();
    213         for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
    214             originSetCopy.add((*it).threadsafeCopy());
    215     }
    216 
    217     // Add missing StorageTracker records.
    218     OriginSet foundOrigins;
    219     Vector<String>::const_iterator end = paths.end();
    220     for (Vector<String>::const_iterator it = paths.begin(); it != end; ++it) {
    221         String path = *it;
    222         if (path.endsWith(fileExt, true) && path.length() > fileExtLength) {
    223             String file = pathGetFileName(path);
    224             String originIdentifier = file.substring(0, file.length() - fileExtLength);
    225             if (!originSetCopy.contains(originIdentifier))
    226                 syncSetOriginDetails(originIdentifier, path);
    227 
    228             foundOrigins.add(originIdentifier);
    229         }
    230     }
    231 
    232     // Delete stale StorageTracker records.
    233     OriginSet::const_iterator setEnd = originSetCopy.end();
    234     for (OriginSet::const_iterator it = originSetCopy.begin(); it != setEnd; ++it) {
    235         if (!foundOrigins.contains(*it)) {
    236             RefPtr<StringImpl> originIdentifier = (*it).threadsafeCopy().impl();
    237             callOnMainThread(deleteOriginOnMainThread, originIdentifier.release().leakRef());
    238         }
    239     }
    240 }
    241 
    242 void StorageTracker::setOriginDetails(const String& originIdentifier, const String& databaseFile)
    243 {
    244     if (!m_isActive)
    245         return;
    246 
    247     {
    248         MutexLocker lockOrigins(m_originSetGuard);
    249 
    250         if (m_originSet.contains(originIdentifier))
    251             return;
    252 
    253         m_originSet.add(originIdentifier);
    254     }
    255 
    256     OwnPtr<LocalStorageTask> task = LocalStorageTask::createSetOriginDetails(originIdentifier.threadsafeCopy(), databaseFile);
    257 
    258     if (isMainThread()) {
    259         ASSERT(m_thread);
    260         m_thread->scheduleTask(task.release());
    261     } else
    262         callOnMainThread(scheduleTask, reinterpret_cast<void*>(task.leakPtr()));
    263 }
    264 
    265 void StorageTracker::scheduleTask(void* taskIn)
    266 {
    267     ASSERT(isMainThread());
    268     ASSERT(StorageTracker::tracker().m_thread);
    269 
    270     OwnPtr<LocalStorageTask> task = adoptPtr(reinterpret_cast<LocalStorageTask*>(taskIn));
    271 
    272     StorageTracker::tracker().m_thread->scheduleTask(task.release());
    273 }
    274 
    275 void StorageTracker::syncSetOriginDetails(const String& originIdentifier, const String& databaseFile)
    276 {
    277     ASSERT(!isMainThread());
    278 
    279     MutexLocker lockDatabase(m_databaseGuard);
    280 
    281     openTrackerDatabase(true);
    282 
    283     if (!m_database.isOpen())
    284         return;
    285 
    286     SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
    287     if (statement.prepare() != SQLResultOk) {
    288         LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data());
    289         return;
    290     }
    291 
    292     statement.bindText(1, originIdentifier);
    293     statement.bindText(2, databaseFile);
    294 
    295     if (statement.step() != SQLResultDone)
    296         LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data());
    297 
    298     {
    299         MutexLocker lockOrigins(m_originSetGuard);
    300         if (!m_originSet.contains(originIdentifier))
    301             m_originSet.add(originIdentifier);
    302     }
    303 
    304     {
    305         MutexLocker lockClient(m_clientGuard);
    306         if (m_client)
    307             m_client->dispatchDidModifyOrigin(originIdentifier);
    308     }
    309 }
    310 
    311 void StorageTracker::origins(Vector<RefPtr<SecurityOrigin> >& result)
    312 {
    313     ASSERT(m_isActive);
    314 
    315     if (!m_isActive)
    316         return;
    317 
    318     MutexLocker lockOrigins(m_originSetGuard);
    319 
    320     OriginSet::const_iterator end = m_originSet.end();
    321     for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
    322         result.append(SecurityOrigin::createFromDatabaseIdentifier(*it));
    323 }
    324 
    325 void StorageTracker::deleteAllOrigins()
    326 {
    327     ASSERT(m_isActive);
    328     ASSERT(isMainThread());
    329     ASSERT(m_thread);
    330 
    331     if (!m_isActive)
    332         return;
    333 
    334     {
    335         MutexLocker lockOrigins(m_originSetGuard);
    336         willDeleteAllOrigins();
    337         m_originSet.clear();
    338     }
    339 
    340     PageGroup::clearLocalStorageForAllOrigins();
    341 
    342     m_thread->scheduleTask(LocalStorageTask::createDeleteAllOrigins());
    343 }
    344 
    345 void StorageTracker::syncDeleteAllOrigins()
    346 {
    347     ASSERT(!isMainThread());
    348 
    349     MutexLocker lockDatabase(m_databaseGuard);
    350 
    351     openTrackerDatabase(false);
    352     if (!m_database.isOpen())
    353         return;
    354 
    355     SQLiteStatement statement(m_database, "SELECT origin, path FROM Origins");
    356     if (statement.prepare() != SQLResultOk) {
    357         LOG_ERROR("Failed to prepare statement.");
    358         return;
    359     }
    360 
    361     int result;
    362     while ((result = statement.step()) == SQLResultRow) {
    363         if (!canDeleteOrigin(statement.getColumnText(0)))
    364             continue;
    365 
    366         SQLiteFileSystem::deleteDatabaseFile(statement.getColumnText(1));
    367 
    368         {
    369             MutexLocker lockClient(m_clientGuard);
    370             if (m_client)
    371                 m_client->dispatchDidModifyOrigin(statement.getColumnText(0));
    372         }
    373     }
    374 
    375     if (result != SQLResultDone)
    376         LOG_ERROR("Failed to read in all origins from the database.");
    377 
    378     if (m_database.isOpen())
    379         m_database.close();
    380 
    381     if (!SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath())) {
    382         // In the case where it is not possible to delete the database file (e.g some other program
    383         // like a virus scanner is accessing it), make sure to remove all entries.
    384         openTrackerDatabase(false);
    385         if (!m_database.isOpen())
    386             return;
    387         SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins");
    388         if (deleteStatement.prepare() != SQLResultOk) {
    389             LOG_ERROR("Unable to prepare deletion of all origins");
    390             return;
    391         }
    392         if (!deleteStatement.executeCommand()) {
    393             LOG_ERROR("Unable to execute deletion of all origins");
    394             return;
    395         }
    396     }
    397     SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_storageDirectoryPath);
    398 }
    399 
    400 void StorageTracker::deleteOriginOnMainThread(void* originIdentifier)
    401 {
    402     ASSERT(isMainThread());
    403 
    404     String identifier = adoptRef(reinterpret_cast<StringImpl*>(originIdentifier));
    405     tracker().deleteOrigin(identifier);
    406 }
    407 
    408 void StorageTracker::deleteOrigin(const String& originIdentifier)
    409 {
    410     deleteOrigin(SecurityOrigin::createFromDatabaseIdentifier(originIdentifier).get());
    411 }
    412 
    413 void StorageTracker::deleteOrigin(SecurityOrigin* origin)
    414 {
    415     ASSERT(m_isActive);
    416     ASSERT(isMainThread());
    417     ASSERT(m_thread);
    418 
    419     if (!m_isActive)
    420         return;
    421 
    422     // Before deleting database, we need to clear in-memory local storage data
    423     // in StorageArea, and to close the StorageArea db. It's possible for an
    424     // item to be added immediately after closing the db and cause StorageAreaSync
    425     // to reopen the db before the db is deleted by a StorageTracker thread.
    426     // In this case, reopening the db in StorageAreaSync will cancel a pending
    427     // StorageTracker db deletion.
    428     PageGroup::clearLocalStorageForOrigin(origin);
    429 
    430     String originId = origin->databaseIdentifier();
    431 
    432     {
    433         MutexLocker lockOrigins(m_originSetGuard);
    434         willDeleteOrigin(originId);
    435         m_originSet.remove(originId);
    436     }
    437 
    438     m_thread->scheduleTask(LocalStorageTask::createDeleteOrigin(originId));
    439 }
    440 
    441 void StorageTracker::syncDeleteOrigin(const String& originIdentifier)
    442 {
    443     ASSERT(!isMainThread());
    444 
    445     MutexLocker lockDatabase(m_databaseGuard);
    446 
    447     if (!canDeleteOrigin(originIdentifier)) {
    448         LOG_ERROR("Attempted to delete origin '%s' while it was being created\n", originIdentifier.ascii().data());
    449         return;
    450     }
    451 
    452     openTrackerDatabase(false);
    453     if (!m_database.isOpen())
    454         return;
    455 
    456     // Get origin's db file path, delete entry in tracker's db, then delete db file.
    457     SQLiteStatement pathStatement(m_database, "SELECT path FROM Origins WHERE origin=?");
    458     if (pathStatement.prepare() != SQLResultOk) {
    459         LOG_ERROR("Unable to prepare selection of path for origin '%s'", originIdentifier.ascii().data());
    460         return;
    461     }
    462     pathStatement.bindText(1, originIdentifier);
    463     int result = pathStatement.step();
    464     if (result != SQLResultRow) {
    465         LOG_ERROR("Unable to find origin '%s' in Origins table", originIdentifier.ascii().data());
    466         return;
    467     }
    468 
    469     String path = pathStatement.getColumnText(0);
    470 
    471     ASSERT(!path.isEmpty());
    472 
    473     SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins where origin=?");
    474     if (deleteStatement.prepare() != SQLResultOk) {
    475         LOG_ERROR("Unable to prepare deletion of origin '%s'", originIdentifier.ascii().data());
    476         return;
    477     }
    478     deleteStatement.bindText(1, originIdentifier);
    479     if (!deleteStatement.executeCommand()) {
    480         LOG_ERROR("Unable to execute deletion of origin '%s'", originIdentifier.ascii().data());
    481         return;
    482     }
    483 
    484     SQLiteFileSystem::deleteDatabaseFile(path);
    485 
    486     bool shouldDeleteTrackerFiles = false;
    487     {
    488         MutexLocker originLock(m_originSetGuard);
    489         m_originSet.remove(originIdentifier);
    490         shouldDeleteTrackerFiles = m_originSet.isEmpty();
    491     }
    492 
    493     if (shouldDeleteTrackerFiles) {
    494         m_database.close();
    495         SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath());
    496         SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_storageDirectoryPath);
    497     }
    498 
    499     {
    500         MutexLocker lockClient(m_clientGuard);
    501         if (m_client)
    502             m_client->dispatchDidModifyOrigin(originIdentifier);
    503     }
    504 }
    505 
    506 void StorageTracker::willDeleteAllOrigins()
    507 {
    508     ASSERT(!m_originSetGuard.tryLock());
    509 
    510     OriginSet::const_iterator end = m_originSet.end();
    511     for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
    512         m_originsBeingDeleted.add((*it).threadsafeCopy());
    513 }
    514 
    515 void StorageTracker::willDeleteOrigin(const String& originIdentifier)
    516 {
    517     ASSERT(isMainThread());
    518     ASSERT(!m_originSetGuard.tryLock());
    519 
    520     m_originsBeingDeleted.add(originIdentifier);
    521 }
    522 
    523 bool StorageTracker::canDeleteOrigin(const String& originIdentifier)
    524 {
    525     ASSERT(!m_databaseGuard.tryLock());
    526     MutexLocker lockOrigins(m_originSetGuard);
    527     return m_originsBeingDeleted.contains(originIdentifier);
    528 }
    529 
    530 void StorageTracker::cancelDeletingOrigin(const String& originIdentifier)
    531 {
    532     if (!m_isActive)
    533         return;
    534 
    535     MutexLocker lockDatabase(m_databaseGuard);
    536     MutexLocker lockOrigins(m_originSetGuard);
    537     if (!m_originsBeingDeleted.isEmpty())
    538         m_originsBeingDeleted.remove(originIdentifier);
    539 }
    540 
    541 void StorageTracker::setClient(StorageTrackerClient* client)
    542 {
    543     MutexLocker lockClient(m_clientGuard);
    544     m_client = client;
    545 }
    546 
    547 void StorageTracker::syncLocalStorage()
    548 {
    549     PageGroup::syncLocalStorage();
    550 }
    551 
    552 bool StorageTracker::isActive()
    553 {
    554     return m_isActive;
    555 }
    556 
    557 void StorageTracker::setIsActive(bool flag)
    558 {
    559     m_isActive = flag;
    560 }
    561 
    562 } // namespace WebCore
    563 
    564 #endif // ENABLE(DOM_STORAGE)
    565