Remove some iOS #ifdefs by adding SQLiteDatabaseTracker to all the builds
[WebKit-https.git] / Source / WebCore / storage / StorageTracker.cpp
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 #include "DatabaseThread.h"
30 #include "FileSystem.h"
31 #include "Logging.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>
45
46 namespace WebCore {
47
48 static StorageTracker* storageTracker = 0;
49
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;
53     
54 void StorageTracker::initializeTracker(const String& storagePath, StorageTrackerClient* client)
55 {
56     ASSERT(isMainThread());
57     ASSERT(!storageTracker || !storageTracker->m_client);
58     
59     if (!storageTracker)
60         storageTracker = new StorageTracker(storagePath);
61     
62     storageTracker->m_client = client;
63     storageTracker->m_needsInitialization = true;
64 }
65
66 void StorageTracker::internalInitialize()
67 {
68     m_needsInitialization = false;
69
70     ASSERT(isMainThread());
71
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?
74     UTF8Encoding();
75     
76     storageTracker->setIsActive(true);
77     storageTracker->m_thread->start();  
78     storageTracker->importOriginIdentifiers();
79 }
80
81 StorageTracker& StorageTracker::tracker()
82 {
83     if (!storageTracker)
84         storageTracker = new StorageTracker("");
85     if (storageTracker->m_needsInitialization)
86         storageTracker->internalInitialize();
87     
88     return *storageTracker;
89 }
90
91 StorageTracker::StorageTracker(const String& storagePath)
92     : m_storageDirectoryPath(storagePath.isolatedCopy())
93     , m_client(0)
94     , m_thread(std::make_unique<StorageThread>())
95     , m_isActive(false)
96     , m_needsInitialization(false)
97     , m_StorageDatabaseIdleInterval(DefaultStorageDatabaseIdleInterval)
98 {
99 }
100
101 void StorageTracker::setDatabaseDirectoryPath(const String& path)
102 {
103     MutexLocker locker(m_databaseMutex);
104
105     if (m_database.isOpen())
106         m_database.close();
107
108     m_storageDirectoryPath = path.isolatedCopy();
109
110     {
111         MutexLocker locker(m_originSetMutex);
112         m_originSet.clear();
113     }
114
115     if (!m_isActive)
116         return;
117
118     importOriginIdentifiers();
119 }
120
121 String StorageTracker::databaseDirectoryPath() const
122 {
123     return m_storageDirectoryPath.isolatedCopy();
124 }
125
126 String StorageTracker::trackerDatabasePath()
127 {
128     ASSERT(!m_databaseMutex.tryLock());
129     return SQLiteFileSystem::appendDatabaseFileNameToPath(m_storageDirectoryPath, "StorageTracker.db");
130 }
131
132 void StorageTracker::openTrackerDatabase(bool createIfDoesNotExist)
133 {
134     ASSERT(m_isActive);
135     ASSERT(!isMainThread());
136
137     SQLiteTransactionInProgressAutoCounter transactionCounter;
138
139     ASSERT(!m_databaseMutex.tryLock());
140
141     if (m_database.isOpen())
142         return;
143     
144     String databasePath = trackerDatabasePath();
145     
146     if (!SQLiteFileSystem::ensureDatabaseFileExists(databasePath, createIfDoesNotExist)) {
147         if (createIfDoesNotExist)
148             LOG_ERROR("Failed to create database file '%s'", databasePath.ascii().data());
149         return;
150     }
151     
152     if (!m_database.open(databasePath)) {
153         LOG_ERROR("Failed to open databasePath %s.", databasePath.ascii().data());
154         return;
155     }
156     
157     m_database.disableThreadingChecks();
158     
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.");
162     }
163 }
164
165 void StorageTracker::importOriginIdentifiers()
166 {   
167     if (!m_isActive)
168         return;
169     
170     ASSERT(isMainThread());
171     ASSERT(m_thread);
172
173     m_thread->dispatch(bind(&StorageTracker::syncImportOriginIdentifiers, this));
174 }
175
176 void StorageTracker::finishedImportingOriginIdentifiers()
177 {
178     MutexLocker locker(m_databaseMutex);
179     if (m_client)
180         m_client->didFinishLoadingOrigins();
181 }
182
183 void StorageTracker::syncImportOriginIdentifiers()
184 {
185     ASSERT(m_isActive);
186     
187     ASSERT(!isMainThread());
188
189     {
190         MutexLocker locker(m_databaseMutex);
191
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);
197
198         if (m_database.isOpen()) {
199             SQLiteTransactionInProgressAutoCounter transactionCounter;
200
201             SQLiteStatement statement(m_database, "SELECT origin FROM Origins");
202             if (statement.prepare() != SQLResultOk) {
203                 LOG_ERROR("Failed to prepare statement.");
204                 return;
205             }
206             
207             int result;
208             
209             {
210                 MutexLocker lockOrigins(m_originSetMutex);
211                 while ((result = statement.step()) == SQLResultRow)
212                     m_originSet.add(statement.getColumnText(0).isolatedCopy());
213             }
214             
215             if (result != SQLResultDone) {
216                 LOG_ERROR("Failed to read in all origins from the database.");
217                 return;
218             }
219         }
220     }
221     
222     syncFileSystemAndTrackerDatabase();
223     
224     {
225         MutexLocker locker(m_clientMutex);
226
227         if (m_client) {
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);
232         }
233     }
234
235     callOnMainThread(bind(&StorageTracker::finishedImportingOriginIdentifiers, this));
236 }
237     
238 void StorageTracker::syncFileSystemAndTrackerDatabase()
239 {
240     ASSERT(!isMainThread());
241
242     SQLiteTransactionInProgressAutoCounter transactionCounter;
243
244     ASSERT(m_isActive);
245
246     Vector<String> paths;
247     {
248         MutexLocker locker(m_databaseMutex);
249         paths = listDirectory(m_storageDirectoryPath, "*.localstorage");
250     }
251
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;
255     {
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());
259     }
260     
261     // Add missing StorageTracker records.
262     OriginSet foundOrigins;
263     String fileExtension = ASCIILiteral(".localstorage");
264
265     for (Vector<String>::const_iterator it = paths.begin(), end = paths.end(); it != end; ++it) {
266         const String& path = *it;
267
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);
273
274             foundOrigins.add(originIdentifier);
275         }
276     }
277
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))
282             continue;
283
284         callOnMainThread(bind(&StorageTracker::deleteOriginWithIdentifier, this, originIdentifier.isolatedCopy()));
285     }
286 }
287
288 void StorageTracker::setOriginDetails(const String& originIdentifier, const String& databaseFile)
289 {
290     if (!m_isActive)
291         return;
292
293     {
294         MutexLocker locker(m_originSetMutex);
295
296         if (m_originSet.contains(originIdentifier))
297             return;
298
299         m_originSet.add(originIdentifier);
300     }
301
302     Function<void ()> function = bind(&StorageTracker::syncSetOriginDetails, this, originIdentifier.isolatedCopy(), databaseFile.isolatedCopy());
303
304     if (isMainThread()) {
305         ASSERT(m_thread);
306         m_thread->dispatch(function);
307     } else {
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));
310     }
311 }
312
313 void StorageTracker::syncSetOriginDetails(const String& originIdentifier, const String& databaseFile)
314 {
315     ASSERT(!isMainThread());
316
317     SQLiteTransactionInProgressAutoCounter transactionCounter;
318
319     MutexLocker locker(m_databaseMutex);
320
321     openTrackerDatabase(true);
322     
323     if (!m_database.isOpen())
324         return;
325
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());
329         return;
330     } 
331     
332     statement.bindText(1, originIdentifier);
333     statement.bindText(2, databaseFile);
334     
335     if (statement.step() != SQLResultDone)
336         LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data());
337
338     {
339         MutexLocker locker(m_originSetMutex);
340         if (!m_originSet.contains(originIdentifier))
341             m_originSet.add(originIdentifier);
342     }
343
344     {
345         MutexLocker locker(m_clientMutex);
346         if (m_client)
347             m_client->dispatchDidModifyOrigin(originIdentifier);
348     }
349 }
350
351 void StorageTracker::origins(Vector<RefPtr<SecurityOrigin>>& result)
352 {
353     ASSERT(m_isActive);
354     
355     if (!m_isActive)
356         return;
357
358     MutexLocker locker(m_originSetMutex);
359
360     for (OriginSet::const_iterator it = m_originSet.begin(), end = m_originSet.end(); it != end; ++it)
361         result.append(SecurityOrigin::createFromDatabaseIdentifier(*it));
362 }
363
364 void StorageTracker::deleteAllOrigins()
365 {
366     ASSERT(m_isActive);
367     ASSERT(isMainThread());
368     ASSERT(m_thread);
369     
370     if (!m_isActive)
371         return;
372
373     {
374         MutexLocker locker(m_originSetMutex);
375         willDeleteAllOrigins();
376         m_originSet.clear();
377     }
378
379     PageGroup::clearLocalStorageForAllOrigins();
380
381     m_thread->dispatch(bind(&StorageTracker::syncDeleteAllOrigins, this));
382 }
383     
384 void StorageTracker::syncDeleteAllOrigins()
385 {
386     ASSERT(!isMainThread());
387
388     SQLiteTransactionInProgressAutoCounter transactionCounter;
389     
390     MutexLocker locker(m_databaseMutex);
391     
392     openTrackerDatabase(false);
393     if (!m_database.isOpen())
394         return;
395     
396     SQLiteStatement statement(m_database, "SELECT origin, path FROM Origins");
397     if (statement.prepare() != SQLResultOk) {
398         LOG_ERROR("Failed to prepare statement.");
399         return;
400     }
401     
402     int result;
403     while ((result = statement.step()) == SQLResultRow) {
404         if (!canDeleteOrigin(statement.getColumnText(0)))
405             continue;
406
407         SQLiteFileSystem::deleteDatabaseFile(statement.getColumnText(1));
408
409         {
410             MutexLocker locker(m_clientMutex);
411             if (m_client)
412                 m_client->dispatchDidModifyOrigin(statement.getColumnText(0));
413         }
414     }
415     
416     if (result != SQLResultDone)
417         LOG_ERROR("Failed to read in all origins from the database.");
418
419     if (m_database.isOpen()) {
420 #if PLATFORM(IOS)
421         SQLiteFileSystem::truncateDatabaseFile(m_database.sqlite3Handle());
422 #endif
423         m_database.close();
424     }
425
426 #if !PLATFORM(IOS)
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())
432             return;
433         SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins");
434         if (deleteStatement.prepare() != SQLResultOk) {
435             LOG_ERROR("Unable to prepare deletion of all origins");
436             return;
437         }
438         if (!deleteStatement.executeCommand()) {
439             LOG_ERROR("Unable to execute deletion of all origins");
440             return;
441         }
442     }
443     SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_storageDirectoryPath);
444 #endif
445 }
446
447 void StorageTracker::deleteOriginWithIdentifier(const String& originIdentifier)
448 {
449     deleteOrigin(SecurityOrigin::createFromDatabaseIdentifier(originIdentifier).get());
450 }
451
452 void StorageTracker::deleteOrigin(SecurityOrigin* origin)
453 {    
454     ASSERT(m_isActive);
455     ASSERT(isMainThread());
456     ASSERT(m_thread);
457     
458     if (!m_isActive)
459         return;
460
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);
468
469     String originId = origin->databaseIdentifier();
470     
471     {
472         MutexLocker locker(m_originSetMutex);
473         willDeleteOrigin(originId);
474         m_originSet.remove(originId);
475     }
476
477     m_thread->dispatch(bind(&StorageTracker::syncDeleteOrigin, this, originId.isolatedCopy()));
478 }
479
480 void StorageTracker::syncDeleteOrigin(const String& originIdentifier)
481 {
482     ASSERT(!isMainThread());
483
484     SQLiteTransactionInProgressAutoCounter transactionCounter;
485
486     MutexLocker locker(m_databaseMutex);
487     
488     if (!canDeleteOrigin(originIdentifier)) {
489         LOG_ERROR("Attempted to delete origin '%s' while it was being created\n", originIdentifier.ascii().data());
490         return;
491     }
492     
493     openTrackerDatabase(false);
494     if (!m_database.isOpen())
495         return;
496
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.
501         return;
502     }
503     
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());
507         return;
508     }
509     deleteStatement.bindText(1, originIdentifier);
510     if (!deleteStatement.executeCommand()) {
511         LOG_ERROR("Unable to execute deletion of origin '%s'", originIdentifier.ascii().data());
512         return;
513     }
514
515     SQLiteFileSystem::deleteDatabaseFile(path);
516     
517     bool shouldDeleteTrackerFiles = false;
518     {
519         MutexLocker locker(m_originSetMutex);
520         m_originSet.remove(originIdentifier);
521         shouldDeleteTrackerFiles = m_originSet.isEmpty();
522     }
523
524     if (shouldDeleteTrackerFiles) {
525 #if PLATFORM(IOS)
526         SQLiteFileSystem::truncateDatabaseFile(m_database.sqlite3Handle());
527 #endif
528         m_database.close();
529 #if !PLATFORM(IOS)
530         SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath());
531         SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_storageDirectoryPath);
532 #endif
533     }
534
535     {
536         MutexLocker locker(m_clientMutex);
537         if (m_client)
538             m_client->dispatchDidModifyOrigin(originIdentifier);
539     }
540 }
541     
542 void StorageTracker::willDeleteAllOrigins()
543 {
544     ASSERT(!m_originSetMutex.tryLock());
545
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());
549 }
550
551 void StorageTracker::willDeleteOrigin(const String& originIdentifier)
552 {
553     ASSERT(isMainThread());
554     ASSERT(!m_originSetMutex.tryLock());
555
556     m_originsBeingDeleted.add(originIdentifier);
557 }
558     
559 bool StorageTracker::canDeleteOrigin(const String& originIdentifier)
560 {
561     ASSERT(!m_databaseMutex.tryLock());
562     MutexLocker locker(m_originSetMutex);
563     return m_originsBeingDeleted.contains(originIdentifier);
564 }
565
566 void StorageTracker::cancelDeletingOrigin(const String& originIdentifier)
567 {
568     if (!m_isActive)
569         return;
570
571     MutexLocker locker(m_databaseMutex);
572     {
573         MutexLocker locker(m_originSetMutex);
574         if (!m_originsBeingDeleted.isEmpty())
575             m_originsBeingDeleted.remove(originIdentifier);
576     }
577 }
578
579 bool StorageTracker::isActive()
580 {
581     return m_isActive;
582 }
583
584 void StorageTracker::setIsActive(bool flag)
585 {
586     m_isActive = flag;
587 }
588     
589 String StorageTracker::databasePathForOrigin(const String& originIdentifier)
590 {
591     ASSERT(!m_databaseMutex.tryLock());
592     ASSERT(m_isActive);
593     
594     if (!m_database.isOpen())
595         return String();
596
597     SQLiteTransactionInProgressAutoCounter transactionCounter;
598
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());
602         return String();
603     }
604     pathStatement.bindText(1, originIdentifier);
605     int result = pathStatement.step();
606     if (result != SQLResultRow)
607         return String();
608
609     return pathStatement.getColumnText(0);
610 }
611     
612 long long StorageTracker::diskUsageForOrigin(SecurityOrigin* origin)
613 {
614     if (!m_isActive)
615         return 0;
616
617     MutexLocker locker(m_databaseMutex);
618
619     String path = databasePathForOrigin(origin->databaseIdentifier());
620     if (path.isEmpty())
621         return 0;
622
623     return SQLiteFileSystem::getDatabaseFileSize(path);
624 }
625
626 } // namespace WebCore